From 8f0b1f6ed31dafa921e9a4b285d7e1b057f536ec Mon Sep 17 00:00:00 2001 From: WBHarry Date: Fri, 13 Feb 2026 23:01:55 +0100 Subject: [PATCH] Fixed DamageReductionDialog --- lang/en.json | 2 +- .../dialogs/damageReductionDialog.mjs | 134 +++++++++++++----- module/data/actor/character.mjs | 12 ++ module/documents/actor.mjs | 11 +- .../damage-reduction-container.less | 26 +++- templates/dialogs/damageReduction.hbs | 56 ++++---- 6 files changed, 172 insertions(+), 69 deletions(-) diff --git a/lang/en.json b/lang/en.json index 54c70ae9..59b058ab 100755 --- a/lang/en.json +++ b/lang/en.json @@ -437,7 +437,7 @@ "text": "Are you sure you want to delete {name}?" }, "DamageReduction": { - "armorMarks": "Armor Marks", + "maxUseableArmor": "Useable Armor Slots", "armorWithStress": "Spend 1 stress to use an extra mark", "thresholdImmunities": "Threshold Immunities", "stress": "Stress", diff --git a/module/applications/dialogs/damageReductionDialog.mjs b/module/applications/dialogs/damageReductionDialog.mjs index d4a2b4d3..31e4f72d 100644 --- a/module/applications/dialogs/damageReductionDialog.mjs +++ b/module/applications/dialogs/damageReductionDialog.mjs @@ -10,7 +10,8 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap this.reject = reject; this.actor = actor; this.damage = damage; - this.damageType = damageType; + // this.damageType = damageType; + this.damageType = ['physical']; this.rulesDefault = game.settings.get( CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation @@ -20,14 +21,25 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap this.rulesDefault ); - const canApplyArmor = damageType.every(t => actor.system.armorApplicableDamageTypes[t] === true); - const availableArmor = actor.system.armorScore.max - actor.system.armorScore.value; - const maxArmorMarks = canApplyArmor ? availableArmor : 0; + const allArmorEffects = Array.from(actor.allApplicableEffects()).filter(x => x.type === 'armor'); + const orderedArmorEffects = game.system.api.data.activeEffects.ArmorEffect.orderEffectsForAutoChange( + allArmorEffects, + true + ); + const armor = orderedArmorEffects.reduce((acc, effect) => { + if (effect.type !== 'armor') return acc; + const { value, max } = effect.system.armorData; + acc.push({ + effect: effect, + marks: [...Array(max).keys()].reduce((acc, _, index) => { + const spent = index < value; + acc[foundry.utils.randomID()] = { selected: false, disabled: spent, spent }; + return acc; + }, {}) + }); - const armor = [...Array(maxArmorMarks).keys()].reduce((acc, _) => { - acc[foundry.utils.randomID()] = { selected: false }; return acc; - }, {}); + }, []); const stress = [...Array(actor.system.rules.damageReduction.maxArmorMarked.stressExtra ?? 0).keys()].reduce( (acc, _) => { acc[foundry.utils.randomID()] = { selected: false }; @@ -121,13 +133,11 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap context.thresholdImmunities = Object.keys(this.thresholdImmunities).length > 0 ? this.thresholdImmunities : null; - const { selectedArmorMarks, selectedStressMarks, stressReductions, currentMarks, currentDamage } = + const { selectedStressMarks, stressReductions, currentMarks, currentDamage, maxArmorUsed, availableArmor } = this.getDamageInfo(); context.armorScore = this.actor.system.armorScore.max; context.armorMarks = currentMarks; - context.basicMarksUsed = - selectedArmorMarks.length === this.actor.system.rules.damageReduction.maxArmorMarked.value; const stressReductionStress = this.availableStressReductions ? stressReductions.reduce((acc, red) => acc + red.cost, 0) @@ -141,16 +151,27 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap } : null; - const maxArmor = this.actor.system.rules.damageReduction.maxArmorMarked.value; - context.marks = { - armor: Object.keys(this.marks.armor).reduce((acc, key, index) => { - const mark = this.marks.armor[key]; - if (!this.rulesOn || index + 1 <= maxArmor) acc[key] = mark; + context.maxArmorUsed = maxArmorUsed; + context.availableArmor = availableArmor; + context.basicMarksUsed = availableArmor === 0 || selectedStressMarks.length; - return acc; - }, {}), + const armorSources = []; + for (const source of this.marks.armor) { + const parent = source.effect.origin + ? await foundry.utils.fromUuid(source.effect.origin) + : source.effect.parent; + armorSources.push({ + label: parent.name, + uuid: source.effect.uuid, + marks: source.marks + }); + } + context.marks = { + armor: armorSources, stress: this.marks.stress }; + + context.usesStressArmor = Object.keys(context.marks.stress).length; context.availableStressReductions = this.availableStressReductions; context.damage = getDamageLabel(this.damage); @@ -167,27 +188,31 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap } getDamageInfo = () => { - const selectedArmorMarks = Object.values(this.marks.armor).filter(x => x.selected); + const selectedArmorMarks = this.marks.armor.flatMap(x => Object.values(x.marks).filter(x => x.selected)); const selectedStressMarks = Object.values(this.marks.stress).filter(x => x.selected); const stressReductions = this.availableStressReductions ? Object.values(this.availableStressReductions).filter(red => red.selected) : []; - const currentMarks = - this.actor.system.armorScore.value + selectedArmorMarks.length + selectedStressMarks.length; + const currentMarks = this.actor.system.armorScore.value + selectedArmorMarks.length; + + const maxArmorUsed = this.actor.system.rules.damageReduction.maxArmorMarked.value + selectedStressMarks.length; + const availableArmor = + maxArmorUsed - + this.marks.armor.reduce((acc, source) => { + acc += Object.values(source.marks).filter(x => x.selected).length; + return acc; + }, 0); const armorMarkReduction = selectedArmorMarks.length * this.actor.system.rules.damageReduction.increasePerArmorMark; - let currentDamage = Math.max( - this.damage - armorMarkReduction - selectedStressMarks.length - stressReductions.length, - 0 - ); + let currentDamage = Math.max(this.damage - armorMarkReduction - stressReductions.length, 0); if (this.reduceSeverity) { currentDamage = Math.max(currentDamage - this.reduceSeverity, 0); } if (this.thresholdImmunities[currentDamage]) currentDamage = 0; - return { selectedArmorMarks, selectedStressMarks, stressReductions, currentMarks, currentDamage }; + return { selectedStressMarks, stressReductions, currentMarks, currentDamage, maxArmorUsed, availableArmor }; }; static toggleRules() { @@ -209,8 +234,8 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap } static setMarks(_, target) { - const currentMark = this.marks[target.dataset.type][target.dataset.key]; - const { selectedStressMarks, stressReductions, currentMarks, currentDamage } = this.getDamageInfo(); + const currentMark = foundry.utils.getProperty(this.marks, target.dataset.path); + const { selectedStressMarks, stressReductions, currentDamage, availableArmor } = this.getDamageInfo(); if (!currentMark.selected && currentDamage === 0) { ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.damageAlreadyNone')); @@ -218,12 +243,18 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap } if (this.rulesOn) { - if (!currentMark.selected && currentMarks === this.actor.system.armorScore.max) { + if (target.dataset.type === 'armor' && !currentMark.selected && !availableArmor) { ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noAvailableArmorMarks')); return; } } + const stressUsed = selectedStressMarks.length; + if (target.dataset.type === 'armor' && stressUsed) { + const updateResult = this.updateStressArmor(target.dataset.id, !currentMark.selected); + if (updateResult === false) return; + } + if (currentMark.selected) { const currentDamageLabel = getDamageLabel(currentDamage); for (let reduction of stressReductions) { @@ -232,8 +263,16 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap } } - if (target.dataset.type === 'armor' && selectedStressMarks.length > 0) { - selectedStressMarks.forEach(mark => (mark.selected = false)); + if (target.dataset.type === 'stress' && currentMark.armorMarkId) { + for (const source of this.marks.armor) { + const match = Object.keys(source.marks).find(key => key === currentMark.armorMarkId); + if (match) { + source.marks[match].selected = false; + break; + } + } + + currentMark.armorMarkId = null; } } @@ -241,6 +280,25 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap this.render(); } + updateStressArmor(armorMarkId, select) { + let stressMarkKey = null; + if (select) { + stressMarkKey = Object.keys(this.marks.stress).find( + key => this.marks.stress[key].selected && !this.marks.stress[key].armorMarkId + ); + } else { + stressMarkKey = Object.keys(this.marks.stress).find( + key => this.marks.stress[key].armorMarkId === armorMarkId + ); + if (!stressMarkKey) + stressMarkKey = Object.keys(this.marks.stress).find(key => this.marks.stress[key].selected); + } + + if (!stressMarkKey) return false; + + this.marks.stress[stressMarkKey].armorMarkId = select ? armorMarkId : null; + } + static useStressReduction(_, target) { const damageValue = Number(target.dataset.reduction); const stressReduction = this.availableStressReductions[damageValue]; @@ -279,11 +337,19 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap } static async takeDamage() { - const { selectedArmorMarks, selectedStressMarks, stressReductions, currentDamage } = this.getDamageInfo(); - const armorSpent = selectedArmorMarks.length + selectedStressMarks.length; - const stressSpent = selectedStressMarks.length + stressReductions.reduce((acc, red) => acc + red.cost, 0); + const { selectedStressMarks, stressReductions, currentDamage } = this.getDamageInfo(); + const armorChanges = this.marks.armor.reduce((acc, source) => { + const amount = Object.values(source.marks).filter(x => x.selected).length; + if (!amount) return acc; - this.resolve({ modifiedDamage: currentDamage, armorSpent, stressSpent }); + acc.push({ uuid: source.effect.uuid, amount }); + return acc; + }, []); + const stressSpent = + selectedStressMarks.filter(x => x.armorMarkId).length + + stressReductions.reduce((acc, red) => acc + red.cost, 0); + + this.resolve({ modifiedDamage: currentDamage, armorChanges, stressSpent }); await this.close(true); } diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 87f203ec..1ede6554 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -559,6 +559,18 @@ export default class DhCharacter extends BaseDataActor { doc.updateEmbeddedDocuments('ActiveEffect', updates, { render: index === updateValues.length - 1 }); } + async updateArmorEffectValue({ uuid, value }) { + const effect = await foundry.utils.fromUuid(uuid); + await effect.update({ + 'system.changes': [ + { + ...effect.system.armorChange, + value: effect.system.armorChange.value + value + } + ] + }); + } + get sheetLists() { const ancestryFeatures = [], communityFeatures = [], diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 64e877c9..2bc0179a 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -626,12 +626,10 @@ export default class DhpActor extends Actor { } ); if (armorSlotResult) { - const { modifiedDamage, armorSpent, stressSpent } = armorSlotResult; + const { modifiedDamage, armorChanges, stressSpent } = armorSlotResult; updates.find(u => u.key === 'hitPoints').value = modifiedDamage; - if (armorSpent) { - const armorUpdate = updates.find(u => u.key === 'armor'); - if (armorUpdate) armorUpdate.value += armorSpent; - else updates.push({ value: armorSpent, key: 'armor' }); + for (const armorChange of armorChanges) { + updates.push({ value: armorChange.amount, key: 'armor', uuid: armorChange.uuid }); } if (stressSpent) { const stressUpdate = updates.find(u => u.key === 'stress'); @@ -778,7 +776,8 @@ export default class DhpActor extends Actor { ); break; case 'armor': - this.system.updateArmorValue(r); + if (!r.uuid) this.system.updateArmorValue(r); + else this.system.updateArmorEffectValue(r); break; default: if (this.system.resources?.[r.key]) { diff --git a/styles/less/dialog/damage-reduction/damage-reduction-container.less b/styles/less/dialog/damage-reduction/damage-reduction-container.less index 2f343fb3..e8242bdd 100644 --- a/styles/less/dialog/damage-reduction/damage-reduction-container.less +++ b/styles/less/dialog/damage-reduction/damage-reduction-container.less @@ -35,7 +35,10 @@ display: flex; flex-direction: column; align-items: center; - width: 100%; + + &.full-width { + width: 100%; + } } .padded { @@ -45,6 +48,7 @@ .armor-title { margin: 0; white-space: nowrap; + width: 100%; } .resources-container { @@ -62,12 +66,17 @@ .mark-selection { display: flex; - align-items: center; + flex-direction: column; width: 100%; margin: 0; + h4 { + margin: 0; + } + .mark-selection-inner { display: flex; + justify-content: center; gap: 8px; .mark-container { @@ -91,6 +100,19 @@ opacity: 0.2; } + &.spent { + ::after { + position: absolute; + content: '/'; + color: red; + font-weight: 700; + font-size: 1.8em; + left: -1px; + top: -7px; + rotate: 13deg; + } + } + .fa-shield { position: relative; right: 0.5px; diff --git a/templates/dialogs/damageReduction.hbs b/templates/dialogs/damageReduction.hbs index 57d7ee61..50fe3422 100644 --- a/templates/dialogs/damageReduction.hbs +++ b/templates/dialogs/damageReduction.hbs @@ -7,53 +7,57 @@
-

{{localize "DAGGERHEART.APPLICATIONS.DamageReduction.armorMarks"}}

-
{{armorMarks}}/{{armorScore}}
+

{{localize "DAGGERHEART.APPLICATIONS.DamageReduction.maxUseableArmor"}}

+
{{availableArmor}}
- {{#if this.stress}} -
-

{{localize "DAGGERHEART.APPLICATIONS.DamageReduction.stress"}}

-
{{this.stress.value}}/{{this.stress.max}}
-
- {{/if}}
-
-

-
- {{#each marks.armor}} -
- -
- {{/each}} +
+ {{#each marks.armor as |source|}} +
+

{{source.label}}

+
+ {{#each source.marks}} + + + + {{/each}} +
+
+ {{/each}} + {{#if usesStressArmor}} +
+

{{localize "Stress"}}

+
{{#each marks.stress}}
{{/each}} +
-

-
{{localize "DAGGERHEART.APPLICATIONS.DamageReduction.usedMarks"}}
+ {{/if}}
{{#if availableStressReductions}}
-

{{localize "DAGGERHEART.APPLICATIONS.DamageReduction.stressReduction"}}

+

{{localize "DAGGERHEART.APPLICATIONS.DamageReduction.stressReduction"}}

{{/if}} {{#each availableStressReductions}}
-

+

{{#if this.any}} {{localize "DAGGERHEART.GENERAL.any"}} @@ -74,7 +78,7 @@ {{#if reduceSeverity}}
-

{{localize "DAGGERHEART.APPLICATIONS.DamageReduction.reduceSeverity" nr=reduceSeverity}}

+

{{localize "DAGGERHEART.APPLICATIONS.DamageReduction.reduceSeverity" nr=reduceSeverity}}

{{/if}} @@ -82,7 +86,7 @@ {{#if thresholdImmunities}}
-

{{localize "DAGGERHEART.APPLICATIONS.DamageReduction.thresholdImmunities"}}

+

{{localize "DAGGERHEART.APPLICATIONS.DamageReduction.thresholdImmunities"}}

{{/if}}