This commit is contained in:
WBHarry 2026-07-21 09:49:51 +02:00 committed by GitHub
commit a1f40ead36
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 104 additions and 60 deletions

View file

@ -6,6 +6,7 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
this.roll = roll; this.roll = roll;
this.config = config; this.config = config;
this.originalIsCritical = config.isCritical;
this.selectedEffects = this.config.bonusEffects; this.selectedEffects = this.config.bonusEffects;
} }
@ -121,6 +122,19 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
} }
static async submitRoll() { 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 }); await this.close({ submitted: true });
} }

View file

@ -1,6 +1,7 @@
import { ResourceUpdateMap } from '../../data/action/baseAction.mjs'; import { ResourceUpdateMap } from '../../data/action/baseAction.mjs';
import { ChatDamageData } from '../../data/chat-message/chatDamageData.mjs'; import { ChatDamageData } from '../../data/chat-message/chatDamageData.mjs';
import { MemberData } from '../../data/tagTeamData.mjs'; import { MemberData } from '../../data/tagTeamData.mjs';
import DamageRoll from '../../dice/damageRoll.mjs';
import { getCritDamageBonus, shouldUseHopeFearAutomation } from '../../helpers/utils.mjs'; import { getCritDamageBonus, shouldUseHopeFearAutomation } from '../../helpers/utils.mjs';
import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs'; import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
import PartySheet from '../sheets/actors/party.mjs'; import PartySheet from '../sheets/actors/party.mjs';
@ -184,8 +185,8 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
? undefined ? undefined
: (selectedRoll?.roll?.isCritical ?? false); : (selectedRoll?.roll?.isCritical ?? false);
partContext.hintText = await this.getInfoTexts(this.party.system.tagTeam.members); partContext.hintText = this.getInfoTexts(this.party.system.tagTeam.members);
partContext.joinedRoll = await this.getJoinedRoll({ partContext.joinedRoll = this.getJoinedRoll({
overrideIsCritical: critSelected, overrideIsCritical: critSelected,
displayVersion: true displayVersion: true
}); });
@ -194,7 +195,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
} }
if (Object.keys(this.party.system.tagTeam.members).includes(partId)) { 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.hasDamage |= Boolean(data?.damage);
partContext.members[partId] = data; partContext.members[partId] = data;
} }
@ -202,7 +203,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
return partContext; return partContext;
} }
async #prepareMemberContext(partId) { #prepareMemberContext(partId) {
const data = this.party.system.tagTeam.members[partId] ?? {}; const data = this.party.system.tagTeam.members[partId] ?? {};
const actor = game.actors.get(partId); const actor = game.actors.get(partId);
if (!actor) console.error(`Failed to get actor ${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 selectedRoll = Object.values(this.party.system.tagTeam.members).find(member => member.selected);
const critSelected = !selectedRoll ? undefined : (selectedRoll?.roll?.isCritical ?? false); 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 { return {
...data, ...data,
@ -248,8 +253,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
rollOptions, rollOptions,
damageRollOptions, damageRollOptions,
damage: data.damageRollData, damage: data.damageRollData,
critDamage: await this.getCriticalDamage(data.damageRollData), isCritical
useCritDamage: critSelected || (critSelected === undefined && data.roll?.isCritical)
}; };
} }
@ -374,7 +378,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
//#endregion //#endregion
//#region Tag Team Roll //#region Tag Team Roll
async getInfoTexts(members) { getInfoTexts(members) {
let rollsAreFinished = true; let rollsAreFinished = true;
let rollIsSelected = false; let rollIsSelected = false;
for (const member of Object.values(members)) { 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; const newDamage = origDamage ? ChatDamageData.fromJSON(JSON.stringify(origDamage)) : null;
if (newDamage?.main) { if (newDamage?.main) {
const criticalDamage = await getCritDamageBonus(newDamage.main.formula); const criticalDamage = getCritDamageBonus(newDamage.main.terms);
if (criticalDamage) { if (criticalDamage) {
const criticalTerm = new foundry.dice.terms.NumericTerm({ number: criticalDamage, evaluated: true }); const criticalTerm = new foundry.dice.terms.NumericTerm({ number: criticalDamage, evaluated: true });
criticalTerm.evaluate(); criticalTerm.evaluate();
newDamage.main = await Roll.fromTerms([ newDamage.main = Roll.fromTerms([
...origDamage.main.terms, ...origDamage.main.terms,
new foundry.dice.terms.OperatorTerm({ operator: '+' }), new foundry.dice.terms.OperatorTerm({ operator: '+' }),
criticalTerm criticalTerm
@ -620,7 +624,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
); );
} }
async getJoinedRoll({ overrideIsCritical, displayVersion } = {}) { getJoinedRoll({ overrideIsCritical, displayVersion } = {}) {
try { try {
const memberValues = Object.values(this.party.system.tagTeam.members); const memberValues = Object.values(this.party.system.tagTeam.members);
const selectedRoll = memberValues.find(x => x.selected); const selectedRoll = memberValues.find(x => x.selected);
@ -640,17 +644,13 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
secondaryRoll.damageRollData = baseSecondaryRoll.damageRollData ? secondaryRoll.damageRollData = baseSecondaryRoll.damageRollData ?
ChatDamageData.fromJSON(JSON.stringify(baseSecondaryRoll.damageRollData)) : null; 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) { if (secondaryRoll.damageRollData) {
const secondaryDamage = (displayVersion ? overrideIsCritical : isCritical) const secondaryDamage = secondaryRoll.damageRollData;
? await this.getCriticalDamage(secondaryRoll.damageRollData)
: secondaryRoll.damageRollData;
if (mainRoll.damageRollData) { if (mainRoll.damageRollData) {
if (secondaryDamage.main) { if (secondaryDamage.main) {
if (mainRoll.damageRollData.main) { if (mainRoll.damageRollData.main) {
mainRoll.damageRollData.main = Roll.fromTerms([ mainRoll.damageRollData.main = DamageRoll.fromTerms([
...baseMainRoll.damageRollData.main.terms, ...baseMainRoll.damageRollData.main.terms,
new foundry.dice.terms.OperatorTerm({ operator: '+' }), new foundry.dice.terms.OperatorTerm({ operator: '+' }),
...baseSecondaryRoll.damageRollData.main.terms ...baseSecondaryRoll.damageRollData.main.terms
@ -672,7 +672,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
for (const [key, damage] of Object.entries(secondaryDamage.resources ?? {})) { for (const [key, damage] of Object.entries(secondaryDamage.resources ?? {})) {
if (key in mainRoll.damageRollData.resources) { if (key in mainRoll.damageRollData.resources) {
mainRoll.damageRollData.resources[key] = Roll.fromTerms([ mainRoll.damageRollData.resources[key] = DamageRoll.fromTerms([
...baseMainRoll.damageRollData.resources[key].terms, ...baseMainRoll.damageRollData.resources[key].terms,
new foundry.dice.terms.OperatorTerm({ operator: '+' }), new foundry.dice.terms.OperatorTerm({ operator: '+' }),
...baseSecondaryRoll.damageRollData.resources[key].terms ...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; return mainRoll;
} catch (err) { } catch (err) {
console.error(err); console.error(err);
@ -741,7 +747,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
const error = this.checkInitiatorHopeError(this.party.system.tagTeam.initiator); const error = this.checkInitiatorHopeError(this.party.system.tagTeam.initiator);
if (error) return error; if (error) return error;
const joinedRoll = await this.getJoinedRoll(); const joinedRoll = this.getJoinedRoll();
const mainRoll = joinedRoll.rollData; const mainRoll = joinedRoll.rollData;
const finalRoll = foundry.utils.deepClone(joinedRoll.roll); const finalRoll = foundry.utils.deepClone(joinedRoll.roll);
@ -755,7 +761,11 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
}; };
if (joinedRoll.damageRollData.main) { 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 ?? {})) { for (const type of Object.keys(joinedRoll.damageRollData?.resources ?? {})) {
systemData.damage.resources[type] = joinedRoll.damageRollData.resources[type].toJSON(); systemData.damage.resources[type] = joinedRoll.damageRollData.resources[type].toJSON();

View file

@ -204,7 +204,7 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
const oldRoll = damageData.parts[0]?.roll; const oldRoll = damageData.parts[0]?.roll;
return oldRoll ? JSON.stringify({ return oldRoll ? JSON.stringify({
...oldRoll, ...oldRoll,
class: 'BaseRoll', class: 'DamageRoll',
options: { options: {
...oldRoll.options, ...oldRoll.options,
damageTypes: damageData.parts[0].damageTypes ?? [] damageTypes: damageData.parts[0].damageTypes ?? []

View file

@ -1,4 +1,6 @@
import { triggerChatRollFx } from '../../helpers/utils.mjs'; import { triggerChatRollFx } from '../../helpers/utils.mjs';
import { MemberData } from '../tagTeamData.mjs';
import DHActorRoll from './actorRoll.mjs';
export class ChatDamageData extends foundry.abstract.DataModel { export class ChatDamageData extends foundry.abstract.DataModel {
constructor(data = {}, options = {}) { constructor(data = {}, options = {}) {
@ -7,6 +9,17 @@ export class ChatDamageData extends foundry.abstract.DataModel {
this._prepareRolls(); 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() { static defineSchema() {
const fields = foundry.data.fields; const fields = foundry.data.fields;
@ -28,7 +41,15 @@ export class ChatDamageData extends foundry.abstract.DataModel {
} }
_prepareRolls() { _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)) { for (const key of Object.keys(this.resources)) {
this.resources[key] = Roll.fromData(this.resources[key]); this.resources[key] = Roll.fromData(this.resources[key]);
} }

View file

@ -107,7 +107,7 @@ export default class DamageField extends fields.SchemaField {
if (configDamage.main) { if (configDamage.main) {
const multiplier = config.actionActor?.system.rules?.attack?.damage?.hpDamageMultiplier ?? 1; const multiplier = config.actionActor?.system.rules?.attack?.damage?.hpDamageMultiplier ?? 1;
const takenMultiplier = actor.system.rules?.attack?.damage?.hpDamageTakenMultiplier; 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( damagePromises.push(

View file

@ -1,5 +1,5 @@
import DamageDialog from '../applications/dialogs/damageDialog.mjs'; 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'; import DHRoll from './dhRoll.mjs';
export default class DamageRoll extends DHRoll { export default class DamageRoll extends DHRoll {
@ -8,7 +8,17 @@ export default class DamageRoll extends DHRoll {
} }
get isCritical() { 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; static DefaultDialog = DamageDialog;
@ -23,7 +33,7 @@ export default class DamageRoll extends DHRoll {
const evaluateRoll = async roll => { const evaluateRoll = async roll => {
await roll.roll.evaluate(); 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; return roll.roll;
} }
@ -31,8 +41,10 @@ export default class DamageRoll extends DHRoll {
if (config.damageFormula) { if (config.damageFormula) {
config.damage.main = await evaluateRoll(config.damageFormula); config.damage.main = await evaluateRoll(config.damageFormula);
config.damage.main.options = { damageTypes: config.damage.main.options = {
config.damageFormula.damageTypes ? [...config.damageFormula.damageTypes] : [] ...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; if (!formulaData) return null;
this.options.isCritical = config.isCritical; 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); formulaData.roll.terms = Roll.parse(formulaData.roll.formula, config.data);
if (formulaData.extraFormula) { if (formulaData.extraFormula) {

View file

@ -495,9 +495,8 @@ export function expireActiveEffects(actor, allowedTypes = null) {
actor.deleteEmbeddedDocuments('ActiveEffect', effectsToExpire); actor.deleteEmbeddedDocuments('ActiveEffect', effectsToExpire);
} }
export async function getCritDamageBonus(formula) { export function getCritDamageBonus(terms) {
const critRoll = new Roll(formula); const critRoll = Roll.fromTerms(terms);
await critRoll.evaluate();
return critRoll.dice.reduce((acc, dice) => acc + dice.faces * dice.results.filter(r => r.active).length, 0); return critRoll.dice.reduce((acc, dice) => acc + dice.faces * dice.results.filter(r => r.active).length, 0);
} }

View file

@ -252,11 +252,8 @@
width: 100%; width: 100%;
text-align: center; text-align: center;
.result-info { .damage-info {
display: flex; text-decoration: underline;
gap: 4px;
align-items: center;
justify-content: center;
} }
} }
} }

View file

@ -1,5 +1,5 @@
{{#if damage.main}} {{#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}} {{/if}}
{{#each damage.resources as |roll key|}} {{#each damage.resources as |roll key|}}

View file

@ -6,23 +6,14 @@
{{#if hintText}} {{#if hintText}}
<div class="hint">{{localize hintText}}</div> <div class="hint">{{localize hintText}}</div>
{{else}} {{else}}
{{#if joinedRoll.rollData}}
<div class="result-container">
<span class="result-section-label">{{localize "DAGGERHEART.GENERAL.dualityRoll"}}</span>
<div class="result-info">
<div class="damage-info">{{joinedRoll.rollData.total}}</div>
<div>{{localize "DAGGERHEART.GENERAL.withThing" thing=joinedRoll.roll.totalLabel}}</div>
</div>
</div>
{{/if}}
{{#if joinedRoll.damageRollData}} {{#if joinedRoll.damageRollData}}
<div class="result-container"> <div class="result-container">
<span class="result-section-label">{{localize "DAGGERHEART.GENERAL.damage"}}</span> <div class="damage-info">{{joinedRoll.rollData.total}} {{localize "DAGGERHEART.GENERAL.withThing" thing=joinedRoll.roll.totalLabel}}</div>
{{#each joinedRoll.damageRollData.types as |damage key|}} {{#if joinedRoll.damageRollData.main}}
<div class="result-info"> {{> damageSummary roll=joinedRoll.damageRollData.main label=(localize "DAGGERHEART.GENERAL.damage")}}
<div>{{localize (concat "DAGGERHEART.CONFIG.HealingType." key ".name")}}</div> {{/if}}
<div class="damage-info">{{damage.total}}</div> {{#each joinedRoll.damageRollData.resources as |roll key|}}
</div> {{> damageSummary roll=roll label=(localize (concat "DAGGERHEART.CONFIG.HealingType." key ".name"))}}
{{/each}} {{/each}}
</div> </div>
{{/if}} {{/if}}
@ -38,4 +29,8 @@
</button> </button>
</div> </div>
</div> </div>
</section> </section>
{{#*inline "damageSummary"}}
<div>{{label}} {{roll.total}}</div>
{{/inline}}

View file

@ -109,11 +109,7 @@
</div> </div>
</span> </span>
{{#if damage.active}} {{#if damage.active}}
{{#if useCritDamage}} {{> "systems/daggerheart/templates/dialogs/tagTeamDialog/parts/tagTeamDamageParts.hbs" damage=damage isCritical=isCritical }}
{{> "systems/daggerheart/templates/dialogs/tagTeamDialog/parts/tagTeamDamageParts.hbs" damage=critDamage isCritical=true }}
{{else}}
{{> "systems/daggerheart/templates/dialogs/tagTeamDialog/parts/tagTeamDamageParts.hbs" damage=damage }}
{{/if}}
{{else}} {{else}}
<span class="hint">{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.makeYourRoll"}}</span> <span class="hint">{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.makeYourRoll"}}</span>
{{/if}} {{/if}}