diff --git a/lang/en.json b/lang/en.json index 6c658919..857915de 100755 --- a/lang/en.json +++ b/lang/en.json @@ -1096,9 +1096,11 @@ "UsedMarks": "Used Marks", "Stress": "Stress", "ArmorWithStress": "Spend 1 stress to use an extra mark", + "UnncessaryStress": "You don't need to expend stress", "StressReduction": "Reduce By Stress", "Notifications": { "DamageAlreadyNone": "The damage has already been reduced to none", + "NoAvailableArmorMarks": "You have no more available armor marks", "DamageIgnore": "{character} did not take damage" } }, diff --git a/module/applications/damageReductionDialog.mjs b/module/applications/damageReductionDialog.mjs index 923d4df5..d4eb5d7c 100644 --- a/module/applications/damageReductionDialog.mjs +++ b/module/applications/damageReductionDialog.mjs @@ -11,15 +11,20 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap this.actor = actor; this.damage = damage; - const maxUseable = actor.system.armorScore - actor.system.armor.system.marks.value; - this.availableArmorMarks = { - max: Math.min( - maxUseable, - actor.system.rules.maxArmorMarked.total + (actor.system.rules.maxArmorMarked.stressExtra ?? 0) - ), - stressIndex: actor.system.rules.maxArmorMarked.total, - selected: 0 - }; + const maxArmorMarks = Math.min( + actor.system.armorScore - actor.system.armor.system.marks.value, + actor.system.rules.maxArmorMarked.total + ); + + const armor = [...Array(maxArmorMarks).keys()].reduce((acc, _) => { + acc[foundry.utils.randomID()] = { selected: false }; + return acc; + }, {}); + const stress = [...Array(actor.system.rules.maxArmorMarked.stressExtra ?? 0).keys()].reduce((acc, _) => { + acc[foundry.utils.randomID()] = { selected: false }; + return acc; + }, {}); + this.marks = { armor, stress }; this.availableStressReductions = Object.keys(actor.system.rules.stressDamageReduction).reduce((acc, key) => { const dr = actor.system.rules.stressDamageReduction[key]; @@ -79,32 +84,31 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap async _prepareContext(_options) { const context = await super._prepareContext(_options); - context.armorScore = this.actor.system.armorScore; - context.armorMarks = this.actor.system.armor.system.marks.value + this.availableArmorMarks.selected; - const selectedStressReductions = Object.values(this.availableStressReductions).filter(red => red.selected); + const { selectedArmorMarks, selectedStressMarks, stressReductions, currentMarks, currentDamage } = + this.getDamageInfo(); + + context.armorScore = this.actor.system.armorScore; + context.armorMarks = currentMarks; + context.basicMarksUsed = selectedArmorMarks.length === this.actor.system.rules.maxArmorMarked.total; + const stressReductionStress = this.availableStressReductions - ? selectedStressReductions.reduce((acc, red) => acc + red.cost, 0) + ? stressReductions.reduce((acc, red) => acc + red.cost, 0) : 0; context.stress = - this.availableArmorMarks.stressIndex || this.availableStressReductions + selectedStressMarks.length > 0 || this.availableStressReductions ? { value: - this.actor.system.resources.stress.value + - (Math.max(this.availableArmorMarks.selected - this.availableArmorMarks.stressIndex, 0) + - stressReductionStress), + this.actor.system.resources.stress.value + selectedStressMarks.length + stressReductionStress, maxTotal: this.actor.system.resources.stress.maxTotal } : null; - context.availableArmorMarks = this.availableArmorMarks; + context.marks = this.marks; context.availableStressReductions = this.availableStressReductions; context.damage = getDamageLabel(this.damage); - context.reducedDamage = - this.availableArmorMarks.selected > 0 || selectedStressReductions.length > 0 - ? getDamageLabel(this.damage - this.availableArmorMarks.selected - selectedStressReductions.length) - : null; + context.reducedDamage = currentDamage !== this.damage ? getDamageLabel(currentDamage) : null; context.currentDamage = context.reducedDamage ?? context.damage; return context; @@ -115,47 +119,64 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap this.render(true); } + getDamageInfo = () => { + const selectedArmorMarks = Object.values(this.marks.armor).filter(x => x.selected); + const selectedStressMarks = Object.values(this.marks.stress).filter(x => x.selected); + const stressReductions = Object.values(this.availableStressReductions).filter(red => red.selected); + const currentMarks = + this.actor.system.armor.system.marks.value + selectedArmorMarks.length + selectedStressMarks.length; + + const currentDamage = + this.damage - selectedArmorMarks.length - selectedStressMarks.length - stressReductions.length; + + return { selectedArmorMarks, selectedStressMarks, stressReductions, currentMarks, currentDamage }; + }; + static setMarks(_, target) { - const index = Number(target.dataset.index); - const isDecreasing = index < this.availableArmorMarks.selected; - if (!isDecreasing && this.damage - this.availableArmorMarks.selected === 0) { + const currentMark = this.marks[target.dataset.type][target.dataset.key]; + const { selectedStressMarks, stressReductions, currentMarks, currentDamage } = this.getDamageInfo(); + if (!currentMark.selected && currentDamage === 0) { ui.notifications.info(game.i18n.localize('DAGGERHEART.DamageReduction.Notifications.DamageAlreadyNone')); return; } - if (isDecreasing) { - const selectedStressReductions = Object.values(this.availableStressReductions).filter(red => red.selected); - const reducedDamage = - this.availableArmorMarks.selected > 0 || selectedStressReductions.length > 0 - ? getDamageLabel(this.damage - this.availableArmorMarks.selected - selectedStressReductions.length) - : null; - const currentDamage = reducedDamage ?? getDamageLabel(this.damage); - for (let reduction of selectedStressReductions) { - if (reduction.selected && reduction.to === currentDamage) { + if (!currentMark.selected && currentMarks === this.actor.system.armorScore) { + ui.notifications.info( + game.i18n.localize('DAGGERHEART.DamageReduction.Notifications.NoAvailableArmorMarks') + ); + return; + } + + if (currentMark.selected) { + const currentDamageLabel = getDamageLabel(currentDamage); + for (let reduction of stressReductions) { + if (reduction.selected && reduction.to === currentDamageLabel) { reduction.selected = false; } } + + if (target.dataset.type === 'armor' && selectedStressMarks.length > 0) { + selectedStressMarks.forEach(mark => (mark.selected = false)); + } } - this.availableArmorMarks.selected = isDecreasing ? index : index + 1; + currentMark.selected = !currentMark.selected; this.render(); } static useStressReduction(_, target) { const damageValue = Number(target.dataset.reduction); const stressReduction = this.availableStressReductions[damageValue]; + const { currentDamage } = this.getDamageInfo(); + if (stressReduction.selected) { stressReduction.selected = false; this.render(); } else { - const selectedStressReductions = Object.values(this.availableStressReductions).filter(red => red.selected); - const reducedDamage = - this.availableArmorMarks.selected > 0 || selectedStressReductions.length > 0 - ? getDamageLabel(this.damage - this.availableArmorMarks.selected - selectedStressReductions.length) - : null; - const currentDamage = reducedDamage ?? getDamageLabel(this.damage); + const reducedDamage = currentDamage !== this.damage ? getDamageLabel(currentDamage) : null; + const currentDamageLabel = reducedDamage ?? getDamageLabel(this.damage); - if (stressReduction.from !== currentDamage) return; + if (stressReduction.from !== currentDamageLabel) return; stressReduction.selected = true; this.render(); @@ -163,10 +184,11 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap } static async takeDamage() { - const armorSpent = this.availableArmorMarks.selected; - const modifiedDamage = this.damage - armorSpent; + 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); - this.resolve({ modifiedDamage, armorSpent }); + this.resolve({ modifiedDamage: currentDamage, armorSpent, stressSpent }); await this.close(true); } diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index c95e0bb0..e1120af7 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -516,10 +516,11 @@ export default class DhpActor extends Actor { new Promise((resolve, reject) => { new DamageReductionDialog(resolve, reject, this, hpDamage).render(true); }) - .then(async ({ modifiedDamage, armorSpent }) => { + .then(async ({ modifiedDamage, armorSpent, stressSpent }) => { const resources = [ { value: modifiedDamage, type: 'hitPoints' }, - ...(armorSpent ? [{ value: armorSpent, type: 'armorStack' }] : []) + ...(armorSpent ? [{ value: armorSpent, type: 'armorStack' }] : []), + ...(stressSpent ? [{ value: stressSpent, type: 'stress' }] : []) ]; await this.modifyResource(resources); }) diff --git a/styles/daggerheart.css b/styles/daggerheart.css index 52520076..2fb08edf 100755 --- a/styles/daggerheart.css +++ b/styles/daggerheart.css @@ -3151,11 +3151,17 @@ div.daggerheart.views.multiclass { .daggerheart.views.damage-reduction .damage-reduction-container .mark-selection { display: flex; align-items: center; - gap: 4px; width: 100%; margin: 0; } -.daggerheart.views.damage-reduction .damage-reduction-container .mark-selection .mark-container { +.daggerheart.views.damage-reduction .damage-reduction-container .mark-selection .mark-selection-inner { + display: flex; + gap: 2px; +} +.daggerheart.views.damage-reduction .damage-reduction-container .mark-selection .mark-selection-inner:not(:last-child) { + margin-right: 8px; +} +.daggerheart.views.damage-reduction .damage-reduction-container .mark-selection .mark-selection-inner .mark-container { cursor: pointer; border: 1px solid light-dark(#18162e, #f3c267); border-radius: 6px; @@ -3167,10 +3173,14 @@ div.daggerheart.views.multiclass { justify-content: center; opacity: 0.4; } -.daggerheart.views.damage-reduction .damage-reduction-container .mark-selection .mark-container.selected { +.daggerheart.views.damage-reduction .damage-reduction-container .mark-selection .mark-selection-inner .mark-container.selected { opacity: 1; } -.daggerheart.views.damage-reduction .damage-reduction-container .mark-selection .mark-container .fa-shield { +.daggerheart.views.damage-reduction .damage-reduction-container .mark-selection .mark-selection-inner .mark-container.inactive { + cursor: initial; + opacity: 0.2; +} +.daggerheart.views.damage-reduction .damage-reduction-container .mark-selection .mark-selection-inner .mark-container .fa-shield { position: relative; right: 0.5px; } diff --git a/styles/damageReduction.less b/styles/damageReduction.less index 3b3ff0e2..e3ffc2e9 100644 --- a/styles/damageReduction.less +++ b/styles/damageReduction.less @@ -41,29 +41,42 @@ .mark-selection { display: flex; align-items: center; - gap: 4px; width: 100%; margin: 0; - .mark-container { - cursor: pointer; - border: 1px solid light-dark(@dark-blue, @golden); - border-radius: 6px; - height: 26px; - padding: 0 1px; - font-size: 18px; + .mark-selection-inner { display: flex; - align-items: center; - justify-content: center; - opacity: 0.4; + gap: 2px; - &.selected { - opacity: 1; + &:not(:last-child) { + margin-right: 8px; } - .fa-shield { - position: relative; - right: 0.5px; + .mark-container { + cursor: pointer; + border: 1px solid light-dark(@dark-blue, @golden); + border-radius: 6px; + height: 26px; + padding: 0 1px; + font-size: 18px; + display: flex; + align-items: center; + justify-content: center; + opacity: 0.4; + + &.selected { + opacity: 1; + } + + &.inactive { + cursor: initial; + opacity: 0.2; + } + + .fa-shield { + position: relative; + right: 0.5px; + } } } } diff --git a/templates/views/damageReduction.hbs b/templates/views/damageReduction.hbs index 2749192c..33bf53aa 100644 --- a/templates/views/damageReduction.hbs +++ b/templates/views/damageReduction.hbs @@ -16,19 +16,26 @@

- {{#times availableArmorMarks.max}} -
- {{#if (or (not @root.availableArmorMarks.stressIndex) (lt this @root.availableArmorMarks.stressIndex))}} - - {{else}} +
+ {{#each marks.armor}} +
+ +
+ {{/each}} +
+
+ {{#each marks.stress}} +
- {{/if}} - -
- {{/times}} +
+ {{/each}} +

{{localize "DAGGERHEART.DamageReduction.UsedMarks"}}