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

This commit is contained in:
WBHarry 2026-07-18 23:31:38 +02:00 committed by GitHub
parent c90c6afc19
commit 3b68c6c895
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 730 additions and 531 deletions

View file

@ -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,11 @@ 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);
if (this.config.damageFormula)
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;

View file

@ -553,23 +553,27 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
const { memberKey } = button.dataset;
this.updatePartyData(
{
[`system.tagTeam.members.${memberKey}.damageRollData.types`]:
_replace({})
[`system.tagTeam.members.${memberKey}.damageRollData`]: {
main: null,
resources: _replace({})
}
},
this.getUpdatingParts(button)
);
}
static async #rerollDamageDice(_, button) {
const { memberKey, damageKey, diceIndex, resultIndex } = button.dataset;
const { isResource, memberKey, damageKey, diceIndex, resultIndex } = button.dataset;
const memberData = this.party.system.tagTeam.members[memberKey];
await memberData.damageRollData.rerollDamageDie(damageKey, diceIndex, resultIndex);
await memberData.damageRollData.rerollDamageDie(isResource, damageKey, diceIndex, resultIndex);
const basePath = `system.tagTeam.members.${memberKey}.damageRollData`;
const updatePath = isResource ? `${basePath}.resources.${damageKey}` : `${basePath}.main`;
const updateValue = isResource ?
memberData.damageRollData.resources[damageKey] : memberData.damageRollData.main;
this.updatePartyData(
{
[`system.tagTeam.members.${memberKey}.damageRollData.types`]: {
[damageKey]: memberData.damageRollData.types[damageKey].toJSON()
}
[updatePath]: updateValue.toJSON()
},
this.getUpdatingParts(button)
);
@ -577,19 +581,19 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
async getCriticalDamage(origDamage) {
const newDamage = origDamage ? ChatDamageData.fromJSON(JSON.stringify(origDamage)) : null;
for (let key in newDamage?.types ?? {}) {
const criticalDamage = await getCritDamageBonus(newDamage.types[key].formula);
if (!criticalDamage) continue;
const criticalTerm = new foundry.dice.terms.NumericTerm({ number: criticalDamage, evaluated: true });
criticalTerm.evaluate();
newDamage.types[key] = await Roll.fromTerms([
...origDamage.types[key].terms,
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
criticalTerm
]);
newDamage.types[key].options = foundry.utils.deepClone(origDamage.types[key].options);
}
if (newDamage?.main) {
const criticalDamage = await getCritDamageBonus(newDamage.main.formula);
if (criticalDamage) {
const criticalTerm = new foundry.dice.terms.NumericTerm({ number: criticalDamage, evaluated: true });
criticalTerm.evaluate();
newDamage.main = await Roll.fromTerms([
...origDamage.main.terms,
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
criticalTerm
]);
newDamage.main.options = foundry.utils.deepClone(origDamage.main.options);
}
}
return newDamage;
}
@ -644,25 +648,47 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
? await this.getCriticalDamage(secondaryRoll.damageRollData)
: secondaryRoll.damageRollData;
if (mainRoll.damageRollData) {
for (const [key, damage] of Object.entries(secondaryDamage.types ?? {})) {
if (key in mainRoll.damageRollData.types) {
mainRoll.damageRollData.types[key] = Roll.fromTerms([
...baseMainRoll.damageRollData.types[key].terms,
if (secondaryDamage.main) {
if (mainRoll.damageRollData.main) {
mainRoll.damageRollData.main = Roll.fromTerms([
...baseMainRoll.damageRollData.main.terms,
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
...baseSecondaryRoll.damageRollData.types[key].terms
...baseSecondaryRoll.damageRollData.main.terms
]);
/* Joining the roll.options of both rolls */
const joinedDamageTypes = new Set([
...baseMainRoll.damageRollData.types[key].options.damageTypes,
...baseSecondaryRoll.damageRollData.types[key].options.damageTypes
...baseMainRoll.damageRollData.main.options.damageTypes,
...baseSecondaryRoll.damageRollData.main.options.damageTypes
]);
mainRoll.damageRollData.types[key].options = {
...baseMainRoll.damageRollData.types[key].options,
mainRoll.damageRollData.main.options = {
...baseMainRoll.damageRollData.main.options,
damageTypes: [...joinedDamageTypes]
};
} else {
mainRoll.damageRollData.types[key] = damage;
mainRoll.damageRollData.main = secondaryDamage.main;
}
}
for (const [key, damage] of Object.entries(secondaryDamage.resources ?? {})) {
if (key in mainRoll.damageRollData.resources) {
mainRoll.damageRollData.resources[key] = Roll.fromTerms([
...baseMainRoll.damageRollData.resources[key].terms,
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
...baseSecondaryRoll.damageRollData.resources[key].terms
]);
/* Joining the roll.options of both rolls */
const joinedDamageTypes = new Set([
...baseMainRoll.damageRollData.resources[key].options.damageTypes,
...baseSecondaryRoll.damageRollData.resources[key].options.damageTypes
]);
mainRoll.damageRollData.resources[key].options = {
...baseMainRoll.damageRollData.resources[key].options,
damageTypes: [...joinedDamageTypes]
};
} else {
mainRoll.damageRollData.resources[key] = damage;
}
}
} else {
@ -727,8 +753,12 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
...mainRoll.options,
damage: joinedRoll.damageRollData?.toJSON()
};
for (const type of Object.keys(joinedRoll.damageRollData?.types ?? {})) {
systemData.damage.types[type] = joinedRoll.damageRollData.types[type].toJSON();
if (joinedRoll.damageRollData.main) {
systemData.damage.main = joinedRoll.damageRollData.toJSON();
}
for (const type of Object.keys(joinedRoll.damageRollData?.resources ?? {})) {
systemData.damage.resources[type] = joinedRoll.damageRollData.resources[type].toJSON();
}
const cls = getDocumentClass('ChatMessage'),

View file

@ -1,4 +1,4 @@
import { getUnusedDamageTypes } from '../../helpers/utils.mjs';
import { DHDamageData } from '../../data/fields/action/damageField.mjs';
import DaggerheartSheet from '../sheets/daggerheart-sheet.mjs';
const { ApplicationV2 } = foundry.applications.api;
@ -31,8 +31,10 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
removeElement: this.removeElement,
removeTransformActor: this.removeTransformActor,
editEffect: this.editEffect,
addDamage: this.addDamage,
removeDamage: this.removeDamage,
addDamage: this.#onAddDamage,
removeDamage: this.#onRemoveDamage,
addDamageResource: this.#onAddDamageResource,
removeDamageResource: this.#onRemoveDamageResource,
editDoc: this.editDoc,
addTrigger: this.addTrigger,
removeTrigger: this.removeTrigger,
@ -157,9 +159,9 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
context.tabs = this._getTabs(this.constructor.TABS);
context.config = CONFIG.DH;
if (this.action.damage) {
context.allDamageTypesUsed = !getUnusedDamageTypes(this.action.damage.parts).length;
if (this.action.damage.hasOwnProperty('includeBase') && this.action.type === 'attack')
const allKeys = Object.keys(CONFIG.DH.GENERAL.healingTypes);
context.allDamageTypesUsed = allKeys.every(k => k in this.action._source.damage.resources);
if (this.action.damage?.main?.hasOwnProperty('includeBase') && this.action.type === 'attack')
context.hasBaseDamage = !!this.action.parent.attack;
}
@ -299,53 +301,71 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
}
static addDamage(_event) {
if (!this.action.damage.parts) return;
/** @this DHActionBaseConfig */
static #onAddDamage() {
if (!this.action.damage || this.action.damage?.main) return;
const choices = getUnusedDamageTypes(this.action._source.damage.parts);
const data = this.action.toObject();
data.damage.main = {
...DHDamageData.schema.getInitialValue(),
applyTo: 'hitPoints',
type: 'physical'
};
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
}
/** @this DHActionBaseConfig */
static #onRemoveDamage() {
if (!this.action.damage?.main) return;
const data = this.action.toObject();
data.damage.main = null;
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
}
/** @this DHActionBaseConfig */
static #onAddDamageResource(_event) {
if (!this.action.damage) return;
const allKeys = Object.keys(CONFIG.DH.GENERAL.healingTypes);
const unused = allKeys.filter(k => !(k in this.action._source.damage.resources));
const choices = unused.map(k => ({ value: k, label: _loc(CONFIG.DH.GENERAL.healingTypes[k].label) }));
const content = new foundry.data.fields.StringField({
label: game.i18n.localize('Damage Type'),
label: _loc('DAGGERHEART.GENERAL.damageType'),
choices,
required: true
}).toFormGroup(
{},
{
name: 'type',
localize: true,
nameAttr: 'value',
labelAttr: 'label'
}
).outerHTML;
}).toFormGroup({}, {
name: 'type',
localize: true,
nameAttr: 'value',
labelAttr: 'label'
}).outerHTML;
const callback = (_, button) => {
const data = this.action.toObject();
const type = choices[button.form.elements.type.value].value;
const part = this.action.schema.fields.damage.fields.parts.element.getInitialValue();
part.applyTo = type;
if (type === CONFIG.DH.GENERAL.healingTypes.hitPoints.id)
part.type = this.action.schema.fields.damage.fields.parts.element.fields.type.element.initial;
data.damage.parts[type] = part;
data.damage.resources[type] = {
...this.action.schema.fields.damage.fields.resources.element.getInitialValue(),
applyTo: type
};
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
};
const typeDialog = new foundry.applications.api.DialogV2({
buttons: [
foundry.utils.mergeObject(
{
action: 'ok',
label: 'Confirm',
icon: 'fas fa-check',
default: true
},
{ callback: callback }
)
{
action: 'ok',
label: 'Confirm',
icon: 'fas fa-check',
default: true,
callback
}
],
content: content,
rejectClose: false,
modal: false,
window: {
title: game.i18n.localize('Add Damage')
/** @todo localize */
title: 'Add Damage'
},
position: { width: 300 }
});
@ -353,12 +373,12 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
typeDialog.render(true);
}
static removeDamage(_event, button) {
if (!this.action.damage.parts) return;
/** @this DHActionBaseConfig */
static #onRemoveDamageResource(_event, button) {
if (!this.action.damage?.resources) return;
const data = this.action.toObject();
const key = button.dataset.key;
delete data.damage.parts[key];
data.damage.parts[`${key}`] = _del;
data.damage.resources[key] = _del;
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
}

View file

@ -1,3 +1,4 @@
import { DHDamageData } from '../../data/fields/action/damageField.mjs';
import DHBaseActorSettings from '../sheets/api/actor-setting.mjs';
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
@ -8,8 +9,10 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
classes: ['adversary-settings'],
position: { width: 455, height: 'auto' },
actions: {
addExperience: DHAdversarySettings.#addExperience,
removeExperience: DHAdversarySettings.#removeExperience
addExperience: this.#onAddExperience,
removeExperience: this.#onRemoveExperience,
addDamage: this.#onAddDamage,
removeDamage: this.#onRemoveDamage
}
};
@ -71,7 +74,7 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
* Adds a new experience entry to the actor.
* @type {ApplicationClickAction}
*/
static async #addExperience() {
static async #onAddExperience() {
const newExperience = {
name: 'Experience',
modifier: 0
@ -83,7 +86,7 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
* Removes an experience entry from the actor.
* @type {ApplicationClickAction}
*/
static async #removeExperience(_, target) {
static async #onRemoveExperience(_, target) {
const experience = this.actor.system.experiences[target.dataset.experience];
const confirmed = await foundry.applications.api.DialogV2.confirm({
window: {
@ -98,4 +101,28 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
await this.actor.update({ [`system.experiences.${target.dataset.experience}`]: _del });
}
/**
* @this DHAdversarySettings
* @type {ApplicationClickAction}
*/
static #onAddDamage() {
this.actor.update({
'system.attack.damage.main': {
...DHDamageData.schema.getInitialValue(),
applyTo: 'hitPoints',
type: 'physical'
}
});
}
/**
* @this DHAdversarySettings
* @type {ApplicationClickAction}
*/
static #onRemoveDamage() {
this.actor.update({
'system.attack.damage.main': null
});
}
}

