[Fix] 1453 - Async Resource Generation Errors (#1454)

* Fixed so that we do not run separate actor.modifyResource calls during actions and dice rolls

* .

* Simplified resourcemap
This commit is contained in:
WBHarry 2025-12-22 13:56:49 +01:00 committed by GitHub
parent e8dd38fbfa
commit 659f73116a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 153 additions and 96 deletions

View file

@ -17,7 +17,6 @@ import {
socketRegistration
} from './module/systemRegistration/_module.mjs';
import { placeables } from './module/canvas/_module.mjs';
import { registerRollDiceHooks } from './module/dice/dhRoll.mjs';
import './node_modules/@yaireo/tagify/dist/tagify.css';
import TemplateManager from './module/documents/templateManager.mjs';
@ -177,7 +176,6 @@ Hooks.on('ready', async () => {
ui.compendiumBrowser = new applications.ui.ItemBrowser();
socketRegistration.registerSocketHooks();
registerRollDiceHooks();
socketRegistration.registerUserQueries();
if (!game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.welcomeMessage)) {

View file

@ -675,16 +675,21 @@ export default class CharacterSheet extends DHBaseActorSheet {
roll: {
trait: button.dataset.attribute
},
hasRoll: true
};
const result = await this.document.diceRoll({
...config,
hasRoll: true,
actionType: 'action',
headerTitle: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.actor.name}`,
title: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
ability: abilityLabel
})
});
};
const result = await this.document.diceRoll(config);
/* This could be avoided by baking config.costs into config.resourceUpdates. Didn't feel like messing with it at the time */
const costResources = result.costs
.filter(x => x.enabled)
.map(cost => ({ ...cost, value: -cost.value, total: -cost.total }));
config.resourceUpdates.addResources(costResources);
await config.resourceUpdates.updateResources();
}
//TODO: redo toggleEquipItem method

View file

@ -206,6 +206,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
// Execute the Action Worflow in order based of schema fields
await this.executeWorkflow(config);
await config.resourceUpdates.updateResources();
if (Hooks.call(`${CONFIG.DH.id}.postUseAction`, this, config) === false) return;
@ -239,8 +240,10 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
isDirect: !!this.damage?.direct,
selectedRollMode: game.settings.get('core', 'rollMode'),
data: this.getRollData(),
evaluate: this.hasRoll
evaluate: this.hasRoll,
resourceUpdates: new ResourceUpdateMap(this.actor)
};
DHBaseAction.applyKeybindings(config);
return config;
}
@ -322,10 +325,46 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
* @returns {string[]} An array of localized tag strings.
*/
_getTags() {
const tags = [
game.i18n.localize(`DAGGERHEART.ACTIONS.TYPES.${this.type}.name`),
];
const tags = [game.i18n.localize(`DAGGERHEART.ACTIONS.TYPES.${this.type}.name`)];
return tags;
}
}
export class ResourceUpdateMap extends Map {
#actor;
constructor(actor) {
super();
this.#actor = actor;
}
addResources(resources) {
for (const resource of resources) {
if (!resource.key) continue;
const existing = this.get(resource.key);
if (existing) {
this.set(resource.key, {
...existing,
value: existing.value + (resource.value ?? 0),
total: existing.total + (resource.total ?? 0)
});
} else {
this.set(resource.key, resource);
}
}
}
#getResources() {
return Array.from(this.values());
}
async updateResources() {
if (this.#actor) {
const target = this.#actor.system.partner ?? this.#actor;
await target.modifyResource(this.#getResources());
}
}
}

View file

@ -75,7 +75,7 @@ export default class CostField extends fields.ArrayField {
}
}, []);
await actor.modifyResource(resources);
config.resourceUpdates.addResources(resources);
}
/**

View file

@ -236,81 +236,3 @@ export default class DHRoll extends Roll {
return {};
}
}
async function automateHopeFear(config) {
const automationSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
const hopeFearAutomation = automationSettings.hopeFear;
if (
!config.source?.actor ||
(game.user.isGM ? !hopeFearAutomation.gm : !hopeFearAutomation.players) ||
config.actionType === 'reaction' ||
config.tagTeamSelected ||
config.skips?.resources
)
return;
const actor = await fromUuid(config.source.actor);
let updates = [];
if (!actor) return;
if (config.rerolledRoll) {
if (config.roll.result.duality != config.rerolledRoll.result.duality) {
const hope =
(config.roll.isCritical || config.roll.result.duality === 1 ? 1 : 0) -
(config.rerolledRoll.isCritical || config.rerolledRoll.result.duality === 1 ? 1 : 0);
const stress = (config.roll.isCritical ? 1 : 0) - (config.rerolledRoll.isCritical ? 1 : 0);
const fear =
(config.roll.result.duality === -1 ? 1 : 0) - (config.rerolledRoll.result.duality === -1 ? 1 : 0);
if (hope !== 0) updates.push({ key: 'hope', value: hope, total: -1 * hope, enabled: true });
if (stress !== 0) updates.push({ key: 'stress', value: -1 * stress, total: stress, enabled: true });
if (fear !== 0) updates.push({ key: 'fear', value: fear, total: -1 * fear, enabled: true });
}
} else {
if (config.roll.isCritical || config.roll.result.duality === 1)
updates.push({ key: 'hope', value: 1, total: -1, enabled: true });
if (config.roll.isCritical) updates.push({ key: 'stress', value: -1, total: 1, enabled: true });
if (config.roll.result.duality === -1) updates.push({ key: 'fear', value: 1, total: -1, enabled: true });
}
if (updates.length) {
const target = actor.system.partner ?? actor;
if (!['dead', 'defeated', 'unconscious'].some(x => actor.statuses.has(x))) {
await target.modifyResource(updates);
}
}
}
export const registerRollDiceHooks = () => {
Hooks.on(`${CONFIG.DH.id}.postRollDuality`, async (config, message) => {
const automationSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
if (
automationSettings.countdownAutomation &&
config.actionType !== 'reaction' &&
!config.tagTeamSelected &&
!config.skips?.updateCountdowns
) {
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
if (config.roll.result.duality === -1) {
await updateCountdowns(
CONFIG.DH.GENERAL.countdownProgressionTypes.actionRoll.id,
CONFIG.DH.GENERAL.countdownProgressionTypes.fear.id
);
} else {
await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.actionRoll.id);
}
}
await automateHopeFear(config);
if (!config.roll.hasOwnProperty('success') && !config.targets?.length) return;
const rollResult = config.roll.success || config.targets.some(t => t.hit),
looseSpotlight = !rollResult || config.roll.result.duality === -1;
if (looseSpotlight && game.combat?.active) {
const currentCombatant = game.combat.combatants.get(game.combat.current?.combatantId);
if (currentCombatant?.actorId == actor.id) ui.combat.setCombatantSpotlight(currentCombatant.id);
}
});
};

View file

@ -2,6 +2,7 @@ import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs';
import D20Roll from './d20Roll.mjs';
import { setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs';
import { getDiceSoNicePresets } from '../config/generalConfig.mjs';
import { ResourceUpdateMap } from '../data/action/baseAction.mjs';
export default class DualityRoll extends D20Roll {
_advantageFaces = 6;
@ -219,6 +220,88 @@ export default class DualityRoll extends D20Roll {
return data;
}
static async buildPost(roll, config, message) {
await super.buildPost(roll, config, message);
await DualityRoll.dualityUpdate(config);
}
static async addDualityResourceUpdates(config) {
const automationSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
const hopeFearAutomation = automationSettings.hopeFear;
if (
!config.source?.actor ||
(game.user.isGM ? !hopeFearAutomation.gm : !hopeFearAutomation.players) ||
config.actionType === 'reaction' ||
config.tagTeamSelected ||
config.skips?.resources
)
return;
const actor = await fromUuid(config.source.actor);
let updates = [];
if (!actor) return;
if (config.rerolledRoll) {
if (config.roll.result.duality != config.rerolledRoll.result.duality) {
const hope =
(config.roll.isCritical || config.roll.result.duality === 1 ? 1 : 0) -
(config.rerolledRoll.isCritical || config.rerolledRoll.result.duality === 1 ? 1 : 0);
const stress = (config.roll.isCritical ? 1 : 0) - (config.rerolledRoll.isCritical ? 1 : 0);
const fear =
(config.roll.result.duality === -1 ? 1 : 0) - (config.rerolledRoll.result.duality === -1 ? 1 : 0);
if (hope !== 0) updates.push({ key: 'hope', value: hope, total: -1 * hope, enabled: true });
if (stress !== 0) updates.push({ key: 'stress', value: -1 * stress, total: stress, enabled: true });
if (fear !== 0) updates.push({ key: 'fear', value: fear, total: -1 * fear, enabled: true });
}
} else {
if (config.roll.isCritical || config.roll.result.duality === 1)
updates.push({ key: 'hope', value: 1, total: -1, enabled: true });
if (config.roll.isCritical) updates.push({ key: 'stress', value: -1, total: 1, enabled: true });
if (config.roll.result.duality === -1) updates.push({ key: 'fear', value: 1, total: -1, enabled: true });
}
if (updates.length) {
// const target = actor.system.partner ?? actor;
if (!['dead', 'defeated', 'unconscious'].some(x => actor.statuses.has(x))) {
config.resourceUpdates.addResources(updates);
}
}
}
static async dualityUpdate(config) {
const automationSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
if (
automationSettings.countdownAutomation &&
config.actionType !== 'reaction' &&
!config.tagTeamSelected &&
!config.skips?.updateCountdowns
) {
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
if (config.roll.result.duality === -1) {
await updateCountdowns(
CONFIG.DH.GENERAL.countdownProgressionTypes.actionRoll.id,
CONFIG.DH.GENERAL.countdownProgressionTypes.fear.id
);
} else {
await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.actionRoll.id);
}
}
await DualityRoll.addDualityResourceUpdates(config);
if (!config.roll.hasOwnProperty('success') && !config.targets?.length) return;
const rollResult = config.roll.success || config.targets.some(t => t.hit),
looseSpotlight = !rollResult || config.roll.result.duality === -1;
if (looseSpotlight && game.combat?.active) {
const currentCombatant = game.combat.combatants.get(game.combat.current?.combatantId);
if (currentCombatant?.actorId == actor.id) ui.combat.setCombatantSpotlight(currentCombatant.id);
}
}
static async reroll(rollString, target, message) {
let parsedRoll = game.system.api.dice.DualityRoll.fromData({ ...rollString, evaluated: false });
const term = parsedRoll.terms[target.dataset.dieIndex];
@ -257,13 +340,20 @@ export default class DualityRoll extends D20Roll {
newRoll.extra = newRoll.extra.slice(2);
const tagTeamSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
Hooks.call(`${CONFIG.DH.id}.postRollDuality`, {
const actor = message.system.source.actor ? await foundry.utils.fromUuid(message.system.source.actor) : null;
const config = {
source: { actor: message.system.source.actor ?? '' },
targets: message.system.targets,
tagTeamSelected: Object.values(tagTeamSettings.members).some(x => x.messageId === message._id),
roll: newRoll,
rerolledRoll: message.system.roll
});
rerolledRoll: message.system.roll,
resourceUpdates: new ResourceUpdateMap(actor)
};
await DualityRoll.addDualityResourceUpdates(config);
await config.resourceUpdates.updateResources();
return { newRoll, parsedRoll };
}
}

View file

@ -3,6 +3,7 @@ import { LevelOptionType } from '../data/levelTier.mjs';
import DHFeature from '../data/item/feature.mjs';
import { createScrollText, damageKeyToNumber } from '../helpers/utils.mjs';
import DhCompanionLevelUp from '../applications/levelup/companionLevelup.mjs';
import { ResourceUpdateMap } from '../data/action/baseAction.mjs';
export default class DhpActor extends Actor {
parties = new Set();
@ -477,6 +478,7 @@ export default class DhpActor extends Actor {
async diceRoll(config) {
config.source = { ...(config.source ?? {}), actor: this.uuid };
config.data = this.getRollData();
config.resourceUpdates = new ResourceUpdateMap(this);
const rollClass = config.roll.lite ? CONFIG.Dice.daggerheart['DHRoll'] : this.rollClass;
return await rollClass.build(config);
}
@ -765,9 +767,10 @@ export default class DhpActor extends Actor {
}
convertDamageToThreshold(damage) {
const massiveDamageEnabled=game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).massiveDamage.enabled;
if (massiveDamageEnabled && damage >= (this.system.damageThresholds.severe * 2)) {
return 4;
const massiveDamageEnabled = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules)
.massiveDamage.enabled;
if (massiveDamageEnabled && damage >= this.system.damageThresholds.severe * 2) {
return 4;
}
return damage >= this.system.damageThresholds.severe ? 3 : damage >= this.system.damageThresholds.major ? 2 : 1;
}