diff --git a/lang/en.json b/lang/en.json index ab348696..26647cc2 100755 --- a/lang/en.json +++ b/lang/en.json @@ -1849,6 +1849,9 @@ "Attachments": { "attachHint": "Drop items here to attach them", "transferHint": "If checked, this effect will be applied to any actor that owns this Effect's parent Item. The effect is always applied if this Item is attached to another one." + }, + "Armor": { + "newArmorEffect": "Armor Effect" } }, "GENERAL": { @@ -2975,7 +2978,9 @@ "tokenActorMissing": "{name} is missing an Actor", "tokenActorsMissing": "[{names}] missing Actors", "domainTouchRequirement": "This domain card requires {nr} {domain} cards in the loadout to be used", - "knowTheTide": "Know The Tide gained a token" + "knowTheTide": "Know The Tide gained a token", + "cannotAlterArmorEffectChanges": "You cannot alter the changes length of an armor effect", + "cannotAlterArmorEffectType": "You cannot alter the type of armor effect changes" }, "Sidebar": { "actorDirectory": { diff --git a/module/applications/sheets-configs/armorActiveEffectConfig.mjs b/module/applications/sheets-configs/armorActiveEffectConfig.mjs index c70422b9..131558a0 100644 --- a/module/applications/sheets-configs/armorActiveEffectConfig.mjs +++ b/module/applications/sheets-configs/armorActiveEffectConfig.mjs @@ -10,7 +10,6 @@ export default class ArmorActiveEffectConfig extends HandlebarsApplicationMixin( closeOnSubmit: false }, actions: { - addEffect: ArmorActiveEffectConfig.#addEffect, finish: ArmorActiveEffectConfig.#finish } }; @@ -54,11 +53,6 @@ export default class ArmorActiveEffectConfig extends HandlebarsApplicationMixin( this.render(); } - static #addEffect() { - this.document.update({ 'system.changes': [...this.document.system.changes, {}] }); - this.render(); - } - static #finish() { this.close(); } diff --git a/module/applications/sheets/items/armor.mjs b/module/applications/sheets/items/armor.mjs index 081ada0e..de61e5b1 100644 --- a/module/applications/sheets/items/armor.mjs +++ b/module/applications/sheets/items/armor.mjs @@ -48,7 +48,7 @@ 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; + context.armorScore = this.document.system.armorData.max; break; } diff --git a/module/data/activeEffect/armorEffect.mjs b/module/data/activeEffect/armorEffect.mjs index 61c897f9..bd397abe 100644 --- a/module/data/activeEffect/armorEffect.mjs +++ b/module/data/activeEffect/armorEffect.mjs @@ -1,3 +1,7 @@ +/** + * ArmorEffects are ActiveEffects that have a static changes field of length 1. It includes current and maximum armor. + * When applied to a character, it adds to their currently marked and maximum armor. + */ export default class ArmorEffect extends foundry.data.ActiveEffectTypeDataModel { static defineSchema() { const fields = foundry.data.fields; @@ -14,7 +18,7 @@ export default class ArmorEffect extends foundry.data.ActiveEffectTypeDataModel }), phase: new fields.StringField({ required: true, blank: false, initial: 'initial' }), priority: new fields.NumberField({ integer: true, initial: 20 }), - marked: new fields.NumberField({ + value: new fields.NumberField({ required: true, integer: true, initial: 0, @@ -28,28 +32,35 @@ export default class ArmorEffect extends foundry.data.ActiveEffectTypeDataModel min: 1, label: 'DAGGERHEART.GENERAL.max' }) - }) + }), + { + initial: [ + { + type: CONFIG.DH.GENERAL.activeEffectModes.armor.id, + phase: 'initial', + priority: 20, + value: 0, + max: 1 + } + ] + } ) }; } - get armorData() { - if (this.changes.length !== 1) return { value: 0, max: 0 }; - return { value: this.changes[0].value, max: this.changes[0].max }; - } + /* Type Functions */ - 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 }); - } + /** + * Validate that an {@link EffectChangeData#type} string is well-formed. + * @param {string} type The string to be validated + * @returns {true} + * @throws {Error} An error if the type string is malformed + */ + static #validateType(type) { + if (type !== CONFIG.DH.GENERAL.activeEffectModes.armor.id) + throw new Error('An armor effect must have change.type "armor"'); - static applyChangeField(model, change, field) { - return [model, change, field]; + return true; } static armorChangeEffect = { @@ -81,23 +92,71 @@ export default class ArmorEffect extends foundry.data.ActiveEffectTypeDataModel render: null }; + /* Helpers */ + + get armorChange() { + if (this.changes.length !== 1) + throw new Error('Unexpected error. An armor effect should have a changes field of length 1.'); + return this.changes[0]; + } + + get armorData() { + return { value: this.armorChange.value, max: this.armorChange.max }; + } + + async updateArmorMax(newMax) { + const newChanges = [ + { + ...this.armorChange, + max: newMax, + value: Math.min(this.armorChange.value, newMax) + } + ]; + await this.parent.update({ 'system.changes': newChanges }); + } + + /* Overrides */ + prepareBaseData() { - for (const change of this.changes) { - change.key = 'system.armorScore'; - change.value = Math.min(change.max - change.marked, change.max); + const armorChange = this.armorChange; + armorChange.key = 'system.armorScore'; + } + + static getDefaultEffectData() { + return { + type: 'armor', + name: game.i18n.localize('DAGGERHEART.EFFECTS.Armor.newArmorEffect'), + img: 'icons/equipment/chest/breastplate-helmet-metal.webp' + }; + } + + async _preCreate(data, options, user) { + const allowed = await super._preCreate(data, options, user); + if (allowed === false) return; + + await this.updateSource({ ...ArmorEffect.getDefaultEffectData(), data }); + } + + async _preUpdate(changes, options, user) { + const allowed = await super._preUpdate(changes, options, user); + if (allowed === false) return false; + + if (changes.system?.changes) { + const changesChanged = changes.system.changes.length !== this.changes.length; + if (changesChanged) { + ui.notifications.error( + game.i18n.localize('DAGGERHEART.UI.Notifications.cannotAlterArmorEffectChanges') + ); + return false; + } + + if ( + changes.system.changes.length === 1 && + changes.system.changes[0].type !== CONFIG.DH.GENERAL.activeEffectModes.armor.id + ) { + ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.cannotAlterArmorEffectType')); + return false; + } } } - - /** - * Validate that an {@link EffectChangeData#type} string is well-formed. - * @param {string} type The string to be validated - * @returns {true} - * @throws {Error} An error if the type string is malformed - */ - static #validateType(type) { - 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/actor/character.mjs b/module/data/actor/character.mjs index b5edbb23..128fef78 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -458,6 +458,11 @@ export default class DhCharacter extends BaseDataActor { return this.parent.items.find(x => x.type === 'armor' && x.system.equipped); } + /* TODO: Prep datastructure to be useful when applying automatic armor damage order */ + get armorEffects() { + return Array.from(this.parent.allApplicableEffects()); + } + get activeBeastform() { return this.parent.effects.find(x => x.type === 'beastform'); } diff --git a/module/data/item/armor.mjs b/module/data/item/armor.mjs index d56eb7d2..117f3963 100644 --- a/module/data/item/armor.mjs +++ b/module/data/item/armor.mjs @@ -54,10 +54,16 @@ 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'); } + get armorData() { + const armorEffect = this.armorEffect; + if (!armorEffect) return { value: 0, max: 0 }; + + return armorEffect.system.armorData; + } + /**@inheritdoc */ async getDescriptionData() { const baseDescription = this.description; @@ -73,6 +79,17 @@ export default class DHArmor extends AttachableItem { return { prefix, value: baseDescription, suffix: null }; } + /**@inheritdoc */ + async _onCreate(_data, _options, userId) { + if (userId !== game.user.id) return; + + if (!this.parent.effects.some(x => x.type === 'armor')) { + this.parent.createEmbeddedDocuments('ActiveEffect', [ + game.system.api.data.activeEffects.ArmorEffect.getDefaultEffectData() + ]); + } + } + /**@inheritdoc */ async _preUpdate(changes, options, user) { const allowed = await super._preUpdate(changes, options, user); @@ -163,9 +180,8 @@ 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')}: ${baseScore}`, + `${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.armorData.value}`, `${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseThresholds.base')}: ${this.baseThresholds.major} / ${this.baseThresholds.severe}` ]; @@ -177,11 +193,7 @@ export default class DHArmor extends AttachableItem { * @returns {(string | { value: string, icons: string[] })[]} An array of localized strings and damage label objects. */ _getLabels() { - const labels = []; - if (this.armorEffect) - labels.push( - `${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.armorEffect.system.armorData?.value}` - ); + const labels = [`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.armorData.value}`]; return labels; } diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs index 8e7a2252..ac30c678 100644 --- a/module/documents/activeEffect.mjs +++ b/module/documents/activeEffect.mjs @@ -146,18 +146,15 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { /**@inheritdoc*/ static applyChangeField(model, change, field) { - if (this.system?.applyChangeField) - super.applyChangeField(...this.system.applyChangeField(model, change, field)); - change.value = Number.isNumeric(change.value) ? change.value : DhActiveEffect.getChangeValue(model, change, change.effect); super.applyChangeField(model, change, field); } - _applyLegacy(actor, change, changes) { + _applyChangeUnguided(actor, change, changes, options) { change.value = DhActiveEffect.getChangeValue(actor, change, change.effect); - super._applyLegacy(actor, change, changes); + super._applyChangeUnguided(actor, change, changes, options); } static getChangeValue(model, change, effect) { diff --git a/templates/sheets/activeEffect/armor/settings.hbs b/templates/sheets/activeEffect/armor/settings.hbs index 087cdef6..c0f77b35 100644 --- a/templates/sheets/activeEffect/armor/settings.hbs +++ b/templates/sheets/activeEffect/armor/settings.hbs @@ -1,10 +1,8 @@
- -
{{#each source.system.changes as |change index|}}
- {{formGroup @root.systemFields.changes.element.fields.marked name=(concat 'system.changes.' index '.marked') value=change.marked localize=true}} + {{formGroup @root.systemFields.changes.element.fields.value name=(concat 'system.changes.' index '.value') value=change.value localize=true}} {{formGroup @root.systemFields.changes.element.fields.max name=(concat 'system.changes.' index '.max') value=change.max localize=true}}
{{/each}}