From 514e0260eba3b92bb19f964728d96e9c7398a412 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Mon, 9 Feb 2026 18:23:30 +0100 Subject: [PATCH] Working armor application --- daggerheart.mjs | 1 + module/applications/sheets/items/armor.mjs | 17 ++++++ module/config/generalConfig.mjs | 5 ++ module/data/activeEffect/_module.mjs | 4 ++ module/data/activeEffect/armorEffect.mjs | 61 ++++++++++++++++++---- module/data/item/armor.mjs | 15 ++++-- module/documents/activeEffect.mjs | 2 +- module/documents/actor.mjs | 4 ++ templates/sheets/items/armor/settings.hbs | 8 ++- 9 files changed, 99 insertions(+), 18 deletions(-) diff --git a/daggerheart.mjs b/daggerheart.mjs index a540de90..294170fb 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -43,6 +43,7 @@ CONFIG.Item.dataModels = models.items.config; CONFIG.ActiveEffect.documentClass = documents.DhActiveEffect; CONFIG.ActiveEffect.dataModels = models.activeEffects.config; +CONFIG.ActiveEffect.changeTypes = { ...CONFIG.ActiveEffect.changeTypes, ...models.activeEffects.changeTypes }; CONFIG.Combat.documentClass = documents.DhpCombat; CONFIG.Combat.dataModels = { base: models.DhCombat }; diff --git a/module/applications/sheets/items/armor.mjs b/module/applications/sheets/items/armor.mjs index 2550b415..081ada0e 100644 --- a/module/applications/sheets/items/armor.mjs +++ b/module/applications/sheets/items/armor.mjs @@ -34,6 +34,13 @@ export default class ArmorSheet extends ItemAttachmentSheet(DHBaseItemSheet) { ...super.PARTS }; + _attachPartListeners(partId, htmlElement, options) { + super._attachPartListeners(partId, htmlElement, options); + + for (const element of htmlElement.querySelectorAll('.base-score-input')) + element.addEventListener('change', this.updateArmorEffect.bind(this)); + } + /**@inheritdoc */ async _preparePartContext(partId, context) { await super._preparePartContext(partId, context); @@ -41,12 +48,22 @@ export default class ArmorSheet extends ItemAttachmentSheet(DHBaseItemSheet) { switch (partId) { case 'settings': context.features = this.document.system.armorFeatures.map(x => x.value); + context.armorScore = this.document.system.armorEffect?.system.armorData?.max; break; } return context; } + async updateArmorEffect(event) { + const value = Number.parseInt(event.target.value); + const armorEffect = this.document.system.armorEffect; + if (Number.isNaN(value) || !armorEffect) return; + + await armorEffect.system.updateArmorMax(value); + this.render(); + } + /** * Callback function used by `tagifyElement`. * @param {Array} selectedOptions - The currently selected tag objects. diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index 1d9f8126..80ac546e 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -854,6 +854,11 @@ export const sceneRangeMeasurementSetting = { }; export const activeEffectModes = { + armor: { + id: 'armor', + priority: 20, + label: 'TYPES.ActiveEffect.armor' + }, custom: { id: 'custom', priority: 0, diff --git a/module/data/activeEffect/_module.mjs b/module/data/activeEffect/_module.mjs index b4b89c78..62f10e3e 100644 --- a/module/data/activeEffect/_module.mjs +++ b/module/data/activeEffect/_module.mjs @@ -11,3 +11,7 @@ export const config = { horde: HordeEffect, armor: ArmorEffect }; + +export const changeTypes = { + armor: ArmorEffect.armorChangeEffect +}; diff --git a/module/data/activeEffect/armorEffect.mjs b/module/data/activeEffect/armorEffect.mjs index 5c125cea..61c897f9 100644 --- a/module/data/activeEffect/armorEffect.mjs +++ b/module/data/activeEffect/armorEffect.mjs @@ -9,12 +9,11 @@ export default class ArmorEffect extends foundry.data.ActiveEffectTypeDataModel type: new fields.StringField({ required: true, blank: false, - choices: CONFIG.DH.GENERAL.activeEffectModes, - initial: CONFIG.DH.GENERAL.activeEffectModes.add.id, + initial: CONFIG.DH.GENERAL.activeEffectModes.armor.id, validate: ArmorEffect.#validateType }), phase: new fields.StringField({ required: true, blank: false, initial: 'initial' }), - priority: new fields.NumberField(), + priority: new fields.NumberField({ integer: true, initial: 20 }), marked: new fields.NumberField({ required: true, integer: true, @@ -34,13 +33,57 @@ export default class ArmorEffect extends foundry.data.ActiveEffectTypeDataModel }; } + get armorData() { + if (this.changes.length !== 1) return { value: 0, max: 0 }; + return { value: this.changes[0].value, max: this.changes[0].max }; + } + + async updateArmorMax(newMax) { + if (this.changes.length !== 1) return; + const newChanges = this.changes.map(change => ({ + ...change, + max: newMax, + marked: Math.min(change.marked, newMax) + })); + await this.parent.update({ 'system.changes': newChanges }); + } + static applyChangeField(model, change, field) { return [model, change, field]; } + static armorChangeEffect = { + label: 'Armor', + defaultPriortiy: 20, + handler: (actor, change, _options, _field, replacementData) => { + game.system.api.documents.DhActiveEffect.applyChange( + actor, + { + ...change, + key: 'system.armorScore.value', + type: CONFIG.DH.GENERAL.activeEffectModes.add.id, + value: change.value + }, + replacementData + ); + game.system.api.documents.DhActiveEffect.applyChange( + actor, + { + ...change, + key: 'system.armorScore.max', + type: CONFIG.DH.GENERAL.activeEffectModes.add.id, + value: change.max + }, + replacementData + ); + return {}; + }, + render: null + }; + prepareBaseData() { for (const change of this.changes) { - change.key = 'system.armorScore.value'; + change.key = 'system.armorScore'; change.value = Math.min(change.max - change.marked, change.max); } } @@ -52,13 +95,9 @@ export default class ArmorEffect extends foundry.data.ActiveEffectTypeDataModel * @throws {Error} An error if the type string is malformed */ static #validateType(type) { - if (type.length < 3) throw new Error('must be at least three characters long'); - if (!/^custom\.-?\d+$/.test(type) && !type.split('.').every(s => /^[a-z0-9]+$/i.test(s))) { - throw new Error( - 'A change type must either be a sequence of dot-delimited, alpha-numeric substrings or of the form' + - ' "custom.{number}"' - ); - } + if (type !== CONFIG.DH.GENERAL.activeEffectModes.armor.id) + throw new Error('An armor effect must have change.type "armor"'); + return true; } } diff --git a/module/data/item/armor.mjs b/module/data/item/armor.mjs index 050b66d4..d56eb7d2 100644 --- a/module/data/item/armor.mjs +++ b/module/data/item/armor.mjs @@ -19,7 +19,6 @@ export default class DHArmor extends AttachableItem { ...super.defineSchema(), tier: new fields.NumberField({ required: true, integer: true, initial: 1, min: 1 }), equipped: new fields.BooleanField({ initial: false }), - baseScore: new fields.NumberField({ integer: true, initial: 0 }), armorFeatures: new fields.ArrayField( new fields.SchemaField({ value: new fields.StringField({ @@ -54,6 +53,11 @@ export default class DHArmor extends AttachableItem { ); } + get armorEffect() { + /* TODO: make armors only able to have on armor effect, or handle in some other way */ + return this.parent.effects.find(x => x.type === 'armor'); + } + /**@inheritdoc */ async getDescriptionData() { const baseDescription = this.description; @@ -159,8 +163,9 @@ export default class DHArmor extends AttachableItem { * @returns {string[]} An array of localized tag strings. */ _getTags() { + const baseScore = this.armorEffect?.system.armorData?.value; const tags = [ - `${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.baseScore}`, + `${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${baseScore}`, `${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseThresholds.base')}: ${this.baseThresholds.major} / ${this.baseThresholds.severe}` ]; @@ -173,8 +178,10 @@ export default class DHArmor extends AttachableItem { */ _getLabels() { const labels = []; - if (this.baseScore) - labels.push(`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.baseScore}`); + if (this.armorEffect) + labels.push( + `${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.armorEffect.system.armorData?.value}` + ); return labels; } diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs index 22a36653..8e7a2252 100644 --- a/module/documents/activeEffect.mjs +++ b/module/documents/activeEffect.mjs @@ -15,7 +15,7 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { } // Then apply the standard suppression rules - if (['weapon', 'armor'].includes(this.parent?.type)) { + if (['weapon', 'armor'].includes(this.parent?.type) && this.transfer) { return !this.parent.system.equipped; } diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index cb51a255..4807f497 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -992,4 +992,8 @@ export default class DhpActor extends Actor { return allTokens; } + + applyActiveEffects(phase) { + super.applyActiveEffects(phase); + } } diff --git a/templates/sheets/items/armor/settings.hbs b/templates/sheets/items/armor/settings.hbs index e7bde6fe..11cd2b8b 100644 --- a/templates/sheets/items/armor/settings.hbs +++ b/templates/sheets/items/armor/settings.hbs @@ -7,8 +7,12 @@ {{localize tabs.settings.label}} {{localize "DAGGERHEART.GENERAL.Tiers.singular"}} {{formField systemFields.tier value=source.system.tier}} - {{localize "DAGGERHEART.ITEMS.Armor.baseScore"}} - {{formField systemFields.baseScore value=source.system.baseScore}} + {{#if this.armorScore includeZero=true}} + {{localize "DAGGERHEART.ITEMS.Armor.baseScore"}} +
+ +
+ {{/if}} {{localize "TYPES.Item.feature"}}