From 92feddce1bac1fa3f8cc2f2eb242a6dc1d1a89a1 Mon Sep 17 00:00:00 2001 From: Dapoolp Date: Sat, 19 Jul 2025 12:52:14 +0200 Subject: [PATCH] ChatMessage & takeDamage updates --- .../dialogs/damageReductionDialog.mjs | 6 +- module/applications/ui/chatLog.mjs | 23 +++++-- module/data/action/baseAction.mjs | 3 + module/data/actor/character.mjs | 5 +- module/dice/d20Roll.mjs | 19 ++---- module/dice/damageRoll.mjs | 47 +++++++++++++- module/dice/dhRoll.mjs | 9 +-- module/dice/dualityRoll.mjs | 14 ++-- module/documents/actor.mjs | 64 +++++++++++-------- module/documents/token.mjs | 2 +- styles/less/ui/chat/chat.less | 5 ++ .../dialogs/dice-roll/damageSelection.hbs | 2 +- templates/ui/chat/parts/damage-chat.hbs | 49 +++++++------- 13 files changed, 158 insertions(+), 90 deletions(-) diff --git a/module/applications/dialogs/damageReductionDialog.mjs b/module/applications/dialogs/damageReductionDialog.mjs index 984106d7..d5d73e28 100644 --- a/module/applications/dialogs/damageReductionDialog.mjs +++ b/module/applications/dialogs/damageReductionDialog.mjs @@ -10,12 +10,12 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap this.reject = reject; this.actor = actor; this.damage = damage; - +console.log(damageType) const canApplyArmor = damageType.every(t => actor.system.armorApplicableDamageTypes[t] === true); const maxArmorMarks = canApplyArmor ? Math.min( actor.system.armorScore - actor.system.armor.system.marks.value, - actor.system.rules.damageReduction.maxArmorMarked.total + actor.system.rules.damageReduction.maxArmorMarked.value ) : 0; @@ -100,7 +100,7 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap context.armorScore = this.actor.system.armorScore; context.armorMarks = currentMarks; context.basicMarksUsed = - selectedArmorMarks.length === this.actor.system.rules.damageReduction.maxArmorMarked.total; + selectedArmorMarks.length === this.actor.system.rules.damageReduction.maxArmorMarked.value; const stressReductionStress = this.availableStressReductions ? stressReductions.reduce((acc, red) => acc + red.cost, 0) diff --git a/module/applications/ui/chatLog.mjs b/module/applications/ui/chatLog.mjs index 4570b076..74e62b59 100644 --- a/module/applications/ui/chatLog.mjs +++ b/module/applications/ui/chatLog.mjs @@ -187,7 +187,6 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.attackTargetDoesNotExist')); return; } - game.canvas.pan(token); }; @@ -207,15 +206,25 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo if (!confirm) return; } } - + if (targets.length === 0) ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected')); - for (let target of targets) { - let damage = message.system.roll.total; - if (message.system.onSave && message.system.targets.find(t => t.id === target.id)?.saved?.success === true) - damage = Math.ceil(damage * (CONFIG.DH.ACTIONS.damageOnSave[message.system.onSave]?.mod ?? 1)); - target.actor.takeDamage(damage, message.system.damage.damageType); + for (let target of targets) { + let damages = message.system.damage; + if (message.system.onSave && message.system.targets.find(t => t.id === target.id)?.saved?.success === true) { + const mod = CONFIG.DH.ACTIONS.damageOnSave[message.system.onSave]?.mod ?? 1; + Object.entries(damages).forEach((k,v) => { + let newTotal = 0; + v.forEach(part => { + v.total = Math.ceil(v.total * mod); + newTotal += v.total; + }) + }) + } + // damage = Math.ceil(damage * (CONFIG.DH.ACTIONS.damageOnSave[message.system.onSave]?.mod ?? 1)); + + target.actor.takeDamage(damages.roll); } }; diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index f8927f0e..a1acd3f6 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -163,6 +163,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel { } getRollData(data = {}) { + if(!this.actor) return null; const actorData = this.actor.getRollData(false); // Add Roll results to RollDatas @@ -177,6 +178,8 @@ export default class DHBaseAction extends foundry.abstract.DataModel { } async use(event, ...args) { + if(!this.actor) throw new Error("An Action can't be used outside of an Actor context."); + const isFastForward = event.shiftKey || (!this.hasRoll && !this.hasSave); // Prepare base Config const initConfig = this.initActionConfig(event); diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index c15d2221..292eedb7 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -170,11 +170,10 @@ export default class DhCharacter extends BaseDataActor { rules: new fields.SchemaField({ damageReduction: new fields.SchemaField({ maxArmorMarked: new fields.SchemaField({ - value: new fields.NumberField({ required: true, integer: true, initial: 1 }), - bonus: new fields.NumberField({ + value: new fields.NumberField({ required: true, integer: true, - initial: 0, + initial: 1, label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedBonus' }), stressExtra: new fields.NumberField({ diff --git a/module/dice/d20Roll.mjs b/module/dice/d20Roll.mjs index 58d45f95..09dd716e 100644 --- a/module/dice/d20Roll.mjs +++ b/module/dice/d20Roll.mjs @@ -140,28 +140,22 @@ export default class D20Roll extends DHRoll { return modifiers; } - static async buildEvaluate(roll, config = {}, message = {}) { - if (config.evaluate !== false) await roll.evaluate(); - - this.postEvaluate(roll, config); - } - static postEvaluate(roll, config = {}) { - super.postEvaluate(roll, config); + const data = super.postEvaluate(roll, config); if (config.targets?.length) { config.targets.forEach(target => { const difficulty = config.roll.difficulty ?? target.difficulty ?? target.evasion; target.hit = this.isCritical || roll.total >= difficulty; }); } else if (config.roll.difficulty) - config.roll.success = roll.isCritical || roll.total >= config.roll.difficulty; - config.roll.advantage = { + data.success = roll.isCritical || roll.total >= config.roll.difficulty; + data.advantage = { type: config.roll.advantage, dice: roll.dAdvantage?.denomination, value: roll.dAdvantage?.total }; - config.roll.isCritical = roll.isCritical; - config.roll.extra = roll.dice + data.isCritical = roll.isCritical; + data.extra = roll.dice .filter(d => !roll.baseTerms.includes(d)) .map(d => { return { @@ -169,7 +163,8 @@ export default class D20Roll extends DHRoll { value: d.total }; }); - config.roll.modifierTotal = this.calculateTotalModifiers(roll); + data.modifierTotal = this.calculateTotalModifiers(roll); + return data; } resetFormula() { diff --git a/module/dice/damageRoll.mjs b/module/dice/damageRoll.mjs index 74de0a25..9a3abc3d 100644 --- a/module/dice/damageRoll.mjs +++ b/module/dice/damageRoll.mjs @@ -14,14 +14,18 @@ export default class DamageRoll extends DHRoll { if ( config.evaluate !== false ) { for ( const roll of config.roll ) await roll.roll.evaluate(); } - config.roll = config.roll.map(r => this.postEvaluate(r.roll)); + const parts = config.roll.map(r => this.postEvaluate(r)); + config.roll = this.unifyDamageRoll(parts); } static postEvaluate(roll, config = {}) { return { - ...super.postEvaluate(roll, config), + ...roll, + ...super.postEvaluate(roll.roll, config), + damageTypes: [...roll.damageTypes], + roll: roll.roll, type: config.type, - modifierTotal: this.calculateTotalModifiers(roll) + modifierTotal: this.calculateTotalModifiers(roll.roll) } } @@ -33,6 +37,43 @@ export default class DamageRoll extends DHRoll { } } + static unifyDamageRoll(rolls) { + const unified = {}; + rolls.forEach(r => { + const resource = unified[r.applyTo] ?? { formula: '', total: 0, parts: [] }; + resource.formula += `${resource.formula !== '' ? ' + ' : ''}${r.formula}`; + resource.total += r.total; + resource.parts.push(r); + unified[r.applyTo] = resource; + }) + return unified; + } + + static formatGlobal(rolls) { + let formula, total; + const applyTo = new Set(rolls.flatMap(r => r.applyTo)); + if(applyTo.size > 1) { + const data = {}; + rolls.forEach(r => { + if(data[r.applyTo]) { + data[r.applyTo].formula += ` + ${r.formula}` ; + data[r.applyTo].total += r.total ; + } else { + data[r.applyTo] = { + formula: r.formula, + total: r.total + } + } + }); + formula = Object.entries(data).reduce((a, [k,v]) => a + ` ${k}: ${v.formula}`, ''); + total = Object.entries(data).reduce((a, [k,v]) => a + ` ${k}: ${v.total}`, ''); + } else { + formula = rolls.map(r => r.formula).join(' + '); + total = rolls.reduce((a,c) => a + c.total, 0) + } + return {formula, total} + } + applyBaseBonus(part) { const modifiers = [], type = this.options.messageType ?? 'damage', diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs index 84c14f43..60b7244d 100644 --- a/module/dice/dhRoll.mjs +++ b/module/dice/dhRoll.mjs @@ -47,7 +47,7 @@ export default class DHRoll extends Roll { static async buildEvaluate(roll, config = {}, message = {}) { if (config.evaluate !== false) await roll.evaluate(); - config.roll = this.postEvaluate(roll); + config.roll = this.postEvaluate(roll, config); } static async buildPost(roll, config, message) { @@ -57,8 +57,9 @@ export default class DHRoll extends Roll { // Create Chat Message if (config.source?.message) { - if(Array.isArray(config.roll)) { - const pool = foundry.dice.terms.PoolTerm.fromRolls(config.roll); + console.log(config) + if(Array.isArray(config.roll?.parts)) { + const pool = foundry.dice.terms.PoolTerm.fromRolls(config.roll.parts.map(r => r.roll)); roll = Roll.fromTerms([pool]); } if (game.modules.get('dice-so-nice')?.active) await game.dice3d.showForRoll(roll, game.user, true); @@ -67,7 +68,7 @@ export default class DHRoll extends Roll { } } - static postEvaluate(roll) { + static postEvaluate(roll, config = {}) { return { total: roll.total, formula: roll.formula, diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index 5fd71e6c..e36d2427 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -161,21 +161,21 @@ export default class DualityRoll extends D20Roll { } static postEvaluate(roll, config = {}) { - super.postEvaluate(roll, config); + const data = super.postEvaluate(roll, config); - config.roll.hope = { + data.hope = { dice: roll.dHope.denomination, value: roll.dHope.total }; - config.roll.fear = { + data.fear = { dice: roll.dFear.denomination, value: roll.dFear.total }; - config.roll.rally = { + data.rally = { dice: roll.dRally?.denomination, value: roll.dRally?.total }; - config.roll.result = { + data.result = { duality: roll.withHope ? 1 : roll.withFear ? -1 : 0, total: roll.dHope.total + roll.dFear.total, label: roll.totalLabel @@ -184,6 +184,8 @@ export default class DualityRoll extends D20Roll { if(roll._rallyIndex && roll.data?.parent) roll.data.parent.deleteEmbeddedDocuments('ActiveEffect', [roll._rallyIndex]); - setDiceSoNiceForDualityRoll(roll, config.roll.advantage.type); + setDiceSoNiceForDualityRoll(roll, data.advantage.type); + + return data; } } diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 73a8a3db..458b68d7 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -391,59 +391,67 @@ export default class DhpActor extends Actor { return canUseArmor || canUseStress; } - async takeDamage(baseDamage, type) { - if (Hooks.call(`${CONFIG.DH.id}.preTakeDamage`, this, baseDamage, type) === false) return null; + async takeDamage(damages) { + if (Hooks.call(`${CONFIG.DH.id}.preTakeDamage`, this, damages) === false) return null; if (this.type === 'companion') { await this.modifyResource([{ value: 1, key: 'stress' }]); return; } - type = !Array.isArray(type) ? [type] : type; + const updates = []; - const hpDamage = this.calculateDamage(baseDamage, type); + Object.entries(damages).forEach(([type, damage]) => { + damage.parts.forEach(part => { + if(part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) + part.total = this.calculateDamage(part.total, part.damageTypes); + const update = updates.find(u => u.type === type); + if(update) { + update.value += part.total; + update.damageTypes.add(...new Set(part.damageTypes)); + } else updates.push({ value: part.total, type, damageTypes: new Set(part.damageTypes) }) + }) + }); - if (!hpDamage) return; + if (Hooks.call(`${CONFIG.DH.id}.postCalculateDamage`, this, damages) === false) return null; - const updates = [{ value: hpDamage, type: 'hitPoints' }]; + if(!updates.length) return; - if (this.type === 'character' && this.system.armor && this.#canReduceDamage(hpDamage, type)) { - const armorStackResult = await this.owner.query('armorStack', { - actorId: this.uuid, - damage: hpDamage, - type: type - }); - if (armorStackResult) { - const { modifiedDamage, armorSpent, stressSpent } = armorStackResult; - updates.find(u => u.type === 'hitPoints').value = modifiedDamage; - updates.push( - ...(armorSpent ? [{ value: armorSpent, key: 'armorStack' }] : []), - ...(stressSpent ? [{ value: stressSpent, key: 'stress' }] : []) - ); + const hpDamage = updates.find(u => u.type === CONFIG.DH.GENERAL.healingTypes.hitPoints.id); + if(hpDamage) { + hpDamage.value = this.convertDamageToThreshold(hpDamage.value); + if (this.type === 'character' && this.system.armor && this.#canReduceDamage(hpDamage.value, hpDamage.damageTypes)) { + const armorStackResult = await this.owner.query('armorStack', { + actorId: this.uuid, + damage: hpDamage.value, + type: [...hpDamage.damageTypes] + }); + if (armorStackResult) { + const { modifiedDamage, armorSpent, stressSpent } = armorStackResult; + updates.find(u => u.type === 'hitPoints').value = modifiedDamage; + updates.push( + ...(armorSpent ? [{ value: armorSpent, key: 'armorStack' }] : []), + ...(stressSpent ? [{ value: stressSpent, key: 'stress' }] : []) + ); + } } } await this.modifyResource(updates); - if (Hooks.call(`${CONFIG.DH.id}.postTakeDamage`, this, damage, type) === false) return null; + if (Hooks.call(`${CONFIG.DH.id}.postTakeDamage`, this, damages) === false) return null; } calculateDamage(baseDamage, type) { - if (Hooks.call(`${CONFIG.DH.id}.preCalculateDamage`, this, baseDamage, type) === false) return null; - /* if(this.system.resistance[type]?.immunity) return 0; - if(this.system.resistance[type]?.resistance) baseDamage = Math.ceil(baseDamage / 2); */ if (this.canResist(type, 'immunity')) return 0; if (this.canResist(type, 'resistance')) baseDamage = Math.ceil(baseDamage / 2); - // const flatReduction = this.system.resistance[type].reduction; const flatReduction = this.getDamageTypeReduction(type); const damage = Math.max(baseDamage - (flatReduction ?? 0), 0); - const hpDamage = this.convertDamageToThreshold(damage); + // const hpDamage = this.convertDamageToThreshold(damage); - if (Hooks.call(`${CONFIG.DH.id}.postCalculateDamage`, this, baseDamage, type) === false) return null; - - return hpDamage; + return damage; } canResist(type, resistance) { diff --git a/module/documents/token.mjs b/module/documents/token.mjs index 89305128..f48af563 100644 --- a/module/documents/token.mjs +++ b/module/documents/token.mjs @@ -16,7 +16,7 @@ export default class DHToken extends TokenDocument { }); bars.sort((a, b) => a.label.compare(b.label)); - const invalidAttributes = ['gold', 'levelData', 'actions', 'rules.damageReduction.maxArmorMarked.value']; + const invalidAttributes = ['gold', 'levelData', 'actions']; const values = attributes.value.reduce((acc, v) => { const a = v.join('.'); if (invalidAttributes.some(x => a.startsWith(x))) return acc; diff --git a/styles/less/ui/chat/chat.less b/styles/less/ui/chat/chat.less index 12e8ba0c..6b5db1b9 100644 --- a/styles/less/ui/chat/chat.less +++ b/styles/less/ui/chat/chat.less @@ -164,6 +164,11 @@ } } } + + .damage-resource { + font-weight: 600; + margin-top: 5px; + } } .dice-total { diff --git a/templates/dialogs/dice-roll/damageSelection.hbs b/templates/dialogs/dice-roll/damageSelection.hbs index d31a31ec..e963d448 100644 --- a/templates/dialogs/dice-roll/damageSelection.hbs +++ b/templates/dialogs/dice-roll/damageSelection.hbs @@ -4,7 +4,7 @@ {{#each @root.formula}}
- Formula: {{roll.formula}} + Formula: {{roll.formula}} {{!-- {{localize (concat 'CONFIG.DH.GENERAL.healingTypes.' applyTo '.label')}} --}} {{#with (lookup @root.config.GENERAL.healingTypes applyTo)}} diff --git a/templates/ui/chat/parts/damage-chat.hbs b/templates/ui/chat/parts/damage-chat.hbs index 18041d80..5c99bdde 100644 --- a/templates/ui/chat/parts/damage-chat.hbs +++ b/templates/ui/chat/parts/damage-chat.hbs @@ -2,28 +2,33 @@ {{log damage}} {{#unless noTitle}}
{{damage.title}}
{{/unless}}
-
{{damage.roll.formula}}
-
-
-
- {{#each damage.roll.dice}} -
-
- {{formula}} - {{total}} -
-
    - {{#each results}} -
  1. {{result}}
  2. - {{/each}} -
-
- {{/each}} - {{#if damage.roll.modifierTotal}}
{{#if (gt damage.roll.modifierTotal 0)}}+{{/if}}{{damage.roll.modifierTotal}}
{{/if}} -
Total: {{damage.roll.total}}
-
+ {{#each damage.roll as | roll index | }} +
{{localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.name')}}
+
{{roll.formula}}
+
+
+ {{#each roll.parts}} +
+
+
+ {{formula}} + {{total}} +
+
    + {{#each dice}} + {{#each results}} +
  1. {{result}}
  2. + {{/each}} + {{/each}} +
+
+ {{#if modifierTotal}}
{{#if (gt modifierTotal 0)}}+{{/if}}{{modifierTotal}}
{{/if}} +
Total: {{total}}
+
+ {{/each}} +
-
-
{{damage.roll.total}}
+
{{roll.total}}
+ {{/each}}
\ No newline at end of file