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}}