diff --git a/module/applications/ui/chatLog.mjs b/module/applications/ui/chatLog.mjs index 199ee87d..d17ddbb5 100644 --- a/module/applications/ui/chatLog.mjs +++ b/module/applications/ui/chatLog.mjs @@ -183,13 +183,8 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo total = message.rolls.reduce((a, c) => a + Roll.fromJSON(c).total, 0), damages = { hitPoints: { - parts: [ - { - applyTo: 'hitPoints', - damageTypes: [], - total - } - ] + damageTypes: [], + roll: { total } } }, targets = Array.from(game.user.targets); @@ -259,23 +254,14 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo const target = event.target.closest('[data-die-index]'); if (target.dataset.type === 'damage') { - const { damageType, part, dice, result } = target.dataset; - const damagePart = message.system.damage[damageType].parts[part]; - const { parsedRoll, rerolledDice } = await game.system.api.dice.DamageRoll.reroll(damagePart, dice, result); - const damageParts = message.system.damage[damageType].parts.map((damagePart, index) => { - if (index !== Number(part)) return damagePart; - return { - ...damagePart, - total: parsedRoll.total, - dice: rerolledDice - }; - }); - const updateMessage = game.messages.get(message._id); - await updateMessage.update({ - [`system.damage.${damageType}`]: { - total: parsedRoll.total, - parts: damageParts - } + const { damageType, dice } = target.dataset; + await message.system.damage.rerollDamageDice(damageType, dice); + await message.update({ + 'system.damage.types': { + [damageType]: { + roll: message.system.damage.types[damageType].roll.toJSON() + } + } }); } else { const rerollDice = message.system.roll.dice[target.dataset.dieIndex]; diff --git a/module/data/chat-message/actorRoll.mjs b/module/data/chat-message/actorRoll.mjs index ccfe25ea..cb14b31c 100644 --- a/module/data/chat-message/actorRoll.mjs +++ b/module/data/chat-message/actorRoll.mjs @@ -29,6 +29,60 @@ export const originItemField = () => actionIndex: new fields.StringField() }); +class ChatMessageRollDamage extends foundry.abstract.DataModel { + static defineSchema() { + return { + types: new fields.TypedObjectField(new fields.SchemaField({ + roll: new fields.JSONField({validate: ChatMessageRollDamage.#validateRoll}), + damageTypes: new fields.ArrayField(new fields.StringField({ choices: CONFIG.DH.GENERAL.damageTypes })), + type: new fields.StringField() + })) + }; + } + + get active() { + return Boolean(Object.keys(this.types).length); + } + + static #validateRoll(rollJSON) { + const roll = JSON.parse(rollJSON); + if (!roll.evaluated) throw new Error('Roll objects added to ChatMessage documents must be evaluated'); + } + + prepareRolls() { + for (const key of Object.keys(this.types)) { + const type = this.types[key]; + try { + const roll = Roll.fromData(type.roll); + type.roll = roll; + type.roll.modifierTotal = CONFIG.Dice.daggerheart.DHRoll.calculateTotalModifiers(roll); + } catch {} + } + } + + async rerollDamageDice(damageType, dice) { + const reroll = this.types[damageType].roll; + const rerollDice = reroll.dice[dice]; + await rerollDice.reroll(`/r1=${rerollDice.total}`); + await reroll._evaluate(); + + const result = rerollDice.results.find(x => x.active); + if (result) { + const fakeRoll = { + _evaluated: true, + dice: [new foundry.dice.terms.Die({ + ...rerollDice, + results: [result], + total: result.value, + faces: rerollDice.faces + })], + options: { appearance: {} } + }; + await triggerChatRollFx([fakeRoll]); + } + } +} + export default class DHActorRoll extends foundry.abstract.TypeDataModel { static defineSchema() { return { @@ -49,7 +103,7 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel { originItem: originItemField(), action: new fields.StringField() }), - damage: new fields.ObjectField(), + damage: new fields.EmbeddedDataField(ChatMessageRollDamage), damageOptions: new fields.ObjectField(), costs: new fields.ArrayField(new fields.ObjectField()), successConsumed: new fields.BooleanField({ initial: false }) @@ -134,28 +188,36 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel { /* TODO: Change how damage data is stored somehow to enable better rerolling */ async getRerolledDamage() { - if (!this.damage) return; + if (!this.damage.active) return; const rerolls = []; - const update = { system: { damage: {} } }; - for (const partKey in this.damage) { - const part = this.damage[partKey]; - const testRoll = Roll.fromData(part.parts[0].roll); - const rerolled = await testRoll.reroll(); - rerolls.push(rerolled); - - if (!update.system.damage[partKey]) update.system.damage[partKey] = { parts: [part.parts[0]] }; - const partData = update.system.damage[partKey].parts[0]; - update.system.damage[partKey].total = rerolled.total; - partData.modifierTotal = rerolled.terms.reduce((acc, x) => { - if (x.isDeterministic && !x.operator) acc += x.total; - return acc; - }, 0); - partData.dice = rerolled.dice.map(d => ({ ...d.toJSON(), dice: d.denomination })); - partData.total = rerolled.total; - partData.roll = rerolled.toJSON(); + const update = { system: { damage: { types: {} } } }; + for (const key of Object.keys(this.damage.types)) { + const type = this.damage.types[key]; + const reroll = await type.roll.reroll(); + rerolls.push(reroll); + update.system.damage.types[key] = { roll: reroll.toJSON() }; } + // const update = { system: { damage: {} } }; + // for (const partKey in this.damage) { + // const part = this.damage[partKey]; + // const testRoll = Roll.fromData(part.parts[0].roll); + // const rerolled = await testRoll.reroll(); + // rerolls.push(rerolled); + + // if (!update.system.damage[partKey]) update.system.damage[partKey] = { parts: [part.parts[0]] }; + // const partData = update.system.damage[partKey].parts[0]; + // update.system.damage[partKey].total = rerolled.total; + // partData.modifierTotal = rerolled.terms.reduce((acc, x) => { + // if (x.isDeterministic && !x.operator) acc += x.total; + // return acc; + // }, 0); + // partData.dice = rerolled.dice.map(d => ({ ...d.toJSON(), dice: d.denomination })); + // partData.total = rerolled.total; + // partData.roll = rerolled.toJSON(); + // } + await triggerChatRollFx(rerolls); return update; @@ -175,6 +237,8 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel { } prepareDerivedData() { + this.damage.prepareRolls(); + if (this.hasTarget) { this.hasHitTarget = this.targets.filter(t => t.hit === true).length > 0; this.currentTargets = this.getTargetList(); diff --git a/module/data/fields/action/damageField.mjs b/module/data/fields/action/damageField.mjs index 9b21d3ba..6efa1f51 100644 --- a/module/data/fields/action/damageField.mjs +++ b/module/data/fields/action/damageField.mjs @@ -111,16 +111,17 @@ export default class DamageField extends fields.SchemaField { : actor.prototypeToken; if (config.hasHealing) damagePromises.push( - actor.takeHealing(config.damage).then(updates => targetDamage.push({ token, updates })) + actor.takeHealing(config.damage.types).then(updates => targetDamage.push({ token, updates })) ); else { - const configDamage = foundry.utils.deepClone(config.damage); + 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) { - for (const part of configDamage.hitPoints.parts) { - part.total = Math.ceil(part.total * hpDamageMultiplier * hpDamageTakenMultiplier); - } + configDamage.hitPoints.roll = configDamage.hitPoints.roll.toJSON(); + configDamage.hitPoints.roll.total = Math.ceil( + configDamage.hitPoints.roll.total * hpDamageMultiplier * hpDamageTakenMultiplier + ); } damagePromises.push( diff --git a/module/dice/damageRoll.mjs b/module/dice/damageRoll.mjs index 3f2f79e0..f080c653 100644 --- a/module/dice/damageRoll.mjs +++ b/module/dice/damageRoll.mjs @@ -14,35 +14,17 @@ export default class DamageRoll extends DHRoll { static DefaultDialog = DamageDialog; /** @inheritdoc */ - static async buildEvaluate(roll, config = {}, message = {}) { + static async buildEvaluate(roll, config = {}) { if (config.dialog.configure === false) roll.constructFormula(config); - for (const roll of config.roll) await roll.roll.evaluate(); - - roll._evaluated = true; - - const parts = []; - for (const rollData of config.roll) { - const roll = rollData.roll; - parts.push({ - ...rollData, - ...roll.options.roll, - total: roll.total, - formula: roll.formula, - dice: roll.dice.map(d => ({ - dice: d.denomination, - total: d.total, - formula: d.formula, - results: d.results - })), - damageTypes: [...(rollData.damageTypes ?? [])], - roll, - type: config.type, - modifierTotal: this.calculateTotalModifiers(roll) - }); - rollData.roll = JSON.stringify(roll.toJSON()); + + for (const roll of config.roll) { + await roll.roll.evaluate(); + config.damage.types[roll.applyTo] = { + roll: roll.roll, + damageTypes: roll.damageTypes ?? [], + type: config.type + }; } - - config.damage = this.unifyDamageRoll(parts); } static async buildPost(roll, config, message) { @@ -54,7 +36,7 @@ export default class DamageRoll extends DHRoll { if (game.modules.get('dice-so-nice')?.active) { config.mute = true; const pool = foundry.dice.terms.PoolTerm.fromRolls( - Object.values(config.damage).flatMap(r => r.parts.map(p => p.roll)) + Object.values(config.damage.types).map(x => x.roll) ); diceRolls.push(Roll.fromTerms([pool])); } @@ -66,22 +48,13 @@ export default class DamageRoll extends DHRoll { await super.buildPost(roll, config, message); if (config.source?.message) { - chatMessage.update({ 'system.damage': config.damage }); + chatMessage.update({ 'system.damage': { + ...config.damage.toObject(), + types: config.damage.types + }}); } } - 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)); diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index 38d9315f..6503efb1 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -409,8 +409,4 @@ export default class DualityRoll extends D20Roll { return rerolled; } - - fromJSON(json) { - return super.fromJSON(json); - } } diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index f8880597..b85c13d5 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -659,15 +659,13 @@ export default class DhpActor extends Actor { const updates = []; Object.entries(damages).forEach(([key, 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.key === key); - if (update) { - update.value += part.total; - update.damageTypes.add(...new Set(part.damageTypes)); - } else updates.push({ value: part.total, key, damageTypes: new Set(part.damageTypes) }); - }); + if (key === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) + damage.roll.total = this.calculateDamage(damage.roll.total, damage.damageTypes); + const update = updates.find(u => u.key === key); + if (update) { + update.value += damage.roll.total; + update.damageTypes.add(...new Set(damage.damageTypes)); + } else updates.push({ value: damage.roll.total, key, damageTypes: new Set(damage.damageTypes) }); }); if (Hooks.call(`${CONFIG.DH.id}.postCalculateDamage`, this, damages) === false) return null; @@ -772,11 +770,9 @@ export default class DhpActor extends Actor { const updates = []; Object.entries(healings).forEach(([key, healing]) => { - healing.parts.forEach(part => { - const update = updates.find(u => u.key === key); - if (update) update.value += part.total; - else updates.push({ value: part.total, key }); - }); + 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( diff --git a/templates/ui/chat/parts/button-part.hbs b/templates/ui/chat/parts/button-part.hbs index 6bc5f372..a0075b5f 100644 --- a/templates/ui/chat/parts/button-part.hbs +++ b/templates/ui/chat/parts/button-part.hbs @@ -1,18 +1,18 @@
\ No newline at end of file diff --git a/templates/ui/chat/parts/damage-part.hbs b/templates/ui/chat/parts/damage-part.hbs index 45b09b72..cd4085e0 100644 --- a/templates/ui/chat/parts/damage-part.hbs +++ b/templates/ui/chat/parts/damage-part.hbs @@ -1,4 +1,4 @@ -