View file

@ -110,7 +110,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
const message = game.messages.get(li.dataset.messageId);
return message.system.hasRoll && (game.user.isGM || message.isAuthor);
},
callback: async li => {
onClick: async (_event, li) => {
const message = game.messages.get(li.dataset.messageId);
const reroll = await message.rolls[0].reroll({ liveRoll: true });
message.update({ rolls: [reroll] });
@ -126,7 +126,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
: false;
return (game.user.isGM || message.isAuthor) && hasRolledDamage;
},
callback: async li => {
onClick: async (_event, li) => {
const message = game.messages.get(li.dataset.messageId);
const update = await message.system.getRerolledDamage();
message.update(update);
@ -251,15 +251,17 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
}
const message = game.messages.get(messageData._id);
const target = event.target.closest('[data-die-index]');
const target = event.target.closest('[data-result]');
if (target.dataset.type === 'damage') {
const { damageType, dice, result } = target.dataset;
await message.system.damage.rerollDamageDie(damageType, dice, result);
const { isResource, damageType, dice, result } = target.dataset;
await message.system.damage.rerollDamageDie(isResource, damageType, dice, result);
const updatePath = isResource ? `system.damage.resources.${damageType}` : 'system.damage.main';
const updateValue = isResource ?
message.system.damage.resources[damageType] : message.system.damage.main;
await message.update({
'system.damage.types': {
[damageType]: message.system.damage.types[damageType].toJSON()
}
[updatePath]: updateValue.toJSON()
});
} else {
const rerollDice = message.system.roll.dice[target.dataset.dieIndex];