From d38b924cad988bd5178528601ca613467f21f500 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Mon, 15 Dec 2025 19:58:23 +0100 Subject: [PATCH] Added support for adversary actor sizes --- daggerheart.mjs | 2 ++ lang/en.json | 16 +++++++-- .../settings/homebrewSettings.mjs | 12 +++++++ module/config/actorConfig.mjs | 33 +++++++++++++++++++ module/data/actor/base.mjs | 18 +++++++++- module/data/actor/party.mjs | 9 ++++- module/data/settings/Homebrew.mjs | 32 ++++++++++++++++++ module/documents/_module.mjs | 1 + module/documents/scene.mjs | 22 +++++++++++++ module/documents/token.mjs | 14 ++++++++ .../less/sheets/actors/adversary/header.less | 1 + styles/less/ui/settings/settings.less | 6 ++++ .../settings/homebrew-settings/settings.hbs | 14 ++++++++ .../adversary-settings/details.hbs | 1 + templates/sheets/actors/adversary/header.hbs | 4 +++ 15 files changed, 180 insertions(+), 5 deletions(-) create mode 100644 module/documents/scene.mjs diff --git a/daggerheart.mjs b/daggerheart.mjs index 644d6d86..5b98e0b3 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -54,6 +54,8 @@ CONFIG.Canvas.rulerClass = placeables.DhRuler; CONFIG.Canvas.layers.templates.layerClass = placeables.DhTemplateLayer; CONFIG.MeasuredTemplate.objectClass = placeables.DhMeasuredTemplate; +CONFIG.Scene.documentClass = documents.DhScene; + CONFIG.Token.documentClass = documents.DhToken; CONFIG.Token.prototypeSheetClass = applications.sheetConfigs.DhPrototypeTokenConfig; CONFIG.Token.objectClass = placeables.DhTokenPlaceable; diff --git a/lang/en.json b/lang/en.json index 32717fcb..cd8d10ae 100755 --- a/lang/en.json +++ b/lang/en.json @@ -1146,6 +1146,14 @@ "rect": "Rectangle", "ray": "Ray" }, + "TokenSize": { + "tiny": "Tiny", + "small": "Small", + "medium": "Medium", + "large": "Large", + "huge": "Huge", + "gargantuan": "Gargantuan" + }, "Traits": { "agility": { "name": "Agility", @@ -2166,6 +2174,7 @@ "plural": "Targets" }, "title": "Title", + "tokenSize": "Token Size", "total": "Total", "traitModifier": "Trait Modifier", "true": "True", @@ -2527,8 +2536,8 @@ "enabled": { "label": "Enabled" }, "tokens": { "label": "Tokens" } }, - "massiveDamage":{ - "title":"Massive Damage", + "massiveDamage": { + "title": "Massive Damage", "enabled": { "label": "Enabled" } } } @@ -2800,7 +2809,8 @@ "companionPartnerLevelBlock": "The companion needs an assigned partner to level up.", "configureAttribution": "Configure Attribution", "deleteItem": "Delete Item", - "immune": "Immune" + "immune": "Immune", + "tokenSize": "The token size used on the canvas" } } } diff --git a/module/applications/settings/homebrewSettings.mjs b/module/applications/settings/homebrewSettings.mjs index 6d36a2b3..fe93a4a5 100644 --- a/module/applications/settings/homebrewSettings.mjs +++ b/module/applications/settings/homebrewSettings.mjs @@ -44,6 +44,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli deleteAdversaryType: this.deleteAdversaryType, selectAdversaryType: this.selectAdversaryType, save: this.save, + resetTokenSizes: this.resetTokenSizes, reset: this.reset }, form: { handler: this.updateData, submitOnChange: true } @@ -424,6 +425,17 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli this.close(); } + static async resetTokenSizes() { + await this.settings.updateSource({ + tokenSizes: Object.values(this.settings.schema.fields.tokenSizes.fields).reduce( + (acc, field) => ({ ...acc, [field.name]: field.initial }), + {} + ) + }); + + this.render(); + } + static async reset() { const confirmed = await foundry.applications.api.DialogV2.confirm({ window: { diff --git a/module/config/actorConfig.mjs b/module/config/actorConfig.mjs index 7ff42754..6496a459 100644 --- a/module/config/actorConfig.mjs +++ b/module/config/actorConfig.mjs @@ -211,6 +211,39 @@ export const adversaryTraits = { } }; +export const tokenSize = { + tiny: { + id: 'tiny', + value: 1, + label: 'DAGGERHEART.CONFIG.TokenSize.tiny' + }, + small: { + id: 'small', + value: 2, + label: 'DAGGERHEART.CONFIG.TokenSize.small' + }, + medium: { + id: 'medium', + value: 3, + label: 'DAGGERHEART.CONFIG.TokenSize.medium' + }, + large: { + id: 'large', + value: 4, + label: 'DAGGERHEART.CONFIG.TokenSize.large' + }, + huge: { + id: 'huge', + value: 5, + label: 'DAGGERHEART.CONFIG.TokenSize.huge' + }, + gargantuan: { + id: 'gargantuan', + value: 6, + label: 'DAGGERHEART.CONFIG.TokenSize.gargantuan' + } +}; + export const levelChoices = { attributes: { name: 'attributes', diff --git a/module/data/actor/base.mjs b/module/data/actor/base.mjs index c7f7ee75..310374ff 100644 --- a/module/data/actor/base.mjs +++ b/module/data/actor/base.mjs @@ -41,7 +41,8 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel { settingSheet: null, hasResistances: true, hasAttribution: false, - hasLimitedView: true + hasLimitedView: true, + usesSize: true }; } @@ -76,6 +77,13 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel { 'DAGGERHEART.GENERAL.DamageResistance.magicalReduction' ) }); + if (this.metadata.usesSize) + schema.size = new fields.StringField({ + required: true, + nullable: false, + choices: CONFIG.DH.ACTOR.tokenSize, + initial: CONFIG.DH.ACTOR.tokenSize.medium.id + }); return schema; } @@ -138,6 +146,14 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel { options.scrollingTextData = textData; } + if (!this.parent.isToken && changes.system?.size) { + const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes; + this.parent.prototypeToken.update({ + width: tokenSizes[changes.system.size], + height: tokenSizes[changes.system.size] + }); + } + if (changes.system?.resources) { const defeatedSettings = game.settings.get( CONFIG.DH.id, diff --git a/module/data/actor/party.mjs b/module/data/actor/party.mjs index b306c486..e72f4b78 100644 --- a/module/data/actor/party.mjs +++ b/module/data/actor/party.mjs @@ -2,6 +2,13 @@ import BaseDataActor from './base.mjs'; import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs'; export default class DhParty extends BaseDataActor { + /**@inheritdoc */ + static get metadata() { + return foundry.utils.mergeObject(super.metadata, { + usesSize: false + }); + } + /**@inheritdoc */ static defineSchema() { const fields = foundry.data.fields; @@ -26,7 +33,7 @@ export default class DhParty extends BaseDataActor { /* -------------------------------------------- */ isItemValid(source) { - return ["weapon", "armor", "consumable", "loot"].includes(source.type); + return ['weapon', 'armor', 'consumable', 'loot'].includes(source.type); } prepareBaseData() { diff --git a/module/data/settings/Homebrew.mjs b/module/data/settings/Homebrew.mjs index 6f280cbd..12ffa87f 100644 --- a/module/data/settings/Homebrew.mjs +++ b/module/data/settings/Homebrew.mjs @@ -40,6 +40,38 @@ export default class DhHomebrew extends foundry.abstract.DataModel { traitArray: new fields.ArrayField(new fields.NumberField({ required: true, integer: true }), { initial: () => [2, 1, 1, 0, 0, -1] }), + tokenSizes: new fields.SchemaField({ + tiny: new fields.NumberField({ + integer: false, + initial: 0.4, + label: 'DAGGERHEART.CONFIG.TokenSize.tiny' + }), + small: new fields.NumberField({ + integer: false, + initial: 0.6, + label: 'DAGGERHEART.CONFIG.TokenSize.small' + }), + medium: new fields.NumberField({ + integer: false, + initial: 1, + label: 'DAGGERHEART.CONFIG.TokenSize.medium' + }), + large: new fields.NumberField({ + integer: false, + initial: 2, + label: 'DAGGERHEART.CONFIG.TokenSize.large' + }), + huge: new fields.NumberField({ + integer: false, + initial: 3, + label: 'DAGGERHEART.CONFIG.TokenSize.huge' + }), + gargantuan: new fields.NumberField({ + integer: false, + initial: 4, + label: 'DAGGERHEART.CONFIG.TokenSize.gargantuan' + }) + }), currency: new fields.SchemaField({ title: new fields.StringField({ required: true, diff --git a/module/documents/_module.mjs b/module/documents/_module.mjs index af1e9942..22718bea 100644 --- a/module/documents/_module.mjs +++ b/module/documents/_module.mjs @@ -4,6 +4,7 @@ export { default as DhpCombat } from './combat.mjs'; export { default as DHCombatant } from './combatant.mjs'; export { default as DhActiveEffect } from './activeEffect.mjs'; export { default as DhChatMessage } from './chatMessage.mjs'; +export { default as DhScene } from './scene.mjs'; export { default as DhToken } from './token.mjs'; export { default as DhTooltipManager } from './tooltipManager.mjs'; export { default as DhTemplateManager } from './templateManager.mjs'; diff --git a/module/documents/scene.mjs b/module/documents/scene.mjs new file mode 100644 index 00000000..860f5eaa --- /dev/null +++ b/module/documents/scene.mjs @@ -0,0 +1,22 @@ +export default class DhScene extends Scene { + /** A map of `TokenDocument` IDs embedded in this scene long with new dimensions from actor size-category changes */ + #sizeSyncBatch = new Map(); + + /** Synchronize a token's dimensions with its actor's size category. */ + syncTokenDimensions(tokenDoc, dimensions) { + if (!tokenDoc.parent?.tokens.has(tokenDoc.id)) return; + this.#sizeSyncBatch.set(tokenDoc.id, dimensions); + this.#processSyncBatch(); + } + + /** Retrieve size and clear size-sync batch, make updates. */ + #processSyncBatch = foundry.utils.debounce(() => { + const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes; + const entries = this.#sizeSyncBatch + .entries() + .toArray() + .map(([_id, { width, height }]) => ({ _id, width: tokenSizes[width], height: tokenSizes[height] })); + this.#sizeSyncBatch.clear(); + this.updateEmbeddedDocuments('Token', entries, { animation: { movementSpeed: 1 } }); + }, 0); +} diff --git a/module/documents/token.mjs b/module/documents/token.mjs index 6996708b..a08bb365 100644 --- a/module/documents/token.mjs +++ b/module/documents/token.mjs @@ -100,4 +100,18 @@ export default class DHToken extends TokenDocument { } super.deleteCombatants(tokens, combat ?? {}); } + + /**@inheritdoc */ + _onRelatedUpdate(update = {}, operation = {}) { + super._onRelatedUpdate(update, operation); + + if (!this.actor?.isOwner) return; + const activeGM = game.users.activeGM; // Let the active GM take care of updates if available + if (this.actor.system.metadata.usesSize && activeGM && game.user.id === activeGM.id) { + const dimensions = { height: this.actor.system.size, width: this.actor.system.size }; + if (dimensions.width !== this.width || dimensions.height !== this.height) { + this.parent?.syncTokenDimensions(this, dimensions); + } + } + } } diff --git a/styles/less/sheets/actors/adversary/header.less b/styles/less/sheets/actors/adversary/header.less index d4a7812e..0fe754ef 100644 --- a/styles/less/sheets/actors/adversary/header.less +++ b/styles/less/sheets/actors/adversary/header.less @@ -39,6 +39,7 @@ .tag { display: flex; flex-direction: row; + gap: 4px; justify-content: center; align-items: center; padding: 3px 5px; diff --git a/styles/less/ui/settings/settings.less b/styles/less/ui/settings/settings.less index 49c9fc7c..34f17d53 100644 --- a/styles/less/ui/settings/settings.less +++ b/styles/less/ui/settings/settings.less @@ -16,6 +16,12 @@ } } + &.three-columns { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: 2px; + } + &.six-columns { display: grid; grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr; diff --git a/templates/settings/homebrew-settings/settings.hbs b/templates/settings/homebrew-settings/settings.hbs index e7340323..5da053f4 100644 --- a/templates/settings/homebrew-settings/settings.hbs +++ b/templates/settings/homebrew-settings/settings.hbs @@ -25,6 +25,20 @@ {{/each}} +
+ + {{localize "Token Sizes"}} + + + + {{formGroup settingFields.schema.fields.tokenSizes.fields.tiny value=settingFields._source.tokenSizes.tiny localize=true}} + {{formGroup settingFields.schema.fields.tokenSizes.fields.small value=settingFields._source.tokenSizes.small localize=true}} + {{formGroup settingFields.schema.fields.tokenSizes.fields.medium value=settingFields._source.tokenSizes.medium localize=true}} + {{formGroup settingFields.schema.fields.tokenSizes.fields.large value=settingFields._source.tokenSizes.large localize=true}} + {{formGroup settingFields.schema.fields.tokenSizes.fields.huge value=settingFields._source.tokenSizes.huge localize=true}} + {{formGroup settingFields.schema.fields.tokenSizes.fields.gargantuan value=settingFields._source.tokenSizes.gargantuan localize=true}} +
+
{{localize "DAGGERHEART.SETTINGS.Homebrew.currency.title"}} diff --git a/templates/sheets-settings/adversary-settings/details.hbs b/templates/sheets-settings/adversary-settings/details.hbs index 194c7f0c..0eb8da30 100644 --- a/templates/sheets-settings/adversary-settings/details.hbs +++ b/templates/sheets-settings/adversary-settings/details.hbs @@ -13,6 +13,7 @@ {{/if}} {{formGroup systemFields.difficulty value=document._source.system.difficulty localize=true}} + {{formGroup systemFields.size value=document._source.system.size label=(localize "DAGGERHEART.GENERAL.tokenSize") localize=true}} {{formField systemFields.description value=document._source.system.description label=(localize "DAGGERHEART.ACTORS.Adversary.FIELDS.description.label")}} {{formField systemFields.motivesAndTactics value=document._source.system.motivesAndTactics label=(localize "DAGGERHEART.ACTORS.Adversary.FIELDS.motivesAndTactics.label")}}
diff --git a/templates/sheets/actors/adversary/header.hbs b/templates/sheets/actors/adversary/header.hbs index e6f829b8..6492fac8 100644 --- a/templates/sheets/actors/adversary/header.hbs +++ b/templates/sheets/actors/adversary/header.hbs @@ -21,6 +21,10 @@ /{{localize "DAGGERHEART.GENERAL.HitPoints.short"}} {{/if}} +
+ + {{localize (concat "DAGGERHEART.CONFIG.TokenSize." source.system.size)}} +