diff --git a/module/applications/dialogs/damageDialog.mjs b/module/applications/dialogs/damageDialog.mjs index 46d3d41f..8b37cf2d 100644 --- a/module/applications/dialogs/damageDialog.mjs +++ b/module/applications/dialogs/damageDialog.mjs @@ -51,7 +51,11 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application const context = await super._prepareContext(_options); context.config = CONFIG.DH; context.title = this.config.title ?? this.title; - context.formula = this.roll.constructFormula(this.config); + + const { damageFormula, resourceFormulas } = this.roll.constructFormulas(this.config); + context.damageFormula = damageFormula; + context.resourceFormulas = resourceFormulas; + context.hasHealing = this.config.hasHealing; context.directDamage = this.config.directDamage; context.selectedMessageMode = this.config.selectedMessageMode; @@ -73,7 +77,8 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application static updateRollConfiguration(_event, _, formData) { const data = foundry.utils.expandObject(formData.object); - foundry.utils.mergeObject(this.config.roll, data.roll); + 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/action/baseAction.mjs b/module/data/action/baseAction.mjs index 04a93d7a..1e914e6f 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 Boolean(Object.keys(this.damage?.parts ?? {}).length) && this.type !== 'healing'; + return this.type !== 'healing' && Boolean(this.damage.main) || Boolean(this.damage.resources.length); } get hasHealing() { - return Boolean(Object.keys(this.damage?.parts ?? {}).length) && this.type === 'healing'; + return this.type === 'healing' && Boolean(this.damage.main) || Boolean(this.damage.resources.length); } get hasSave() { diff --git a/module/data/action/damageAction.mjs b/module/data/action/damageAction.mjs index 51735543..15135e0d 100644 --- a/module/data/action/damageAction.mjs +++ b/module/data/action/damageAction.mjs @@ -8,11 +8,8 @@ export default class DHDamageAction extends DHBaseAction { * @returns Formula string */ getDamageFormula() { - const strings = []; - for (const { value } of this.damage.parts) { - strings.push(Roll.replaceFormulaData(value.getFormula(), this.actor?.getRollData() ?? {})); - } + if (!this.damage.main) return ''; - return strings.join(' + '); + return Roll.replaceFormulaData(this.damage.main.value.getFormula(), this.actor?.getRollData() ?? {}); } } diff --git a/module/data/chat-message/chatDamageData.mjs b/module/data/chat-message/chatDamageData.mjs index bf61c892..c612729e 100644 --- a/module/data/chat-message/chatDamageData.mjs +++ b/module/data/chat-message/chatDamageData.mjs @@ -17,7 +17,7 @@ export class ChatDamageData extends foundry.abstract.DataModel { } get active() { - return Boolean(Object.keys(this.types).length); + return !!this.main || Boolean(Object.keys(this.resources).length); } static #validateRoll(rollJSON) { @@ -28,12 +28,12 @@ export class ChatDamageData extends foundry.abstract.DataModel { } _prepareRolls() { - for (const key of Object.keys(this.types)) { - const type = this.types[key]; - try { - this.types[key] = Roll.fromData(type); - this.types[key].options.modifierTotal = CONFIG.Dice.daggerheart.DHRoll.calculateTotalModifiers(type); - } catch {} + if (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 87f50e4b..58c33997 100644 --- a/module/data/fields/action/damageField.mjs +++ b/module/data/fields/action/damageField.mjs @@ -32,25 +32,23 @@ export default class DamageField extends fields.SchemaField { this.hasRoll && DamageField.getAutomation() === CONFIG.DH.SETTINGS.actionAutomationChoices.never.id && !force - ) + ) { return; + } - let formulas = this.damage.parts.map(p => ({ - formula: DamageField.getFormulaValue.call(this, p, config).getFormula(this.actor), - damageTypes: p.applyTo === 'hitPoints' && !p.type.size ? new Set(['physical']) : p.type, - applyTo: p.applyTo - })); + const damageFormula = this.damage.main ? + DamageField.formatFormulas.call(this, [this.damage.main], config)[0] : null; + const resourceFormulas = DamageField.formatFormulas.call(this, this.damage.resources, config); - if (!formulas.length) return false; - - formulas = DamageField.formatFormulas.call(this, formulas, config); + if (!damageFormula && !resourceFormulas.length) return false; messageId = config.message?._id ?? messageId; const message = game.messages.get(messageId); const damageConfig = { dialog: {}, ...config, - roll: formulas, + damageFormula, + resourceFormulas, data: this.getRollData(), isCritical: Boolean(message?.system.roll?.isCritical) }; @@ -175,11 +173,17 @@ export default class DamageField extends fields.SchemaField { /** * Prepare formulas for Damage Roll * Must be called within Action context or similar. - * @param {object[]} formulas Array of formatted formulas object + * @param {DHResourceData[]} damageData Array of DHResourceData * @param {object} data Action getRollData * @returns */ - static formatFormulas(formulas, data) { + 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, + applyTo: x.applyTo + })); + const formattedFormulas = []; formulas.forEach(formula => { if (isNaN(formula.formula)) @@ -190,6 +194,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 8667d89f..64077e1f 100644 --- a/module/dice/damageRoll.mjs +++ b/module/dice/damageRoll.mjs @@ -13,16 +13,27 @@ export default class DamageRoll extends DHRoll { static DefaultDialog = DamageDialog; + static createRollInstance(config) { + return new this(undefined, config.data, config); + } + /** @inheritdoc */ static async buildEvaluate(roll, config = {}) { - if (config.dialog.configure === false) roll.constructFormula(config); + if (config.dialog.configure === false) roll.constructFormulas(config); - for (const roll of config.roll) { + const evaluateRoll = async roll => { await roll.roll.evaluate(); roll.roll.options = { damageTypes: roll.damageTypes ? [...roll.damageTypes] : [] }; + return roll.roll; + } - if (!config.damage?.types) config.damage = { types: {} }; - config.damage.types[roll.applyTo] = roll.roll; + 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); } roll._evaluated = true; @@ -51,7 +62,8 @@ export default class DamageRoll extends DHRoll { if (config.source?.message) { chatMessage.update({ 'system.damage': { ...config.damage.toObject(), - types: config.damage.types + main: config.damage.main, + resources: config.damage.resources }}); } } @@ -125,62 +137,72 @@ export default class DamageRoll extends DHRoll { return changeKeys; } - constructFormula(config) { + constructFormulas(config) { + return { + damageFormula: this.constructFormula(this.options.damageFormula, config, true), + resourceFormulas: this.options.resourceFormulas.map(x => this.constructFormula(x, config)) + }; + } + + constructFormula(formulaData, config, isDamage) { this.options.isCritical = config.isCritical; - for (const [index, part] of this.options.roll.entries()) { - const isHitpointPart = part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id; - part.roll = new Roll(Roll.replaceFormulaData(part.formula, config.data)); - 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)); - }); - } - /* To Remove When Reaction System */ - if (index === 0 && part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) { - for (const mod in config.modifiers) { - const modifier = config.modifiers[mod]; - if (modifier.beforeCrit === true && (modifier.enabled || modifier.value)) modifier.callback(part); - } - } - - if (part.extraFormula) { - part.roll.terms.push( - new foundry.dice.terms.OperatorTerm({ operator: '+' }), - ...this.constructor.parse(part.extraFormula, this.options.data) - ); - } - - if (config.damageOptions.groupAttack?.numAttackers > 1 && isHitpointPart) { - const damageTypes = [foundry.dice.terms.Die, foundry.dice.terms.NumericTerm]; - for (const term of part.roll.terms) { - if (damageTypes.some(type => term instanceof type)) { - term.number *= config.damageOptions.groupAttack.numAttackers; - } - } - } - - if (config.isCritical && isHitpointPart) { - const total = part.roll.dice.reduce((acc, term) => acc + term._faces * term._number, 0); - if (total > 0) { - part.roll.terms.push(...this.formatModifier(total)); - } - } - - /* To Remove When Reaction System */ - if (index === 0 && part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) { - for (const mod in config.modifiers) { - const modifier = config.modifiers[mod]; - if (!modifier.beforeCrit && (modifier.enabled || modifier.value)) modifier.callback(part); - } - } - - part.roll._formula = this.constructor.getFormula(part.roll.terms); + const isHitpointPart = formulaData.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id; + formulaData.roll = new Roll(Roll.replaceFormulaData(formulaData.formula, config.data)); + formulaData.roll.terms = Roll.parse(formulaData.roll.formula, config.data); + if (formulaData.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) { + formulaData.modifiers = this.applyBaseBonus(formulaData); + this.addModifiers(formulaData); + formulaData.modifiers?.forEach(m => { + formulaData.roll.terms.push(...this.formatModifier(m.value)); + }); } - return this.options.roll; + + /* To Remove When Reaction System */ + if (isDamage && formulaData.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) { + for (const mod in config.modifiers) { + const modifier = config.modifiers[mod]; + if ( + modifier.beforeCrit === true && + (modifier.enabled || modifier.value) + ) modifier.callback(formulaData); + } + } + + if (formulaData.extraFormula) { + formulaData.roll.terms.push( + new foundry.dice.terms.OperatorTerm({ operator: '+' }), + ...this.constructor.parse(formulaData.extraFormula, this.options.data) + ); + } + + if (config.damageOptions.groupAttack?.numAttackers > 1 && isHitpointPart) { + const damageTypes = [foundry.dice.terms.Die, foundry.dice.terms.NumericTerm]; + for (const term of formulaData.roll.terms) { + if (damageTypes.some(type => term instanceof type)) { + term.number *= config.damageOptions.groupAttack.numAttackers; + } + } + } + + if (config.isCritical && isHitpointPart) { + const total = formulaData.roll.dice.reduce((acc, term) => acc + term._faces * term._number, 0); + if (total > 0) { + formulaData.roll.terms.push(...this.formatModifier(total)); + } + } + + /* To Remove When Reaction System */ + if (isDamage && formulaData.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) { + for (const mod in config.modifiers) { + const modifier = config.modifiers[mod]; + if (!modifier.beforeCrit && (modifier.enabled || modifier.value)) modifier.callback(formulaData); + } + } + + formulaData.roll._formula = this.constructor.getFormula(formulaData.roll.terms); + + return formulaData; } /* To Remove When Reaction System */ diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs index 13408329..c78caa4f 100644 --- a/module/dice/dhRoll.mjs +++ b/module/dice/dhRoll.mjs @@ -41,6 +41,10 @@ export default class DHRoll extends BaseRoll { return config; } + static createRollInstance(config) { + return new this(config.roll.formula, config.data, config); + } + /** * @param {Partial} config * @returns {Promise} @@ -58,7 +62,7 @@ export default class DHRoll extends BaseRoll { this.temporaryModifierBuilder(config); - let roll = new this(config.roll.formula, config.data, config); + let roll = this.createRollInstance(config); if (config.dialog.configure !== false) { // Open Roll Dialog const DialogClass = config.dialog?.class ?? this.DefaultDialog; diff --git a/templates/dialogs/dice-roll/damageSelection.hbs b/templates/dialogs/dice-roll/damageSelection.hbs index 7bcd7063..cc12e26f 100644 --- a/templates/dialogs/dice-roll/damageSelection.hbs +++ b/templates/dialogs/dice-roll/damageSelection.hbs @@ -16,31 +16,10 @@ {{/if}} - {{#each @root.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}} - -
-
- - -
+ {{> formula @root.damageFormula path="damageFormula"}} + + {{#each @root.resourceFormulas}} + {{> formula path=(concat "resourceFormulas." @key)}} {{/each}} {{#if damageOptions.groupAttack}} @@ -87,4 +66,31 @@ {{localize "DAGGERHEART.GENERAL.roll"}} - \ No newline at end of file + + +{{#*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