diff --git a/module/applications/dialogs/damageDialog.mjs b/module/applications/dialogs/damageDialog.mjs index ce613ade..0d98ce24 100644 --- a/module/applications/dialogs/damageDialog.mjs +++ b/module/applications/dialogs/damageDialog.mjs @@ -6,6 +6,7 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application this.roll = roll; this.config = config; + this.originalIsCritical = config.isCritical; this.selectedEffects = this.config.bonusEffects; } @@ -121,6 +122,19 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application } static async submitRoll() { + const { damageFormula, resourceFormulas } = this.roll.constructFormulas({ ...this.config, isCritical: false }); + /* Sideeffect occuring in constructFormulas that sets this.config.isCritical to the false value. Can remove the below if it can be prevented */ + this.config.isCritical = this.originalIsCritical; + + /* If a critical has been forced in the Dialog, save that forced state to the damage roll */ + if (this.config.isCritical && !this.originalIsCritical) { + damageFormula.roll.options.isCritical = true; + for (const formula of resourceFormulas) + formula.roll.options.isCritical = true; + } + + this.config.damageFormula = damageFormula; + this.config.resourceFormulas = resourceFormulas; await this.close({ submitted: true }); } diff --git a/module/applications/dialogs/tagTeamDialog.mjs b/module/applications/dialogs/tagTeamDialog.mjs index 4c6c5339..e0517abe 100644 --- a/module/applications/dialogs/tagTeamDialog.mjs +++ b/module/applications/dialogs/tagTeamDialog.mjs @@ -1,6 +1,7 @@ import { ResourceUpdateMap } from '../../data/action/baseAction.mjs'; import { ChatDamageData } from '../../data/chat-message/chatDamageData.mjs'; import { MemberData } from '../../data/tagTeamData.mjs'; +import DamageRoll from '../../dice/damageRoll.mjs'; import { getCritDamageBonus, shouldUseHopeFearAutomation } from '../../helpers/utils.mjs'; import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs'; import PartySheet from '../sheets/actors/party.mjs'; @@ -184,8 +185,8 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio ? undefined : (selectedRoll?.roll?.isCritical ?? false); - partContext.hintText = await this.getInfoTexts(this.party.system.tagTeam.members); - partContext.joinedRoll = await this.getJoinedRoll({ + partContext.hintText = this.getInfoTexts(this.party.system.tagTeam.members); + partContext.joinedRoll = this.getJoinedRoll({ overrideIsCritical: critSelected, displayVersion: true }); @@ -194,7 +195,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio } if (Object.keys(this.party.system.tagTeam.members).includes(partId)) { - const data = await this.#prepareMemberContext(partId); + const data = this.#prepareMemberContext(partId); partContext.hasDamage |= Boolean(data?.damage); partContext.members[partId] = data; } @@ -202,7 +203,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio return partContext; } - async #prepareMemberContext(partId) { + #prepareMemberContext(partId) { const data = this.party.system.tagTeam.members[partId] ?? {}; const actor = game.actors.get(partId); if (!actor) console.error(`Failed to get actor ${partId}`); @@ -237,6 +238,10 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio const selectedRoll = Object.values(this.party.system.tagTeam.members).find(member => member.selected); const critSelected = !selectedRoll ? undefined : (selectedRoll?.roll?.isCritical ?? false); + const isCritical = critSelected || (critSelected === undefined && data.roll?.isCritical); + if (data.damageRollData.main) { + data.damageRollData.main.options.isCritical = isCritical; + } return { ...data, @@ -248,8 +253,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio rollOptions, damageRollOptions, damage: data.damageRollData, - critDamage: await this.getCriticalDamage(data.damageRollData), - useCritDamage: critSelected || (critSelected === undefined && data.roll?.isCritical) + isCritical }; } @@ -374,7 +378,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio //#endregion //#region Tag Team Roll - async getInfoTexts(members) { + getInfoTexts(members) { let rollsAreFinished = true; let rollIsSelected = false; for (const member of Object.values(members)) { @@ -579,14 +583,14 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio ); } - async getCriticalDamage(origDamage) { + getCriticalDamage(origDamage) { const newDamage = origDamage ? ChatDamageData.fromJSON(JSON.stringify(origDamage)) : null; if (newDamage?.main) { - const criticalDamage = await getCritDamageBonus(newDamage.main.formula); + const criticalDamage = getCritDamageBonus(newDamage.main.terms); if (criticalDamage) { const criticalTerm = new foundry.dice.terms.NumericTerm({ number: criticalDamage, evaluated: true }); criticalTerm.evaluate(); - newDamage.main = await Roll.fromTerms([ + newDamage.main = Roll.fromTerms([ ...origDamage.main.terms, new foundry.dice.terms.OperatorTerm({ operator: '+' }), criticalTerm @@ -620,7 +624,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio ); } - async getJoinedRoll({ overrideIsCritical, displayVersion } = {}) { + getJoinedRoll({ overrideIsCritical, displayVersion } = {}) { try { const memberValues = Object.values(this.party.system.tagTeam.members); const selectedRoll = memberValues.find(x => x.selected); @@ -640,17 +644,13 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio secondaryRoll.damageRollData = baseSecondaryRoll.damageRollData ? ChatDamageData.fromJSON(JSON.stringify(baseSecondaryRoll.damageRollData)) : null; - const isCritical = overrideIsCritical ?? mainRoll.roll.isCritical; - if (isCritical) mainRoll.damageRollData = await this.getCriticalDamage(mainRoll.damageRollData); - if (secondaryRoll.damageRollData) { - const secondaryDamage = (displayVersion ? overrideIsCritical : isCritical) - ? await this.getCriticalDamage(secondaryRoll.damageRollData) - : secondaryRoll.damageRollData; + const secondaryDamage = secondaryRoll.damageRollData; + if (mainRoll.damageRollData) { if (secondaryDamage.main) { if (mainRoll.damageRollData.main) { - mainRoll.damageRollData.main = Roll.fromTerms([ + mainRoll.damageRollData.main = DamageRoll.fromTerms([ ...baseMainRoll.damageRollData.main.terms, new foundry.dice.terms.OperatorTerm({ operator: '+' }), ...baseSecondaryRoll.damageRollData.main.terms @@ -672,7 +672,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio for (const [key, damage] of Object.entries(secondaryDamage.resources ?? {})) { if (key in mainRoll.damageRollData.resources) { - mainRoll.damageRollData.resources[key] = Roll.fromTerms([ + mainRoll.damageRollData.resources[key] = DamageRoll.fromTerms([ ...baseMainRoll.damageRollData.resources[key].terms, new foundry.dice.terms.OperatorTerm({ operator: '+' }), ...baseSecondaryRoll.damageRollData.resources[key].terms @@ -696,6 +696,12 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio } } + if (mainRoll.damageRollData.main) { + const isCritical = overrideIsCritical ?? mainRoll.roll.isCritical; + mainRoll.damageRollData.main.options.isCritical = isCritical; + } + + return mainRoll; } catch (err) { console.error(err); @@ -741,7 +747,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio const error = this.checkInitiatorHopeError(this.party.system.tagTeam.initiator); if (error) return error; - const joinedRoll = await this.getJoinedRoll(); + const joinedRoll = this.getJoinedRoll(); const mainRoll = joinedRoll.rollData; const finalRoll = foundry.utils.deepClone(joinedRoll.roll); @@ -755,7 +761,11 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio }; if (joinedRoll.damageRollData.main) { - systemData.damage.main = joinedRoll.damageRollData.toJSON(); + systemData.damage.main = joinedRoll.damageRollData.main.toJSON(); + // isCritical is used internally in TagTeamDialog to force-flip damage from normal to critical and vice versa. + // It's deleted here to avoid interupting normal critical damage logic in the chatMessage. + // If someone explicitly set their own damage roll to be a forced critical, then I think it's fine that isn't transmitted to the final joined roll. + delete systemData.damage.main.options.isCritical; } for (const type of Object.keys(joinedRoll.damageRollData?.resources ?? {})) { systemData.damage.resources[type] = joinedRoll.damageRollData.resources[type].toJSON(); diff --git a/module/data/chat-message/actorRoll.mjs b/module/data/chat-message/actorRoll.mjs index 7071ed1e..f300e88f 100644 --- a/module/data/chat-message/actorRoll.mjs +++ b/module/data/chat-message/actorRoll.mjs @@ -204,7 +204,7 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel { const oldRoll = damageData.parts[0]?.roll; return oldRoll ? JSON.stringify({ ...oldRoll, - class: 'BaseRoll', + class: 'DamageRoll', options: { ...oldRoll.options, damageTypes: damageData.parts[0].damageTypes ?? [] diff --git a/module/data/chat-message/chatDamageData.mjs b/module/data/chat-message/chatDamageData.mjs index 404cda43..da28061f 100644 --- a/module/data/chat-message/chatDamageData.mjs +++ b/module/data/chat-message/chatDamageData.mjs @@ -1,4 +1,6 @@ import { triggerChatRollFx } from '../../helpers/utils.mjs'; +import { MemberData } from '../tagTeamData.mjs'; +import DHActorRoll from './actorRoll.mjs'; export class ChatDamageData extends foundry.abstract.DataModel { constructor(data = {}, options = {}) { @@ -7,6 +9,17 @@ export class ChatDamageData extends foundry.abstract.DataModel { this._prepareRolls(); } + get isCritical() { + if (this.parent && this.parent instanceof MemberData) { + return this.parent.roll.isCritical; + } + if (this.parent && this.parent instanceof DHActorRoll && this.parent.parent) { + return Roll.fromJSON(this.parent.parent._source.rolls[0]).isCritical; + } + + return false; + } + static defineSchema() { const fields = foundry.data.fields; @@ -28,7 +41,15 @@ export class ChatDamageData extends foundry.abstract.DataModel { } _prepareRolls() { - this.main &&= Roll.fromData(this.main); + this.main &&= Roll.fromData({ + ...this.main, + options: { + ...this.main.options, + isCritical: + this.main.options.isCritical === false ? false : (this.main.options.isCritical || this.isCritical) + } + }); + 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 25b3ba97..05d9f664 100644 --- a/module/data/fields/action/damageField.mjs +++ b/module/data/fields/action/damageField.mjs @@ -107,7 +107,7 @@ export default class DamageField extends fields.SchemaField { 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); + configDamage.main.total = Math.ceil(config.damage.main.total * multiplier * takenMultiplier); } damagePromises.push( diff --git a/module/dice/damageRoll.mjs b/module/dice/damageRoll.mjs index 683d14d8..722b05f6 100644 --- a/module/dice/damageRoll.mjs +++ b/module/dice/damageRoll.mjs @@ -1,5 +1,5 @@ import DamageDialog from '../applications/dialogs/damageDialog.mjs'; -import { parseRallyDice, triggerChatRollFx } from '../helpers/utils.mjs'; +import { getCritDamageBonus, parseRallyDice, triggerChatRollFx } from '../helpers/utils.mjs'; import DHRoll from './dhRoll.mjs'; export default class DamageRoll extends DHRoll { @@ -8,7 +8,17 @@ export default class DamageRoll extends DHRoll { } get isCritical() { - return !!this.options.isCritical; + return this.options.isCritical; + } + + get modifierTotal() { + const criticalDamageBonus = this.isCritical ? getCritDamageBonus(this.terms) : 0; + return super.modifierTotal + criticalDamageBonus; + } + + get total() { + const criticalDamageBonus = this.isCritical ? getCritDamageBonus(this.terms) : 0; + return super.total + criticalDamageBonus; } static DefaultDialog = DamageDialog; @@ -23,7 +33,7 @@ export default class DamageRoll extends DHRoll { const evaluateRoll = async roll => { await roll.roll.evaluate(); - roll.roll.options = { damageTypes: roll.damageTypes ? [...roll.damageTypes] : [] }; + roll.roll.options = { ...roll.roll.options, damageTypes: roll.damageTypes ? [...roll.damageTypes] : [] }; return roll.roll; } @@ -31,8 +41,10 @@ export default class DamageRoll extends DHRoll { if (config.damageFormula) { config.damage.main = await evaluateRoll(config.damageFormula); - config.damage.main.options = { damageTypes: - config.damageFormula.damageTypes ? [...config.damageFormula.damageTypes] : [] + config.damage.main.options = { + ...config.damage.main.options, + damageTypes: + config.damageFormula.damageTypes ? [...config.damageFormula.damageTypes] : [] }; } @@ -151,7 +163,7 @@ export default class DamageRoll extends DHRoll { if (!formulaData) return null; this.options.isCritical = config.isCritical; - formulaData.roll = new Roll(Roll.replaceFormulaData(formulaData.formula, config.data)); + formulaData.roll = new this.constructor(Roll.replaceFormulaData(formulaData.formula, config.data)); formulaData.roll.terms = Roll.parse(formulaData.roll.formula, config.data); if (formulaData.extraFormula) { diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index f600eae6..9f59c653 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -495,9 +495,8 @@ export function expireActiveEffects(actor, allowedTypes = null) { actor.deleteEmbeddedDocuments('ActiveEffect', effectsToExpire); } -export async function getCritDamageBonus(formula) { - const critRoll = new Roll(formula); - await critRoll.evaluate(); +export function getCritDamageBonus(terms) { + const critRoll = Roll.fromTerms(terms); return critRoll.dice.reduce((acc, dice) => acc + dice.faces * dice.results.filter(r => r.active).length, 0); } diff --git a/styles/less/dialog/tag-team-dialog/sheet.less b/styles/less/dialog/tag-team-dialog/sheet.less index 82bc0270..3a6778d8 100644 --- a/styles/less/dialog/tag-team-dialog/sheet.less +++ b/styles/less/dialog/tag-team-dialog/sheet.less @@ -252,11 +252,8 @@ width: 100%; text-align: center; - .result-info { - display: flex; - gap: 4px; - align-items: center; - justify-content: center; + .damage-info { + text-decoration: underline; } } } diff --git a/templates/dialogs/tagTeamDialog/parts/tagTeamDamageParts.hbs b/templates/dialogs/tagTeamDialog/parts/tagTeamDamageParts.hbs index e8bec150..4727d7e8 100644 --- a/templates/dialogs/tagTeamDialog/parts/tagTeamDamageParts.hbs +++ b/templates/dialogs/tagTeamDialog/parts/tagTeamDamageParts.hbs @@ -1,5 +1,5 @@ {{#if damage.main}} - {{> damage roll=damage.main label=(localize "DAGGERHEART.GENERAL.damage") memberKey=key}} + {{> damage roll=damage.main label=(localize "DAGGERHEART.GENERAL.damage") memberKey=key isCritical=isCritical}} {{/if}} {{#each damage.resources as |roll key|}} diff --git a/templates/dialogs/tagTeamDialog/result.hbs b/templates/dialogs/tagTeamDialog/result.hbs index 88f579a6..4351823e 100644 --- a/templates/dialogs/tagTeamDialog/result.hbs +++ b/templates/dialogs/tagTeamDialog/result.hbs @@ -6,23 +6,14 @@ {{#if hintText}}