[Rework] ChatMessage Damage (#2079)
Some checks are pending
Project CI / build (24.x) (push) Waiting to run

* Initial rework

* Removed unneeded method

* Removed outcommented code

* Added migration

* Fixed DamageActions

* Semi-corrected reroll of one of multiple results on a die

* Code improvement

* Added aseDie.rerollResult method to specifically reroll a specific result die in a grouping

* TagTeamDialog somewhat working

* Added migration for TagTeamData

* Fix for mean tagTeamDialog.finish

* .

* Improved migration

* Internalised ChatDamageData.prepareRolls to its own constructor

* Moved DamageTypes to roll.options

* TagTeamDialog fixes to accomodate new flat damage.types structure

* Changed so all default rolls become BaseRolls to access convenience functions and getters

* .

* Fixed so that party.tagTeam now uses a declared damageRollData(ChatDamageData) field. rollData.options.damage is retired.

* Critical damage fixes

* Corrected TagTeamDialog rerender logic when selecting a roll

* Fixed so that DamageTypes are retained and joined together throughout TagTeamDialog

* Removed some remaining types.<>.roll references

* [Rework] Damage and Damage Resource split (#2094)

* Changed the label of ActionSheet damage.resources to Mark Resources

* Fix assigning includeBase

* Localize add resource

* Maybe simplify API

* Fix onRollSimple

* Handle erroring action update differently

---------

Co-authored-by: Carlos Fernandez <cfern1990@gmail.com>
This commit is contained in:
WBHarry 2026-07-19 03:02:57 +02:00 committed by GitHub
parent 4b651836ff
commit effab8db0c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
51 changed files with 1007 additions and 788 deletions

View file

@ -12,20 +12,10 @@ export default class DamageField extends fields.SchemaField {
/** @inheritDoc */
constructor(options, context = {}) {
const damageFields = {
parts: new IterableTypedObjectField(DHDamageData),
includeBase: new fields.BooleanField({
initial: false,
label: 'DAGGERHEART.ACTIONS.Settings.includeBase.label'
}),
direct: new fields.BooleanField({ initial: false, label: 'DAGGERHEART.CONFIG.DamageType.direct.name' }),
groupAttack: new fields.StringField({
choices: CONFIG.DH.GENERAL.groupAttackRange,
blank: true,
label: 'DAGGERHEART.ACTIONS.Settings.groupAttack.label'
})
};
super(damageFields, options, context);
super({
main: new fields.EmbeddedDataField(DHDamageData, { nullable: true }),
resources: new IterableTypedObjectField(DHResourceData)
}, options, context);
}
/**
@ -41,25 +31,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)
};
@ -92,7 +80,7 @@ export default class DamageField extends fields.SchemaField {
const targetDamage = [];
const damagePromises = [];
for (let target of targets) {
for (const target of targets) {
const actor = foundry.utils.fromUuidSync(target.actorId);
if (!actor) continue;
if (!config.hasHealing && config.onSave && target.saved?.success === true) {
@ -114,13 +102,12 @@ export default class DamageField extends fields.SchemaField {
actor.takeHealing(config.damage).then(updates => targetDamage.push({ token, updates }))
);
else {
const configDamage = foundry.utils.deepClone(config.damage);
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);
}
const configDamage = config.damage.clone();
configDamage.main &&= configDamage.main.toJSON();
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);
}
damagePromises.push(
@ -183,13 +170,19 @@ 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.type ?? new Set(),
applyTo: x.applyTo
}));
const formattedFormulas = [];
formulas.forEach(formula => {
for (const formula of formulas) {
if (isNaN(formula.formula))
formula.formula = Roll.replaceFormulaData(formula.formula, this.getRollData(data));
const same = formattedFormulas.find(
@ -197,7 +190,8 @@ export default class DamageField extends fields.SchemaField {
);
if (same) same.formula += ` + ${formula.formula}`;
else formattedFormulas.push(formula);
});
}
return formattedFormulas;
}
@ -294,6 +288,7 @@ export class DHResourceData extends foundry.abstract.DataModel {
/** @override */
static defineSchema() {
return {
base: new fields.BooleanField({ initial: false, readonly: true, label: 'Base' }),
applyTo: new fields.StringField({
choices: CONFIG.DH.GENERAL.healingTypes,
required: true,
@ -316,7 +311,16 @@ export class DHDamageData extends DHResourceData {
static defineSchema() {
return {
...super.defineSchema(),
base: new fields.BooleanField({ initial: false, readonly: true, label: 'Base' }),
includeBase: new fields.BooleanField({
initial: false,
label: 'DAGGERHEART.ACTIONS.Settings.includeBase.label'
}),
direct: new fields.BooleanField({ initial: false, label: 'DAGGERHEART.CONFIG.DamageType.direct.name' }),
groupAttack: new fields.StringField({
choices: CONFIG.DH.GENERAL.groupAttackRange,
blank: true,
label: 'DAGGERHEART.ACTIONS.Settings.groupAttack.label'
}),
type: new fields.SetField(
new fields.StringField({
choices: CONFIG.DH.GENERAL.damageTypes,