diff --git a/assets/icons/arrow-dunk.png b/assets/icons/arrow-dunk.png new file mode 100644 index 00000000..1958713e Binary files /dev/null and b/assets/icons/arrow-dunk.png differ diff --git a/lang/en.json b/lang/en.json index 7dacd0ea..3c6a9770 100755 --- a/lang/en.json +++ b/lang/en.json @@ -439,7 +439,9 @@ }, "HUD": { "tokenHUD": { - "genericEffects": "Foundry Effects" + "genericEffects": "Foundry Effects", + "depositPartyTokens": "Deposit Party Tokens", + "retrievePartyTokens": "Retrieve Party Tokens" } }, "Levelup": { diff --git a/module/applications/hud/tokenHUD.mjs b/module/applications/hud/tokenHUD.mjs index 48d5ac89..a5a3f490 100644 --- a/module/applications/hud/tokenHUD.mjs +++ b/module/applications/hud/tokenHUD.mjs @@ -1,8 +1,11 @@ +import { shuffleArray } from '../../helpers/utils.mjs'; + export default class DHTokenHUD extends foundry.applications.hud.TokenHUD { static DEFAULT_OPTIONS = { classes: ['daggerheart'], actions: { - combat: DHTokenHUD.#onToggleCombat + combat: DHTokenHUD.#onToggleCombat, + togglePartyTokens: DHTokenHUD.#togglePartyTokens } }; @@ -19,6 +22,10 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD { async _prepareContext(options) { const context = await super._prepareContext(options); + context.partyOnCanvas = this.actor.system.partyMembers.some(member => member.getActiveTokens().length > 0); + context.icons.toggleParty = 'systems/daggerheart/assets/icons/arrow-dunk.png'; + context.actorType = this.actor.type; + context.usesEffects = this.actor.type !== 'party'; context.canToggleCombat = DHTokenHUD.#nonCombatTypes.includes(this.actor.type) ? false : context.canToggleCombat; @@ -59,6 +66,102 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD { } } + static async #togglePartyTokens(_, button) { + const icon = button.querySelector('img'); + icon.classList.toggle('flipped'); + button.dataset.tooltip = game.i18n.localize( + icon.classList.contains('flipped') + ? 'DAGGERHEART.APPLICATIONS.HUD.tokenHUD.retrievePartyTokens' + : 'DAGGERHEART.APPLICATIONS.HUD.tokenHUD.depositPartyTokens' + ); + + const animationDuration = 500; + const activeTokens = this.actor.system.partyMembers.flatMap(member => member.getActiveTokens()); + const { x: actorX, y: actorY } = this.actor.token; + if (activeTokens.length > 0) { + for (let token of activeTokens) { + await token.document.update( + { x: actorX, y: actorY, alpha: 0 }, + { animation: { duration: animationDuration } } + ); + setTimeout(() => token.document.delete(), animationDuration); + } + } else { + const activeScene = game.scenes.find(x => x.active); + const newTokens = await activeScene.createEmbeddedDocuments( + 'Token', + this.actor.system.partyMembers.map(member => ({ + ...member.getTokenDocument(), + actorId: member.id, + actorLink: true, + alpha: 0, + x: actorX, + y: actorY + })) + ); + + const { sizeX, sizeY } = activeScene.grid; + const nrRandomPositions = Math.ceil(newTokens.length / 8) * 8; + /* This is an overcomplicated mess, but I'm stupid */ + const positions = shuffleArray( + [...Array(nrRandomPositions).keys()].map((_, index) => { + const nonZeroIndex = index + 1; + const indexFloor = Math.floor(index / 8); + const distanceCoefficient = indexFloor + 1; + const side = 3 + indexFloor * 2; + const sideMiddle = Math.ceil(side / 2); + const inbetween = 1 + indexFloor * 2; + const inbetweenMiddle = Math.ceil(inbetween / 2); + + if (index < side) { + const distance = + nonZeroIndex === sideMiddle + ? 0 + : nonZeroIndex < sideMiddle + ? -nonZeroIndex + : nonZeroIndex - sideMiddle; + return { x: actorX - sizeX * distance, y: actorY - sizeY * distanceCoefficient }; + } else if (index < side + inbetween) { + const inbetweenIndex = nonZeroIndex - side; + const distance = + inbetweenIndex === inbetweenMiddle + ? 0 + : inbetweenIndex < inbetweenMiddle + ? -inbetweenIndex + : inbetweenIndex - inbetweenMiddle; + return { x: actorX + sizeX * distanceCoefficient, y: actorY + sizeY * distance }; + } else if (index < 2 * side + inbetween) { + const sideIndex = nonZeroIndex - side - inbetween; + const distance = + sideIndex === sideMiddle + ? 0 + : sideIndex < sideMiddle + ? sideIndex + : -(sideIndex - sideMiddle); + return { x: actorX + sizeX * distance, y: actorY + sizeY * distanceCoefficient }; + } else { + const inbetweenIndex = nonZeroIndex - 2 * side - inbetween; + const distance = + inbetweenIndex === inbetweenMiddle + ? 0 + : inbetweenIndex < inbetweenMiddle + ? inbetweenIndex + : -(inbetweenIndex - inbetweenMiddle); + return { x: actorX - sizeX * distanceCoefficient, y: actorY + sizeY * distance }; + } + }) + ); + + for (let token of newTokens) { + const position = positions.pop(); + token.update( + { x: position.x, y: position.y, alpha: 1 }, + { animation: { duration: animationDuration } } + ); + } + } + } + _getStatusEffectChoices() { // Include all HUD-enabled status effects const choices = {}; diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index dbf66ff4..3044cd71 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -418,3 +418,15 @@ export async function createEmbeddedItemsWithEffects(actor, baseData) { export const slugify = name => { return name.toLowerCase().replaceAll(' ', '-').replaceAll('.', ''); }; + +export function shuffleArray(array) { + let currentIndex = array.length; + while (currentIndex != 0) { + let randomIndex = Math.floor(Math.random() * currentIndex); + currentIndex--; + + [array[currentIndex], array[randomIndex]] = [array[randomIndex], array[currentIndex]]; + } + + return array; +} diff --git a/styles/less/hud/token-hud/token-hud.less b/styles/less/hud/token-hud/token-hud.less index 7d231e8c..c73c611b 100644 --- a/styles/less/hud/token-hud/token-hud.less +++ b/styles/less/hud/token-hud/token-hud.less @@ -6,5 +6,13 @@ font-weight: bold; } } + + .clown-car img { + transition: 0.5s; + + &.flipped { + transform: scaleX(-1); + } + } } } diff --git a/templates/hud/tokenHUD.hbs b/templates/hud/tokenHUD.hbs index 197b94f7..0ea047c5 100644 --- a/templates/hud/tokenHUD.hbs +++ b/templates/hud/tokenHUD.hbs @@ -40,6 +40,7 @@ {{/if}} + {{#if usesEffects}} @@ -54,6 +55,13 @@ {{/each}} {{/if}} + {{/if}} + + {{#if (eq actorType 'party')}} + + {{/if}}