From 1cdabf15a5c3f9d1275989f8263629edbb95b4ac Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 21 Mar 2026 18:55:03 +0100 Subject: [PATCH] Readded so that armor items have their system defined armor instead of using an ActiveEffect --- .../dialogs/damageReductionDialog.mjs | 19 ++-- .../applications/sheets/actors/character.mjs | 86 +++++++++-------- module/applications/sheets/items/armor.mjs | 12 --- module/data/activeEffect/baseEffect.mjs | 2 +- module/data/actor/character.mjs | 94 ++++++++++++------- module/data/item/armor.mjs | 40 +++----- module/data/item/base.mjs | 13 +++ module/helpers/utils.mjs | 37 ++++++++ templates/sheets/items/armor/header.hbs | 2 +- templates/sheets/items/armor/settings.hbs | 10 +- templates/ui/tooltip/armorManagement.hbs | 44 +++------ 11 files changed, 198 insertions(+), 161 deletions(-) diff --git a/module/applications/dialogs/damageReductionDialog.mjs b/module/applications/dialogs/damageReductionDialog.mjs index 50225528..6576bd2b 100644 --- a/module/applications/dialogs/damageReductionDialog.mjs +++ b/module/applications/dialogs/damageReductionDialog.mjs @@ -21,15 +21,17 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap this.rulesDefault ); - const allArmorEffects = Array.from(actor.allApplicableEffects()).filter(x => x.system.armorData); - const orderedArmorEffects = game.system.api.data.activeEffects.changeTypes.armor.orderEffectsForAutoChange( - allArmorEffects, + const allArmorSources = Array.from(actor.allApplicableEffects()).filter(x => x.system.armorData); + if (actor.system.armor) allArmorSources.push(actor.system.armor); + + const orderedArmorSources = game.system.api.data.activeEffects.changeTypes.armor.orderEffectsForAutoChange( + allArmorSources, true ); - const armor = orderedArmorEffects.reduce((acc, effect) => { - const { current, max } = effect.system.armorData; + const armor = orderedArmorSources.reduce((acc, source) => { + const { current, max } = source.type === 'armor' ? source.system.armor : source.system.armorData; acc.push({ - effect: effect, + effect: source, marks: [...Array(max).keys()].reduce((acc, _, index) => { const spent = index < current; acc[foundry.utils.randomID()] = { selected: false, disabled: spent, spent }; @@ -159,8 +161,11 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap const parent = source.effect.origin ? await foundry.utils.fromUuid(source.effect.origin) : source.effect.parent; + + const useEffectName = parent.type === 'armor' || parent instanceof Actor; + const label = useEffectName ? source.effect.name : parent.name; armorSources.push({ - label: parent.name, + label: label, uuid: source.effect.uuid, marks: source.marks }); diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index bfcc13a2..0172bff7 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -948,8 +948,8 @@ export default class CharacterSheet extends DHBaseActorSheet { const origin = effect.origin ? await foundry.utils.fromUuid(effect.origin) : effect.parent; if (!effect.system.armorData || effect.disabled || effect.isSuppressed) continue; - const originIsActor = origin instanceof Actor; - const name = originIsActor ? effect.name : origin.name; + const useEffectName = origin.type === 'armor' || origin instanceof Actor; + const name = useEffectName ? effect.name : origin.name; armorSources.push({ uuid: effect.uuid, name, @@ -957,6 +957,15 @@ export default class CharacterSheet extends DHBaseActorSheet { }); } + if (this.document.system.armor) { + armorSources.push({ + ...this.document.system.armor.system.armor, + uuid: this.document.system.armor.uuid, + name: this.document.system.armor.name, + isArmorItem: true + }); + } + if (!armorSources.length) return; const useResourcePips = game.settings.get( @@ -980,11 +989,6 @@ export default class CharacterSheet extends DHBaseActorSheet { direction: 'DOWN' }); - html.querySelectorAll('.armor-marks-input').forEach(element => { - element.addEventListener('blur', CharacterSheet.armorSourceUpdate); - element.addEventListener('input', CharacterSheet.armorSourceInput); - }); - html.querySelectorAll('.armor-slot').forEach(element => { element.addEventListener('click', CharacterSheet.armorSourcePipUpdate); }); @@ -999,49 +1003,49 @@ export default class CharacterSheet extends DHBaseActorSheet { } /** Update specific armor source */ - static async armorSourceUpdate(event) { - const effect = await foundry.utils.fromUuid(event.target.dataset.uuid); - const armorChange = effect.system.armorChange; - if (!armorChange) return; - const current = Math.max(Math.min(Number.parseInt(event.target.value), effect.system.armorData.max), 0); - - const newChanges = effect.system.changes.map(change => ({ - ...change, - value: change.type === 'armor' ? { ...change.value, current } : change.value - })); - - event.target.value = current; - const progressBar = event.target.closest('.status-bar.armor-slots').querySelector('progress'); - progressBar.value = current; - - await effect.update({ 'system.changes': newChanges }); - } - static async armorSourcePipUpdate(event) { const target = event.target.closest('.armor-slot'); - const effect = await foundry.utils.fromUuid(target.dataset.uuid); - const armorChange = effect.system.armorChange; - if (!armorChange) return; + const { uuid, value, isArmorItem: isArmorItemString } = target.dataset; + const isArmorItem = Boolean(isArmorItemString); - const { current } = effect.system.armorData; + let inputValue = Number.parseInt(value); + let decreasing = false; + let newCurrent = 0; - const inputValue = Number.parseInt(target.dataset.value); - const decreasing = current >= inputValue; - const newCurrent = decreasing ? inputValue - 1 : inputValue; + if (isArmorItem) { + const armor = await foundry.utils.fromUuid(uuid); + decreasing = armor.system.armor.current >= inputValue; + newCurrent = decreasing ? inputValue - 1 : inputValue; - const newChanges = effect.system.changes.map(change => ({ - ...change, - value: change.type === 'armor' ? { ...change.value, current: newCurrent } : change.value - })); + await armor.update({ 'system.armor.current': newCurrent }); + } else { + const effect = await foundry.utils.fromUuid(uuid); + const armorChange = effect.system.armorChange; + if (!armorChange) return; + + const { current } = effect.system.armorData; + decreasing = current >= inputValue; + newCurrent = decreasing ? inputValue - 1 : inputValue; + + const newChanges = effect.system.changes.map(change => ({ + ...change, + value: change.type === 'armor' ? { ...change.value, current: newCurrent } : change.value + })); + + await effect.update({ 'system.changes': newChanges }); + } const container = target.closest('.slot-bar'); for (const armorSlot of container.querySelectorAll('.armor-slot i')) { - const marked = !decreasing && Number.parseInt(armorSlot.dataset.index) < newCurrent; - armorSlot.classList.toggle('fa-shield', marked); - armorSlot.classList.toggle('fa-shield-halved', !marked); + const index = Number.parseInt(armorSlot.dataset.index); + if (decreasing && index >= newCurrent) { + armorSlot.classList.remove('fa-shield'); + armorSlot.classList.add('fa-shield-halved'); + } else if (!decreasing && index < newCurrent) { + armorSlot.classList.add('fa-shield'); + armorSlot.classList.remove('fa-shield-halved'); + } } - - await effect.update({ 'system.changes': newChanges }); } static async #toggleResourceManagement(event, button) { diff --git a/module/applications/sheets/items/armor.mjs b/module/applications/sheets/items/armor.mjs index 46d03ede..54685fee 100644 --- a/module/applications/sheets/items/armor.mjs +++ b/module/applications/sheets/items/armor.mjs @@ -34,13 +34,6 @@ 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); @@ -48,11 +41,6 @@ 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.armorData.max; - break; - case 'effects': - context.effects.actives = context.effects.actives.filter(x => !x.system.armorData); - context.effects.inactives = context.effects.inactives.filter(x => !x.system.armorData); break; } diff --git a/module/data/activeEffect/baseEffect.mjs b/module/data/activeEffect/baseEffect.mjs index 81ddb4ad..c00b8cf7 100644 --- a/module/data/activeEffect/baseEffect.mjs +++ b/module/data/activeEffect/baseEffect.mjs @@ -151,7 +151,7 @@ export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel { const newArmorTotal = (changed.system?.changes ?? []).reduce((acc, change) => { if (change.type === 'armor') acc += change.value.current; return acc; - }, 0); + }, this.parent.actor.system.armor?.system?.armor?.current ?? 0); const armorData = getScrollTextData(this.parent.actor, { value: newArmorTotal }, 'armor'); options.scrollingTextData = [armorData]; diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index b8846a75..216a0d72 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -6,6 +6,7 @@ import DhCreature from './creature.mjs'; import { attributeField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs'; import { ActionField } from '../fields/actionField.mjs'; import DHCharacterSettings from '../../applications/sheets-configs/character-settings.mjs'; +import { orderSourcesForArmorAutoChange } from '../../helpers/utils.mjs'; export default class DhCharacter extends DhCreature { /**@override */ @@ -469,43 +470,60 @@ export default class DhCharacter extends DhCreature { const increasing = armorChange >= 0; let remainingChange = Math.abs(armorChange); - const armorEffects = Array.from(this.parent.allApplicableEffects()).filter(x => x.system.armorData); - const orderedEffects = game.system.api.data.activeEffects.changeTypes.armor.orderEffectsForAutoChange( - armorEffects, - increasing - ); + const armorSources = Array.from(this.parent.allApplicableEffects()).filter(x => x.system.armorData); + if (this.armor) armorSources.push(this.armor); - const embeddedUpdates = []; - for (const armorEffect of orderedEffects) { + const orderedSources = orderSourcesForArmorAutoChange(armorSources, increasing); + + const handleArmorData = (embeddedUpdates, doc, armorData) => { let usedArmorChange = 0; if (clear) { - usedArmorChange -= armorEffect.system.armorChange.value.current; + usedArmorChange -= armorData.current; } else { if (increasing) { - const remainingArmor = armorEffect.system.armorData.max - armorEffect.system.armorData.current; + const remainingArmor = armorData.max - armorData.current; usedArmorChange = Math.min(remainingChange, remainingArmor); remainingChange -= usedArmorChange; } else { - const changeChange = Math.min(armorEffect.system.armorData.current, remainingChange); + const changeChange = Math.min(armorData.current, remainingChange); usedArmorChange -= changeChange; remainingChange -= changeChange; } } - if (!usedArmorChange) continue; + if (!usedArmorChange) return usedArmorChange; else { - if (!embeddedUpdates[armorEffect.parent.id]) - embeddedUpdates[armorEffect.parent.id] = { doc: armorEffect.parent, updates: [] }; + if (!embeddedUpdates[doc.id]) embeddedUpdates[doc.id] = { doc: doc, updates: [] }; - embeddedUpdates[armorEffect.parent.id].updates.push({ - '_id': armorEffect.id, - 'system.changes': armorEffect.system.changes.map(change => ({ + return usedArmorChange; + } + }; + + const armorUpdates = []; + const effectUpdates = []; + for (const armorSource of orderedSources) { + const usedArmorChange = handleArmorData( + armorSource.type === 'armor' ? armorUpdates : effectUpdates, + armorSource.parent, + armorSource.type === 'armor' ? armorSource.system.armor : armorSource.system.armorData + ); + if (!usedArmorChange) continue; + + if (armorSource.type === 'armor') { + armorUpdates[armorSource.parent.id].updates.push({ + '_id': armorSource.id, + 'system.armor.current': armorSource.system.armor.current + usedArmorChange + }); + } else { + effectUpdates[armorSource.parent.id].updates.push({ + '_id': armorSource.id, + 'system.changes': armorSource.system.changes.map(change => ({ ...change, value: change.type === 'armor' ? { ...change.value, - current: armorEffect.system.armorChange.value.current + usedArmorChange + current: armorSource.system.armorChange.value.current + usedArmorChange } : change.value })) @@ -515,22 +533,34 @@ export default class DhCharacter extends DhCreature { if (remainingChange === 0 && !clear) break; } - const updateValues = Object.values(embeddedUpdates); - for (const [index, { doc, updates }] of updateValues.entries()) - await doc.updateEmbeddedDocuments('ActiveEffect', updates, { render: index === updateValues.length - 1 }); + const armorUpdateValues = Object.values(armorUpdates); + for (const [index, { doc, updates }] of armorUpdateValues.entries()) + await doc.updateEmbeddedDocuments('Item', updates, { render: index === armorUpdateValues.length - 1 }); + + const effectUpdateValues = Object.values(effectUpdates); + for (const [index, { doc, updates }] of effectUpdateValues.entries()) + await doc.updateEmbeddedDocuments('ActiveEffect', updates, { + render: index === effectUpdateValues.length - 1 + }); } async updateArmorEffectValue({ uuid, value }) { - const effect = await foundry.utils.fromUuid(uuid); - const effectValue = effect.system.armorChange.value; - await effect.update({ - 'system.changes': [ - { - ...effect.system.armorChange, - value: { ...effectValue, current: effectValue.current + value } - } - ] - }); + const source = await foundry.utils.fromUuid(uuid); + if (source.type === 'armor') { + await source.update({ + 'system.armor.current': source.system.armor.current + value + }); + } else { + const effectValue = source.system.armorChange.value; + await source.update({ + 'system.changes': [ + { + ...source.system.armorChange, + value: { ...effectValue, current: effectValue.current + value } + } + ] + }); + } } get sheetLists() { @@ -657,8 +687,8 @@ export default class DhCharacter extends DhCreature { prepareBaseData() { super.prepareBaseData(); this.armorScore = { - max: 0, - value: 0 + max: this.armor?.system.armor.max ?? 0, + value: this.armor?.system.armor.current ?? 0 }; this.evasion += this.class.value?.system?.evasion ?? 0; diff --git a/module/data/item/armor.mjs b/module/data/item/armor.mjs index 18d20e2e..760f9c22 100644 --- a/module/data/item/armor.mjs +++ b/module/data/item/armor.mjs @@ -19,6 +19,14 @@ 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 }), + armor: new fields.SchemaField({ + current: new fields.NumberField({ integer: true, min: 0, initial: 0 }), + max: new fields.NumberField({ required: true, integer: true, initial: 0 }) + }), + baseThresholds: new fields.SchemaField({ + major: new fields.NumberField({ integer: true, initial: 0 }), + severe: new fields.NumberField({ integer: true, initial: 0 }) + }), armorFeatures: new fields.ArrayField( new fields.SchemaField({ value: new fields.StringField({ @@ -27,11 +35,7 @@ export default class DHArmor extends AttachableItem { effectIds: new fields.ArrayField(new fields.StringField({ required: true })), actionIds: new fields.ArrayField(new fields.StringField({ required: true })) }) - ), - baseThresholds: new fields.SchemaField({ - major: new fields.NumberField({ integer: true, initial: 0 }), - severe: new fields.NumberField({ integer: true, initial: 0 }) - }) + ) }; } @@ -48,17 +52,6 @@ export default class DHArmor extends AttachableItem { ); } - get armorEffect() { - return this.parent.effects.find(x => x.system.armorData); - } - - 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,17 +66,6 @@ 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.system.armorData)) { - this.parent.createEmbeddedDocuments('ActiveEffect', [ - game.system.api.data.activeEffects.changeTypes.armor.getDefaultArmorEffect() - ]); - } - } - /**@inheritdoc */ async _preUpdate(changes, options, user) { const allowed = await super._preUpdate(changes, options, user); @@ -184,7 +166,7 @@ export default class DHArmor extends AttachableItem { */ _getTags() { const tags = [ - `${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.armorData.max}`, + `${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.armor.max}`, `${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseThresholds.base')}: ${this.baseThresholds.major} / ${this.baseThresholds.severe}` ]; @@ -196,7 +178,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 = [`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.armorData.max}`]; + const labels = [`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.armor.max}`]; return labels; } diff --git a/module/data/item/base.mjs b/module/data/item/base.mjs index 25986afe..21a11149 100644 --- a/module/data/item/base.mjs +++ b/module/data/item/base.mjs @@ -220,6 +220,19 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel { addLinkedItemsDiff(changed.system?.features, this.features, options); + const autoSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation); + const armorChanged = + changed.system?.armor?.current !== undefined && changed.system.armor.current !== this.armor.current; + if (armorChanged && autoSettings.resourceScrollTexts && this.parent.parent?.type === 'character') { + const armorChangeValue = changed.system.armor.current - this.armor.current; + const armorData = getScrollTextData( + this.parent.parent, + { value: armorChangeValue + this.parent.parent.system.armorScore.value }, + 'armor' + ); + options.scrollingTextData = [armorData]; + } + if (changed.system?.actions) { const triggersToRemove = Object.keys(changed.system.actions).reduce((acc, key) => { const action = changed.system.actions[key]; diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index 59ebbbb3..e9bd09a9 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -743,3 +743,40 @@ export function getUnusedDamageTypes(parts) { return acc; }, []); } + +/** + * + * @param {{ type: string, parent: Object, disabled: boolean, isSuppressed: boolean }} sources + * @param { boolean } increasing + * @returns + */ +export function orderSourcesForArmorAutoChange(sources, increasing) { + const getSourceWeight = source => { + switch (source.type) { + case 'class': + case 'subclass': + case 'ancestry': + case 'community': + case 'feature': + case 'domainCard': + return 2; + case 'armor': + return 3; + case 'loot': + case 'consumable': + return 4; + case 'weapon': + return 5; + case 'character': + return 6; + default: + return 1; + } + }; + + return sources + .filter(x => !x.disabled && !x.isSuppressed) + .sort((a, b) => + increasing ? getSourceWeight(b) - getSourceWeight(a) : getSourceWeight(a) - getSourceWeight(b) + ); +} diff --git a/templates/sheets/items/armor/header.hbs b/templates/sheets/items/armor/header.hbs index 6d3bc0fb..d8044332 100644 --- a/templates/sheets/items/armor/header.hbs +++ b/templates/sheets/items/armor/header.hbs @@ -9,7 +9,7 @@

{{localize "DAGGERHEART.ITEMS.Armor.baseScore"}}: - {{source.system.armorData.max}} + {{source.system.armor.max}} - {{localize "DAGGERHEART.ITEMS.Armor.baseThresholds.base"}}: {{source.system.baseThresholds.major}} diff --git a/templates/sheets/items/armor/settings.hbs b/templates/sheets/items/armor/settings.hbs index 11cd2b8b..51bf1746 100644 --- a/templates/sheets/items/armor/settings.hbs +++ b/templates/sheets/items/armor/settings.hbs @@ -6,13 +6,9 @@
{{localize tabs.settings.label}} {{localize "DAGGERHEART.GENERAL.Tiers.singular"}} - {{formField systemFields.tier value=source.system.tier}} - {{#if this.armorScore includeZero=true}} - {{localize "DAGGERHEART.ITEMS.Armor.baseScore"}} -
- -
- {{/if}} + {{ formField systemFields.tier value=source.system.tier }} + {{localize "DAGGERHEART.ITEMS.Armor.baseScore"}} + {{ formField systemFields.armor.fields.max value=source.system.armor.max }} {{localize "TYPES.Item.feature"}} diff --git a/templates/ui/tooltip/armorManagement.hbs b/templates/ui/tooltip/armorManagement.hbs index 55d702b1..251840c9 100644 --- a/templates/ui/tooltip/armorManagement.hbs +++ b/templates/ui/tooltip/armorManagement.hbs @@ -1,37 +1,19 @@

{{localize "DAGGERHEART.GENERAL.armorSlots"}}

{{#each sources as |source|}} - {{#if ../useResourcePips}} -
-

{{source.name}}

- +
+

{{source.name}}

+ - {{else}} -
-

{{source.name}}

-
-
- - / - {{source.max}} -
- -
-
- {{/if}} +
{{/each}}
\ No newline at end of file