From a7d2916e9389076ccc5a06bd2c64d288f08a3a8e Mon Sep 17 00:00:00 2001 From: Dapoolp Date: Fri, 18 Jul 2025 02:29:31 +0200 Subject: [PATCH] Damages parts roll --- module/applications/dialogs/damageDialog.mjs | 4 +- module/data/action/damageAction.mjs | 42 ++++++++-- module/dice/damageRoll.mjs | 76 ++++++++++++++++--- module/dice/dhRoll.mjs | 23 +++--- module/helpers/utils.mjs | 12 +++ templates/actionTypes/damage.hbs | 16 +++- .../dialogs/dice-roll/damageSelection.hbs | 10 ++- 7 files changed, 145 insertions(+), 38 deletions(-) diff --git a/module/applications/dialogs/damageDialog.mjs b/module/applications/dialogs/damageDialog.mjs index 70dcace8..841de805 100644 --- a/module/applications/dialogs/damageDialog.mjs +++ b/module/applications/dialogs/damageDialog.mjs @@ -46,7 +46,7 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application context.title = this.config.title ? this.config.title : game.i18n.localize('DAGGERHEART.EFFECTS.ApplyLocations.damageRoll.name'); - context.extraFormula = this.config.extraFormula; + // context.extraFormula = this.config.extraFormula; context.formula = this.roll.constructFormula(this.config); context.directDamage = this.config.directDamage; context.selectedRollMode = this.config.selectedRollMode; @@ -61,7 +61,7 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application static updateRollConfiguration(_event, _, formData) { const { ...rest } = foundry.utils.expandObject(formData.object); - this.config.extraFormula = rest.extraFormula; + foundry.utils.mergeObject(this.config.roll, rest.roll) this.config.selectedRollMode = rest.selectedRollMode; this.render(); diff --git a/module/data/action/damageAction.mjs b/module/data/action/damageAction.mjs index 5ee12c68..099ec283 100644 --- a/module/data/action/damageAction.mjs +++ b/module/data/action/damageAction.mjs @@ -1,3 +1,4 @@ +import { setsEqual } from '../../helpers/utils.mjs'; import DHBaseAction from './baseAction.mjs'; export default class DHDamageAction extends DHBaseAction { @@ -18,27 +19,53 @@ export default class DHDamageAction extends DHBaseAction { return formulaValue; } + formatFormulas(formulas, systemData) { + const formattedFormulas = []; + formulas.forEach(formula => { + if (isNaN(formula.formula)) formula.formula = Roll.replaceFormulaData(formula.formula, this.getRollData(systemData)); + const same = formattedFormulas.find(f => setsEqual(f.damageTypes, formula.damageTypes) && f.applyTo === formula.applyTo); + if(same) + same.formula += ` + ${formula.formula}`; + else + formattedFormulas.push(formula); + }) + return formattedFormulas; + } + async rollDamage(event, data) { const systemData = data.system ?? data; - let formula = this.damage.parts.map(p => this.getFormulaValue(p, data).getFormula(this.actor)).join(' + '), + /* let formula = this.damage.parts.map(p => this.getFormulaValue(p, data).getFormula(this.actor)).join(' + '), damageTypes = [...new Set(this.damage.parts.reduce((a, c) => a.concat([...c.type]), []))]; damageTypes = !damageTypes.length ? ['physical'] : damageTypes; + + if (!formula || formula == '') return; */ + + let formulas = this.damage.parts.map(p => ({ + formula: this.getFormulaValue(p, data).getFormula(this.actor), + damageTypes: p.type, + applyTo: p.applyTo + })); - if (!formula || formula == '') return; - let roll = { formula: formula, total: formula }; + if(!formulas.length) return; - if (isNaN(formula)) formula = Roll.replaceFormulaData(formula, this.getRollData(systemData)); + formulas = this.formatFormulas(formulas, systemData); + + /* let roll = { formula: formula, total: formula }; + + if (isNaN(formula)) formula = Roll.replaceFormulaData(formula, this.getRollData(systemData)); */ const config = { title: game.i18n.format('DAGGERHEART.UI.Chat.damageRoll.title', { damage: this.name }), - roll: { formula }, + // roll: { formula }, + // roll: { formulas }, + roll: formulas, targets: systemData.targets.filter(t => t.hit) ?? data.targets, hasSave: this.hasSave, isCritical: systemData.roll?.isCritical ?? false, source: systemData.source, data: this.getRollData(), - damageTypes, + // damageTypes, event }; if (this.hasSave) config.onSave = this.save.damageMod; @@ -49,6 +76,7 @@ export default class DHDamageAction extends DHBaseAction { config.directDamage = true; } - roll = CONFIG.Dice.daggerheart.DamageRoll.build(config); + // roll = CONFIG.Dice.daggerheart.DamageRoll.build(config); + CONFIG.Dice.daggerheart.DamageRoll.build(config); } } diff --git a/module/dice/damageRoll.mjs b/module/dice/damageRoll.mjs index bfbfc7d5..b9ce18ce 100644 --- a/module/dice/damageRoll.mjs +++ b/module/dice/damageRoll.mjs @@ -24,12 +24,13 @@ export default class DamageRoll extends DHRoll { } } - applyBaseBonus() { + applyBaseBonus(part) { const modifiers = [], - type = this.options.messageType ?? 'damage'; + type = this.options.messageType ?? 'damage', + options = part ?? this.options; modifiers.push(...this.getBonus(`${type}`, `${type.capitalize()} Bonus`)); - this.options.damageTypes?.forEach(t => { + options.damageTypes?.forEach(t => { modifiers.push(...this.getBonus(`${type}.${t}`, `${t.capitalize()} ${type.capitalize()} Bonus`)); }); const weapons = ['primaryWeapon', 'secondaryWeapon']; @@ -42,13 +43,70 @@ export default class DamageRoll extends DHRoll { } constructFormula(config) { - super.constructFormula(config); + this.options.roll.forEach( part => { + part.roll = new Roll(part.formula); + this.constructFormulaPart(config, part) + }) + return this.options.roll; + } - if (config.isCritical) { - const tmpRoll = new Roll(this._formula)._evaluateSync({ maximize: true }), - criticalBonus = tmpRoll.total - this.constructor.calculateTotalModifiers(tmpRoll); - this.terms.push(...this.formatModifier(criticalBonus)); + constructFormulaPart(config, part) { + part.roll.terms = Roll.parse(part.roll.formula, config.data); + + if(part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) { + part.modifiers = this.applyBaseBonus(part); + this.addModifiers(part); + part.modifiers?.forEach(m => { + part.roll.terms.push(...this.formatModifier(m.value)); + }); } - return (this._formula = this.constructor.getFormula(this.terms)); + + if (part.extraFormula) { + part.roll.terms.push( + new foundry.dice.terms.OperatorTerm({ operator: '+' }), + ...this.constructor.parse(part.extraFormula, this.options.data) + ); + } + + if (config.isCritical && part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) { + const tmpRoll = Roll.fromTerms(part.roll.terms)._evaluateSync({ maximize: true }), + criticalBonus = tmpRoll.total - this.constructor.calculateTotalModifiers(tmpRoll); + part.roll.terms.push(...this.formatModifier(criticalBonus)); + } + return (part.roll._formula = this.constructor.getFormula(part.roll.terms)); + } + + async evaluate({minimize=false, maximize=false, allowStrings=false, allowInteractive=true, ...options}={}) { + if ( this._evaluated ) { + throw new Error(`The ${this.constructor.name} has already been evaluated and is now immutable`); + } + this._evaluated = true; + if ( CONFIG.debug.dice ) console.debug(`Evaluating roll with formula "${this.formula}"`); + + // Migration path for async rolls + if ( "async" in options ) { + foundry.utils.logCompatibilityWarning("The async option for Roll#evaluate has been removed. " + + "Use Roll#evaluateSync for synchronous roll evaluation."); + } + + this.options.roll.forEach( async part => { + await part.roll.evaluate({minimize, maximize, allowStrings, allowInteractive, ...options}) + }) + // return this._evaluate({minimize, maximize, allowStrings, allowInteractive}); + } + + static postEvaluate(roll, config = {}) { + if (!config.roll) config.roll = {}; + config.roll.total = roll.total; + config.roll.formula = roll.formula; + config.roll.dice = []; + roll.dice.forEach(d => { + config.roll.dice.push({ + dice: d.denomination, + total: d.total, + formula: d.formula, + results: d.results + }); + }); } } diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs index 33de251b..3590baf3 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(); - this.postEvaluate(roll, config); + config.roll = this.postEvaluate(roll); } static async buildPost(roll, config, message) { @@ -63,19 +63,17 @@ export default class DHRoll extends Roll { } } - static postEvaluate(roll, config = {}) { - if (!config.roll) config.roll = {}; - config.roll.total = roll.total; - config.roll.formula = roll.formula; - config.roll.dice = []; - roll.dice.forEach(d => { - config.roll.dice.push({ + static postEvaluate(roll) { + return { + total: roll.total, + formula: roll.formula, + dice: roll.dice.map(d => ({ dice: d.denomination, total: d.total, formula: d.formula, results: d.results - }); - }); + })) + } } static async toMessage(roll, config) { @@ -118,8 +116,9 @@ export default class DHRoll extends Roll { return []; } - addModifiers() { - this.options.roll.modifiers?.forEach(m => { + addModifiers(roll) { + roll = roll ?? this.options.roll; + roll.modifiers?.forEach(m => { this.terms.push(...this.formatModifier(m.value)); }); } diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index 336ecf5b..91671e36 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -311,3 +311,15 @@ export const itemAbleRollParse = (value, actor, item) => { return ''; } }; + +export const arraysEqual = (a, b) => + a.length === b.length && + [...new Set([...a, ...b])].every( + v => a.filter(e => e === v).length === b.filter(e => e === v).length + ); + +export const setsEqual = (a, b) => + a.size === b.size && + [...a].every( + value => b.has(value) + ); \ No newline at end of file diff --git a/templates/actionTypes/damage.hbs b/templates/actionTypes/damage.hbs index 4e707c8a..bfbbc592 100644 --- a/templates/actionTypes/damage.hbs +++ b/templates/actionTypes/damage.hbs @@ -22,8 +22,12 @@ {{formField ../fields.value.fields.bonus value=dmg.value.bonus name=(concat ../path "damage.parts." index ".value.bonus") localize=true classes="inline-child"}} {{/if}} - {{formField ../fields.applyTo value=dmg.applyTo name=(concat ../path "damage.parts." realIndex ".applyTo") localize=true}} - {{formField ../fields.type value=dmg.type name=(concat ../path "damage.parts." index ".type") localize=true}} +
+ {{formField ../fields.applyTo value=dmg.applyTo name=(concat ../path "damage.parts." realIndex ".applyTo") localize=true}} + {{#if (eq dmg.applyTo 'hitPoints')}} + {{formField ../fields.type value=dmg.type name=(concat ../path "damage.parts." index ".type") localize=true}} + {{/if}} +
{{#if ../horde}}
{{localize "DAGGERHEART.ACTORS.Adversary.hordeDamage"}} @@ -57,8 +61,12 @@ {{> formula fields=../../fields.value.fields type=../fields.type dmg=dmg source=dmg.value target="value" realIndex=realIndex}}
{{/if}} - {{formField ../../fields.applyTo value=dmg.applyTo name=(concat "damage.parts." realIndex ".applyTo") localize=true}} - {{formField ../../fields.type value=dmg.type name=(concat "damage.parts." realIndex ".type") localize=true}} +
+ {{formField ../../fields.applyTo value=dmg.applyTo name=(concat "damage.parts." realIndex ".applyTo") localize=true}} + {{#if (eq dmg.applyTo 'hitPoints')}} + {{formField ../../fields.type value=dmg.type name=(concat "damage.parts." realIndex ".type") localize=true}} + {{/if}} +
{{#unless dmg.base}}
{{/unless}} diff --git a/templates/dialogs/dice-roll/damageSelection.hbs b/templates/dialogs/dice-roll/damageSelection.hbs index bd97cfdf..fee74582 100644 --- a/templates/dialogs/dice-roll/damageSelection.hbs +++ b/templates/dialogs/dice-roll/damageSelection.hbs @@ -2,10 +2,12 @@

{{title}}

- Formula: {{@root.formula}} -
- -
+ {{#each @root.formula}} + Formula: {{roll.formula}} +
+ +
+ {{/each}}
{{#if directDamage}}