From ce37bd9c60a667e24ca2d8729d8ac1dea16a109a Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 18 Jul 2026 01:06:58 -0400 Subject: [PATCH 1/7] Fix taking damage and start fixing healing --- .../sheets-configs/adversary-settings.mjs | 4 +- module/data/action/baseAction.mjs | 4 +- module/data/chat-message/chatDamageData.mjs | 5 +- module/data/fields/action/damageField.mjs | 22 ++++---- module/dice/damageRoll.mjs | 1 + module/documents/actor.mjs | 55 +++++++++---------- .../dialogs/dice-roll/damageSelection.hbs | 4 +- 7 files changed, 45 insertions(+), 50 deletions(-) diff --git a/module/applications/sheets-configs/adversary-settings.mjs b/module/applications/sheets-configs/adversary-settings.mjs index c5f036ed..0bf18ee6 100644 --- a/module/applications/sheets-configs/adversary-settings.mjs +++ b/module/applications/sheets-configs/adversary-settings.mjs @@ -9,8 +9,8 @@ export default class DHAdversarySettings extends DHBaseActorSettings { classes: ['adversary-settings'], position: { width: 455, height: 'auto' }, actions: { - addExperience: DHAdversarySettings.#onAddExperience, - removeExperience: DHAdversarySettings.#onRemoveExperience, + addExperience: this.#onAddExperience, + removeExperience: this.#onRemoveExperience, addDamage: this.#onAddDamage, removeDamage: this.#onRemoveDamage } diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index f773ae32..8789c859 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -429,11 +429,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel } get hasDamage() { - return this.type !== 'healing' && Boolean(this.damage.main) || Boolean(this.damage.resources.length); + return this.type !== 'healing' && (Boolean(this.damage.main) || !foundry.utils.isEmpty(this.damage.resources)); } get hasHealing() { - return this.type === 'healing' && Boolean(this.damage.main) || Boolean(this.damage.resources.length); + return this.type === 'healing' && !foundry.utils.isEmpty(this.damage.resources); } get hasSave() { diff --git a/module/data/chat-message/chatDamageData.mjs b/module/data/chat-message/chatDamageData.mjs index c612729e..b7679ee1 100644 --- a/module/data/chat-message/chatDamageData.mjs +++ b/module/data/chat-message/chatDamageData.mjs @@ -28,10 +28,7 @@ export class ChatDamageData extends foundry.abstract.DataModel { } _prepareRolls() { - if (this.main) { - this.main = Roll.fromData(this.main); - } - + this.main &&= Roll.fromData(this.main); for (const key of Object.keys(this.resources)) { this.resources[key] = Roll.fromData(this.resources[key]); } diff --git a/module/data/fields/action/damageField.mjs b/module/data/fields/action/damageField.mjs index 4edc2533..4fe38aca 100644 --- a/module/data/fields/action/damageField.mjs +++ b/module/data/fields/action/damageField.mjs @@ -80,7 +80,7 @@ export default class DamageField extends fields.SchemaField { const targetDamage = []; const damagePromises = []; - for (let target of targets) { + for (const target of targets) { const actor = foundry.utils.fromUuidSync(target.actorId); if (!actor) continue; if (!config.hasHealing && config.onSave && target.saved?.success === true) { @@ -102,14 +102,12 @@ export default class DamageField extends fields.SchemaField { actor.takeHealing(config.damage.types).then(updates => targetDamage.push({ token, updates })) ); else { - const configDamage = foundry.utils.deepClone(config.damage.types); - const hpDamageMultiplier = config.actionActor?.system.rules?.attack?.damage?.hpDamageMultiplier ?? 1; - const hpDamageTakenMultiplier = actor.system.rules?.attack?.damage?.hpDamageTakenMultiplier; - if (configDamage.hitPoints) { - configDamage.hitPoints = configDamage.hitPoints.toJSON(); - configDamage.hitPoints.total = Math.ceil( - configDamage.hitPoints.total * hpDamageMultiplier * hpDamageTakenMultiplier - ); + const configDamage = config.damage.clone(); + configDamage.main &&= configDamage.main.toJSON(); + if (configDamage.main) { + const multiplier = config.actionActor?.system.rules?.attack?.damage?.hpDamageMultiplier ?? 1; + const takenMultiplier = actor.system.rules?.attack?.damage?.hpDamageTakenMultiplier; + configDamage.main.total = Math.ceil(configDamage.main.total * multiplier * takenMultiplier); } damagePromises.push( @@ -179,12 +177,12 @@ export default class DamageField extends fields.SchemaField { static formatFormulas(damageData, data) { const formulas = damageData.map(x => ({ formula: DamageField.getFormulaValue.call(this, x, data).getFormula(this.actor), - damageTypes: x.applyTo === 'hitPoints' && !x.type.size ? new Set(['physical']) : x.type, + damageTypes: x.type ?? new Set(), applyTo: x.applyTo })); const formattedFormulas = []; - formulas.forEach(formula => { + for (const formula of formulas) { if (isNaN(formula.formula)) formula.formula = Roll.replaceFormulaData(formula.formula, this.getRollData(data)); const same = formattedFormulas.find( @@ -192,7 +190,7 @@ export default class DamageField extends fields.SchemaField { ); if (same) same.formula += ` + ${formula.formula}`; else formattedFormulas.push(formula); - }); + } return formattedFormulas; } diff --git a/module/dice/damageRoll.mjs b/module/dice/damageRoll.mjs index 64077e1f..b5e8b29e 100644 --- a/module/dice/damageRoll.mjs +++ b/module/dice/damageRoll.mjs @@ -145,6 +145,7 @@ export default class DamageRoll extends DHRoll { } constructFormula(formulaData, config, isDamage) { + if (!formulaData) return null; this.options.isCritical = config.isCritical; const isHitpointPart = formulaData.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id; diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index d7733dd5..ec2d8348 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -656,26 +656,30 @@ export default class DhpActor extends Actor { return; } - const updates = []; - - Object.entries(damages).forEach(([key, damage]) => { - if (key === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) - damage.total = this.calculateDamage(damage.total, damage.damageTypes); - const update = updates.find(u => u.key === key); - if (update) { - update.value += damage.total; - update.damageTypes.add(...new Set(damage.damageTypes)); - } else updates.push({ value: damage.total, key, damageTypes: new Set(damage.damageTypes) }); - }); + if (damages.main) { + damages.main.total = this.calculateDamage(damages.main.total, damages.main.damageTypes); + } if (Hooks.call(`${CONFIG.DH.id}.postCalculateDamage`, this, damages) === false) return null; - if (!updates.length) return; + // Convert deducted resources and damage to a record of updates, merging damage to hp with hp marked + const updates = Object.entries(damages.resources).map(([key, damage]) => ({ key, value: damage.total })); + if (damages.main) { + const existing = updates.find(u => u.key === CONFIG.DH.GENERAL.healingTypes.hitPoints.id); + const value = this.convertDamageToThreshold(damages.main.total); + const damageTypes = new Set(damages.main.options.damageTypes); + if (existing) { + existing.value += value; + existing.damageTypes = damageTypes; + } else { + updates.push({ value, damageTypes, key: CONFIG.DH.GENERAL.healingTypes.hitPoints.id }); + } + } + if (!updates.some(u => u.value)) return; // early return if nothing to do const hpDamage = updates.find(u => u.key === CONFIG.DH.GENERAL.healingTypes.hitPoints.id); if (hpDamage?.value) { - hpDamage.value = this.convertDamageToThreshold(hpDamage.value); - if (this.type === 'character' && !isDirect && this.#canReduceDamage(hpDamage.value, hpDamage.damageTypes)) { + if (this.type === 'character' && !isDirect && this.#canReduceDamage(hpDamage.total, hpDamage.damageTypes)) { const armorSlotResult = await this.owner.query( 'armorSlot', { @@ -689,7 +693,7 @@ export default class DhpActor extends Actor { ); if (armorSlotResult) { const { modifiedDamage, armorChanges, stressSpent } = armorSlotResult; - updates.find(u => u.key === 'hitPoints').value = modifiedDamage; + hpDamage.value = modifiedDamage; for (const armorChange of armorChanges) { updates.push({ value: armorChange.amount, key: 'armor', uuid: armorChange.uuid }); } @@ -699,18 +703,13 @@ export default class DhpActor extends Actor { else updates.push({ value: stressSpent, key: 'stress' }); } } - } - if (this.type === 'adversary') { + } else if (this.type === 'adversary') { const reducedSeverity = hpDamage.damageTypes.reduce((value, curr) => { return Math.max(this.system.rules.damageReduction.reduceSeverity[curr], value); }, 0); hpDamage.value = Math.max(hpDamage.value - reducedSeverity, 0); - - if ( - hpDamage.value && - this.system.rules.damageReduction.thresholdImmunities[getDamageKey(hpDamage.value)] - ) { - hpDamage.value -= 1; + if (this.system.rules.damageReduction.thresholdImmunities[getDamageKey(hpDamage.value)]) { + hpDamage.value = Math.max(0, hpDamage.value - 1); } } } @@ -727,12 +726,10 @@ export default class DhpActor extends Actor { for (var result of results) resourceMap.addResources(result.updates); resourceMap.updateResources(); } - - updates.forEach( - u => - (u.value = - u.key === 'fear' || this.system?.resources?.[u.key]?.isReversed === false ? u.value * -1 : u.value) - ); + + for (const u of updates) { + u.value = u.key === 'fear' || this.system?.resources?.[u.key]?.isReversed === false ? u.value * -1 : u.value; + } await this.modifyResource(updates); diff --git a/templates/dialogs/dice-roll/damageSelection.hbs b/templates/dialogs/dice-roll/damageSelection.hbs index cc12e26f..b12b78cb 100644 --- a/templates/dialogs/dice-roll/damageSelection.hbs +++ b/templates/dialogs/dice-roll/damageSelection.hbs @@ -16,7 +16,9 @@ {{/if}} - {{> formula @root.damageFormula path="damageFormula"}} + {{#if @root.damageFormula}} + {{> formula @root.damageFormula path="damageFormula"}} + {{/if}} {{#each @root.resourceFormulas}} {{> formula path=(concat "resourceFormulas." @key)}} From 4bffc27403ed712009e5c0886977730e01ed7848 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 18 Jul 2026 04:24:32 -0400 Subject: [PATCH 2/7] Fix tier adjustment damage --- module/data/actor/tierAdjustment.mjs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/module/data/actor/tierAdjustment.mjs b/module/data/actor/tierAdjustment.mjs index 8b9e5bdc..4bf74a45 100644 --- a/module/data/actor/tierAdjustment.mjs +++ b/module/data/actor/tierAdjustment.mjs @@ -40,17 +40,17 @@ export function getTierAdjustedAdversary(source, tier) { // Store initial attack damage for abilities that have you deal a "standard attack" const initialAttack = { - type: source.system.attack.damage?.parts.hitPoints?.type?.toSorted(), - value: getFormula(source.system.attack.damage?.parts.hitPoints?.value) + type: source.system.attack.damage?.main?.type?.toSorted(), + value: getFormula(source.system.attack.damage?.main?.value) }; // Update damage of base attack. try { const damage = source.system.attack.damage; - if (!damage?.parts.hitPoints) throw new Error('Unexpected missing attack in adversary'); + if (!damage?.main) throw new Error('Unexpected missing damage in adversary'); for (const property of ['value', 'valueAlt']) { - const data = damage.parts.hitPoints[property]; + const data = damage.main[property]; const previousFormula = getFormula(data); const value = calculateAdjustedDamage(previousFormula, 'attack', damageMeta); applyAdjustedDamage(data, value); @@ -82,12 +82,12 @@ export function getTierAdjustedAdversary(source, tier) { // Update damage in item actions and convert all formula matches in the descriptions to the new damage for (const action of Object.values(item.system.actions)) { - if (!action.damage?.parts.hitPoints) continue; + if (!action.damage?.main) continue; try { // Apply conversions and save a record. If it matches attack damage *and* Its not in the description, use attack conversion instead const result = []; for (const property of ['value', 'valueAlt']) { - const { [property]: data, type: damageType } = action.damage.parts.hitPoints; + const { [property]: data, type: damageType } = action.damage.main; const previousFormula = getFormula(data); const isActuallyAttack = previousFormula === initialAttack.value && @@ -199,7 +199,7 @@ function calculateAdjustedDamage(formula, type, { currentDamageRange, newDamageR } /** - * Get formula from either damage parts *or* a simple formula object. + * Get formula from either damage data *or* a simple formula object. * @returns {string} the new formula data */ function getFormula(data) { From b7e92cfe0e508dca4d42cea1b428346f91cac00e Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 18 Jul 2026 12:37:51 +0200 Subject: [PATCH 3/7] Fixed actorRoll migrateData --- module/data/chat-message/actorRoll.mjs | 32 +++++++++++++++----------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/module/data/chat-message/actorRoll.mjs b/module/data/chat-message/actorRoll.mjs index 2d578a0f..363a9344 100644 --- a/module/data/chat-message/actorRoll.mjs +++ b/module/data/chat-message/actorRoll.mjs @@ -188,31 +188,35 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel { } static migrateData(source) { - if (source.hasDamage && !source.damage.resources === undefined) { + const { main, resources, ...flatDamageKeys } = source.damage; + if (!main && !resources) { + source.damage.main = null; + source.damage.resources = {}; + const getRoll = key => { const damageData = source.damage[key]; const oldRoll = damageData.parts[0]?.roll; - return oldRoll ? { + return oldRoll ? JSON.stringify({ ...oldRoll, options: { ...oldRoll.options, damageTypes: damageData.parts[0].damageTypes ?? [] } - } : null; + }) : null; }; - source.damage = { - main: source.damage.hitPoints ? getRoll('hitPoints') : null, - resources: Object.keys(source.damage).reduce((acc, key) => { - if (key === 'hitPoints') return acc; + for (const key of Object.keys(flatDamageKeys)) { + if (key === 'hitPoints' && source.hasDamage && !source.hasHealing) { + source.damage.main = getRoll('hitPoints'); + } + else { + source.damage.resources[key] = getRoll(key); + } + } + } - const roll = getRoll(key); - if (!roll) return acc; - - acc[key] = roll; - return acc; - }, {}) - }; + for (const key of Object.keys(flatDamageKeys)) { + delete source.damage[key]; } return source; From b2bf102443ce277c61053281d40c8b248e4a1cc5 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 18 Jul 2026 12:48:24 +0200 Subject: [PATCH 4/7] Fixed old messages being instantiated with damage rolls as class=roll instead of the new class=baseRoll --- daggerheart.mjs | 2 +- module/data/chat-message/actorRoll.mjs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/daggerheart.mjs b/daggerheart.mjs index 4641c34a..94f243b8 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -24,7 +24,7 @@ import TokenManager from './module/documents/tokenManager.mjs'; CONFIG.DH = SYSTEM; CONFIG.TextEditor.enrichers.push(...enricherConfig); -CONFIG.Dice.rolls = [Roll = BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll, FateRoll]; +CONFIG.Dice.rolls = [BaseRoll, Roll, DHRoll, DualityRoll, D20Roll, DamageRoll, FateRoll]; CONFIG.Dice.daggerheart = { DHRoll: DHRoll, DualityRoll: DualityRoll, diff --git a/module/data/chat-message/actorRoll.mjs b/module/data/chat-message/actorRoll.mjs index 363a9344..376e7e3a 100644 --- a/module/data/chat-message/actorRoll.mjs +++ b/module/data/chat-message/actorRoll.mjs @@ -192,12 +192,13 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel { if (!main && !resources) { source.damage.main = null; source.damage.resources = {}; - + const getRoll = key => { const damageData = source.damage[key]; const oldRoll = damageData.parts[0]?.roll; return oldRoll ? JSON.stringify({ ...oldRoll, + class: 'BaseRoll', options: { ...oldRoll.options, damageTypes: damageData.parts[0].damageTypes ?? [] From 21daa7f3c071abffacabde3b6cd594f58332eca2 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 18 Jul 2026 13:54:16 +0200 Subject: [PATCH 5/7] Corrected actor.takeDamage and actor.takeHealing --- module/applications/dialogs/damageDialog.mjs | 5 +- module/data/fields/action/damageField.mjs | 2 +- module/dice/damageRoll.mjs | 12 ++- module/documents/actor.mjs | 78 ++++++++++---------- 4 files changed, 52 insertions(+), 45 deletions(-) diff --git a/module/applications/dialogs/damageDialog.mjs b/module/applications/dialogs/damageDialog.mjs index 8b37cf2d..ce613ade 100644 --- a/module/applications/dialogs/damageDialog.mjs +++ b/module/applications/dialogs/damageDialog.mjs @@ -77,7 +77,10 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application static updateRollConfiguration(_event, _, formData) { const data = foundry.utils.expandObject(formData.object); - foundry.utils.mergeObject(this.config.damageFormula, data.damageFormula); + + if (this.config.damageFormula) + foundry.utils.mergeObject(this.config.damageFormula, data.damageFormula); + foundry.utils.mergeObject(this.config.resourceFormulas, data.resourceFormulas); foundry.utils.mergeObject(this.config.modifiers, data.modifiers); this.config.selectedMessageMode = data.selectedMessageMode; diff --git a/module/data/fields/action/damageField.mjs b/module/data/fields/action/damageField.mjs index 4fe38aca..c2db4b51 100644 --- a/module/data/fields/action/damageField.mjs +++ b/module/data/fields/action/damageField.mjs @@ -99,7 +99,7 @@ export default class DamageField extends fields.SchemaField { : actor.prototypeToken; if (config.hasHealing) damagePromises.push( - actor.takeHealing(config.damage.types).then(updates => targetDamage.push({ token, updates })) + actor.takeHealing(config.damage).then(updates => targetDamage.push({ token, updates })) ); else { const configDamage = config.damage.clone(); diff --git a/module/dice/damageRoll.mjs b/module/dice/damageRoll.mjs index b5e8b29e..79b9f653 100644 --- a/module/dice/damageRoll.mjs +++ b/module/dice/damageRoll.mjs @@ -27,10 +27,14 @@ export default class DamageRoll extends DHRoll { return roll.roll; } - config.damage.main = await evaluateRoll(config.damageFormula); - config.damage.main.options = { damageTypes: - config.damageFormula.damageTypes ? [...config.damageFormula.damageTypes] : [] - }; + if (!config.damage) config.damage = { main: null, resources: {} }; + + if (config.damageFormula) { + config.damage.main = await evaluateRoll(config.damageFormula); + config.damage.main.options = { damageTypes: + config.damageFormula.damageTypes ? [...config.damageFormula.damageTypes] : [] + }; + } for (const roll of config.resourceFormulas) { config.damage.resources[roll.applyTo] = await evaluateRoll(roll); diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index ec2d8348..de55b4b7 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -662,23 +662,16 @@ export default class DhpActor extends Actor { if (Hooks.call(`${CONFIG.DH.id}.postCalculateDamage`, this, damages) === false) return null; - // Convert deducted resources and damage to a record of updates, merging damage to hp with hp marked + // Convert deducted resources to a record of updates. Return if nothing to do. const updates = Object.entries(damages.resources).map(([key, damage]) => ({ key, value: damage.total })); - if (damages.main) { - const existing = updates.find(u => u.key === CONFIG.DH.GENERAL.healingTypes.hitPoints.id); - const value = this.convertDamageToThreshold(damages.main.total); - const damageTypes = new Set(damages.main.options.damageTypes); - if (existing) { - existing.value += value; - existing.damageTypes = damageTypes; - } else { - updates.push({ value, damageTypes, key: CONFIG.DH.GENERAL.healingTypes.hitPoints.id }); - } - } - if (!updates.some(u => u.value)) return; // early return if nothing to do + if (!updates.some(u => u.value) && !damages.main) return; - const hpDamage = updates.find(u => u.key === CONFIG.DH.GENERAL.healingTypes.hitPoints.id); - if (hpDamage?.value) { + if (damages.main) { + const hpDamage = { + value: this.convertDamageToThreshold(damages.main.total), + damageTypes: new Set(damages.main.options.damageTypes), + key: CONFIG.DH.GENERAL.healingTypes.hitPoints.id + }; if (this.type === 'character' && !isDirect && this.#canReduceDamage(hpDamage.total, hpDamage.damageTypes)) { const armorSlotResult = await this.owner.query( 'armorSlot', @@ -712,6 +705,15 @@ export default class DhpActor extends Actor { hpDamage.value = Math.max(0, hpDamage.value - 1); } } + + // Merge existing hitPoint deduction with finalised damage deduction + const existing = updates.find(u => u.key === CONFIG.DH.GENERAL.healingTypes.hitPoints.id); + if (existing) { + existing.value += hpDamage.value; + existing.damageTypes = hpDamage.damageTypes; + } else { + updates.push(hpDamage); + } } const results = await game.system.registeredTriggers.runTrigger( @@ -738,6 +740,28 @@ export default class DhpActor extends Actor { return updates; } + async takeHealing(healings) { + if (Hooks.call(`${CONFIG.DH.id}.preTakeHealing`, this, healings) === false) return null; + + const updates = Object.entries(healings.resources).map(([key, damage]) => ({ + key, + value: damage.total + })); + + updates.forEach( + u => + (u.value = !(u.key === 'fear' || this.system?.resources?.[u.key]?.isReversed === false) + ? u.value * -1 + : u.value) + ); + + await this.modifyResource(updates); + + if (Hooks.call(`${CONFIG.DH.id}.postTakeHealing`, this, updates) === false) return null; + + return updates; + } + calculateDamage(baseDamage, type) { if (this.canResist(type, 'immunity')) return 0; if (this.canResist(type, 'resistance')) baseDamage = Math.ceil(baseDamage / 2); @@ -762,30 +786,6 @@ export default class DhpActor extends Actor { return reduction === Infinity ? 0 : reduction; } - async takeHealing(healings) { - if (Hooks.call(`${CONFIG.DH.id}.preTakeHealing`, this, healings) === false) return null; - - const updates = []; - Object.entries(healings).forEach(([key, healing]) => { - const update = updates.find(u => u.key === key); - if (update) update.value += healing.roll.total; - else updates.push({ value: healing.roll.total, key }); - }); - - updates.forEach( - u => - (u.value = !(u.key === 'fear' || this.system?.resources?.[u.key]?.isReversed === false) - ? u.value * -1 - : u.value) - ); - - await this.modifyResource(updates); - - if (Hooks.call(`${CONFIG.DH.id}.postTakeHealing`, this, updates) === false) return null; - - return updates; - } - /** * Resources are modified asynchronously, so be careful not to update the same resource in * quick succession. From 6273394db894f92c3f177f8b78c68248c7a7f1b5 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 18 Jul 2026 14:41:41 +0200 Subject: [PATCH 6/7] DamageDialog handlebars improvement --- .../less/dialog/damage-selection/sheet.less | 6 ++ .../dialogs/dice-roll/damageSelection.hbs | 78 +++++++++++-------- 2 files changed, 51 insertions(+), 33 deletions(-) diff --git a/styles/less/dialog/damage-selection/sheet.less b/styles/less/dialog/damage-selection/sheet.less index 9f8cfc8a..cb2c14e5 100644 --- a/styles/less/dialog/damage-selection/sheet.less +++ b/styles/less/dialog/damage-selection/sheet.less @@ -17,6 +17,12 @@ } } + .section-header { + font-size: var(--font-size-20); + color: light-dark(@dark, @beige); + text-align: center; + } + .bonuses { gap: 4px; .critical-chip { diff --git a/templates/dialogs/dice-roll/damageSelection.hbs b/templates/dialogs/dice-roll/damageSelection.hbs index b12b78cb..117df634 100644 --- a/templates/dialogs/dice-roll/damageSelection.hbs +++ b/templates/dialogs/dice-roll/damageSelection.hbs @@ -15,14 +15,53 @@ {{/each}} {{/if}} - + {{#if @root.damageFormula}} - {{> formula @root.damageFormula path="damageFormula"}} + {{#with @root.damageFormula}} +
+ {{localize "DAGGERHEART.GENERAL.formula"}}: {{roll.formula}} + + {{#with (lookup @root.config.GENERAL.healingTypes applyTo)}} + {{localize label}} + {{/with}} + {{#if damageTypes}} + {{#each damageTypes as | type | }} + {{#with (lookup @root.config.GENERAL.damageTypes type)}} + + {{/with}} + {{/each}} + {{/if}} + +
+
+ + +
+ {{/with}} {{/if}} - {{#each @root.resourceFormulas}} - {{> formula path=(concat "resourceFormulas." @key)}} - {{/each}} +
+ {{#unless (empty @root.resourceFormulas)}} +
{{localize "Resource Drain"}}
+ {{/unless}} + + {{#each @root.resourceFormulas}} +
+ {{localize "DAGGERHEART.GENERAL.formula"}}: {{roll.formula}} + + {{#with (lookup @root.config.GENERAL.healingTypes applyTo)}} + {{localize label}} + {{/with}} + +
+
+ +
+ {{/each}} +
{{#if damageOptions.groupAttack}}
@@ -68,31 +107,4 @@ {{localize "DAGGERHEART.GENERAL.roll"}} - - -{{#*inline "formula"}} -
- {{localize "DAGGERHEART.GENERAL.formula"}}: {{roll.formula}} - - {{#with (lookup @root.config.GENERAL.healingTypes applyTo)}} - {{localize label}} - {{/with}} - {{#unless @root.hasHealing}} - {{#if damageTypes}} - {{#each damageTypes as | type | }} - {{#with (lookup @root.config.GENERAL.damageTypes type)}} - - {{/with}} - {{/each}} - {{/if}} - {{/unless}} - -
-
- - -
-{{/inline}} \ No newline at end of file + \ No newline at end of file From 88754c5a805d257d100f3291a85797f48ae50393 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 18 Jul 2026 15:32:57 +0200 Subject: [PATCH 7/7] Fixed GroupAttack and DirectDamage --- module/applications/dialogs/tagTeamDialog.mjs | 82 +++++++++++++------ module/data/action/attackAction.mjs | 17 ++-- module/data/action/baseAction.mjs | 11 ++- module/dice/damageRoll.mjs | 8 +- 4 files changed, 72 insertions(+), 46 deletions(-) diff --git a/module/applications/dialogs/tagTeamDialog.mjs b/module/applications/dialogs/tagTeamDialog.mjs index c54720f4..594b136f 100644 --- a/module/applications/dialogs/tagTeamDialog.mjs +++ b/module/applications/dialogs/tagTeamDialog.mjs @@ -553,8 +553,10 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio const { memberKey } = button.dataset; this.updatePartyData( { - [`system.tagTeam.members.${memberKey}.damageRollData.types`]: - _replace({}) + [`system.tagTeam.members.${memberKey}.damageRollData`]: { + main: null, + resources: _replace({}) + } }, this.getUpdatingParts(button) ); @@ -577,19 +579,19 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio async getCriticalDamage(origDamage) { const newDamage = origDamage ? ChatDamageData.fromJSON(JSON.stringify(origDamage)) : null; - for (let key in newDamage?.types ?? {}) { - const criticalDamage = await getCritDamageBonus(newDamage.types[key].formula); - if (!criticalDamage) continue; - - const criticalTerm = new foundry.dice.terms.NumericTerm({ number: criticalDamage, evaluated: true }); - criticalTerm.evaluate(); - newDamage.types[key] = await Roll.fromTerms([ - ...origDamage.types[key].terms, - new foundry.dice.terms.OperatorTerm({ operator: '+' }), - criticalTerm - ]); - newDamage.types[key].options = foundry.utils.deepClone(origDamage.types[key].options); - } + if (newDamage?.main) { + const criticalDamage = await getCritDamageBonus(newDamage.main.formula); + if (criticalDamage) { + const criticalTerm = new foundry.dice.terms.NumericTerm({ number: criticalDamage, evaluated: true }); + criticalTerm.evaluate(); + newDamage.main = await Roll.fromTerms([ + ...origDamage.main.terms, + new foundry.dice.terms.OperatorTerm({ operator: '+' }), + criticalTerm + ]); + newDamage.main.options = foundry.utils.deepClone(origDamage.main.options); + } + } return newDamage; } @@ -644,25 +646,47 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio ? await this.getCriticalDamage(secondaryRoll.damageRollData) : secondaryRoll.damageRollData; if (mainRoll.damageRollData) { - for (const [key, damage] of Object.entries(secondaryDamage.types ?? {})) { - if (key in mainRoll.damageRollData.types) { - mainRoll.damageRollData.types[key] = Roll.fromTerms([ - ...baseMainRoll.damageRollData.types[key].terms, + if (secondaryDamage.main) { + if (mainRoll.damageRollData.main) { + mainRoll.damageRollData.main = Roll.fromTerms([ + ...baseMainRoll.damageRollData.main.terms, new foundry.dice.terms.OperatorTerm({ operator: '+' }), - ...baseSecondaryRoll.damageRollData.types[key].terms + ...baseSecondaryRoll.damageRollData.main.terms ]); /* Joining the roll.options of both rolls */ const joinedDamageTypes = new Set([ - ...baseMainRoll.damageRollData.types[key].options.damageTypes, - ...baseSecondaryRoll.damageRollData.types[key].options.damageTypes + ...baseMainRoll.damageRollData.main.options.damageTypes, + ...baseSecondaryRoll.damageRollData.main.options.damageTypes ]); - mainRoll.damageRollData.types[key].options = { - ...baseMainRoll.damageRollData.types[key].options, + mainRoll.damageRollData.main.options = { + ...baseMainRoll.damageRollData.main.options, damageTypes: [...joinedDamageTypes] }; } else { - mainRoll.damageRollData.types[key] = damage; + mainRoll.damageRollData.main = secondaryDamage.main; + } + } + + for (const [key, damage] of Object.entries(secondaryDamage.resources ?? {})) { + if (key in mainRoll.damageRollData.resources) { + mainRoll.damageRollData.resources[key] = Roll.fromTerms([ + ...baseMainRoll.damageRollData.resources[key].terms, + new foundry.dice.terms.OperatorTerm({ operator: '+' }), + ...baseSecondaryRoll.damageRollData.resources[key].terms + ]); + + /* Joining the roll.options of both rolls */ + const joinedDamageTypes = new Set([ + ...baseMainRoll.damageRollData.resources[key].options.damageTypes, + ...baseSecondaryRoll.damageRollData.resources[key].options.damageTypes + ]); + mainRoll.damageRollData.resources[key].options = { + ...baseMainRoll.damageRollData.resources[key].options, + damageTypes: [...joinedDamageTypes] + }; + } else { + mainRoll.damageRollData.resources[key] = damage; } } } else { @@ -727,8 +751,12 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio ...mainRoll.options, damage: joinedRoll.damageRollData?.toJSON() }; - for (const type of Object.keys(joinedRoll.damageRollData?.types ?? {})) { - systemData.damage.types[type] = joinedRoll.damageRollData.types[type].toJSON(); + + if (joinedRoll.damageRollData.main) { + systemData.damage.main = joinedRoll.damageRollData.toJSON(); + } + for (const type of Object.keys(joinedRoll.damageRollData?.resources ?? {})) { + systemData.damage.resources[type] = joinedRoll.damageRollData.resources[type].toJSON(); } const cls = getDocumentClass('ChatMessage'), diff --git a/module/data/action/attackAction.mjs b/module/data/action/attackAction.mjs index 6c205de6..dadc85f0 100644 --- a/module/data/action/attackAction.mjs +++ b/module/data/action/attackAction.mjs @@ -13,18 +13,19 @@ export default class DHAttackAction extends DHDamageAction { if (this.damage.includeBase) { const baseDamage = this.getParentHitPointDamage(); if (baseDamage) { - if (!this.damage.parts.hitPoints) { - this.damage.parts.hitPoints = baseDamage; + if (!this.damage.main) { + this.damage.main = baseDamage; } else { - for (const type of baseDamage.type) this.damage.parts.hitPoints.type.add(type); + for (const type of baseDamage.type) this.damage.main.type.add(type); - this.damage.parts.hitPoints.value.custom = { + this.damage.main.value.custom = { enabled: true, - formula: `${baseDamage.value.getFormula()} + ${this.damage.parts.hitPoints.value.getFormula()}` + formula: `${baseDamage.value.getFormula()} + ${this.damage.main.value.getFormula()}` }; } } } + if (this.roll.useDefault) { this.roll.trait = this.item.system.attack.roll.trait; this.roll.type = 'attack'; @@ -33,18 +34,18 @@ export default class DHAttackAction extends DHDamageAction { } getParentHitPointDamage() { - return this.item?.system?.attack.damage.parts.hitPoints; + return this.item?.system?.attack.damage.main; } get damageFormula() { - const hitPointsPart = this.damage.parts.hitPoints; + const hitPointsPart = this.damage.main; if (!hitPointsPart) return '0'; return hitPointsPart.value.getFormula(); } get altDamageFormula() { - const hitPointsPart = this.damage.parts.hitPoints; + const hitPointsPart = this.damage.main; if (!hitPointsPart) return '0'; return hitPointsPart.valueAlt.getFormula(); diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index 8789c859..a94d7d61 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -288,7 +288,6 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel hasEffect: this.hasEffect, hasSave: this.hasSave, onSave: this.save?.damageMod, - isDirect: !!this.damage?.direct, selectedMessageMode: game.settings.get('core', 'messageMode'), data: this.getRollData(), evaluate: this.hasRoll, @@ -306,20 +305,20 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel }; if (this.damage) { - config.isDirect = this.damage.direct; + config.isDirect = !!this.damage.main?.direct; - const groupAttackTokens = this.damage.groupAttack + const groupAttackTokens = this.damage.main?.groupAttack ? game.system.api.fields.ActionFields.DamageField.getGroupAttackTokens( this.actor.id, - this.damage.groupAttack + this.damage.main.groupAttack ) : null; config.damageOptions = { - groupAttack: this.damage.groupAttack + groupAttack: this.damage.main?.groupAttack ? { numAttackers: Math.max(groupAttackTokens.length, 1), - range: this.damage.groupAttack + range: this.damage.main.groupAttack } : null }; diff --git a/module/dice/damageRoll.mjs b/module/dice/damageRoll.mjs index 79b9f653..c78a42e9 100644 --- a/module/dice/damageRoll.mjs +++ b/module/dice/damageRoll.mjs @@ -120,12 +120,10 @@ export default class DamageRoll extends DHRoll { const type = this.options.messageType ?? (this.options.hasHealing ? 'healing' : 'damage'); const changeKeys = []; - for (const roll of this.options.roll) { - for (const damageType of roll.damageTypes?.values?.() ?? []) { - changeKeys.push(`system.bonuses.${type}.${damageType}`); - } + for (const damageType of this.options.damageFormula?.damageTypes?.values?.() ?? []) { + changeKeys.push(`system.bonuses.${type}.${damageType}`); } - + const item = this.data.parent?.items?.get(this.options.source.item); if (item) { switch (item.type) {