Cost & Uses consume to execute

This commit is contained in:
Dapoolp 2025-08-22 13:07:28 +02:00
parent f747dc3c32
commit 9d9c636dbc
5 changed files with 109 additions and 101 deletions

View file

@ -141,14 +141,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
if (this.chatDisplay) await this.toChat(); if (this.chatDisplay) await this.toChat();
let config = this.prepareConfig(event); let config = this.prepareConfig(event);
if(!config) return;
/* Object.values(this.schema.fields).forEach( clsField => {
if (clsField?.prepareConfig) {
// const keep = clsField.prepareConfig.call(this, config);
// if (config.isFastForward && !keep) return;
if(clsField.prepareConfig.call(this, config) === false) return;
}
}) */
if (Hooks.call(`${CONFIG.DH.id}.preUseAction`, this, config) === false) return; if (Hooks.call(`${CONFIG.DH.id}.preUseAction`, this, config) === false) return;
@ -161,9 +154,6 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
// Execute the Action Worflow in order based of schema fields // Execute the Action Worflow in order based of schema fields
await this.executeWorkflow(config); await this.executeWorkflow(config);
// Consume resources
await this.consume(config);
if (Hooks.call(`${CONFIG.DH.id}.postUseAction`, this, config) === false) return; if (Hooks.call(`${CONFIG.DH.id}.postUseAction`, this, config) === false) return;
return config; return config;
@ -179,18 +169,15 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
action: this._id, action: this._id,
actor: this.actor.uuid actor: this.actor.uuid
}, },
dialog: { dialog: {},
// configure: this.hasRoll
},
type: this.type, type: this.type,
hasRoll: this.hasRoll, hasRoll: this.hasRoll,
hasDamage: this.hasDamagePart && this.type !== 'healing', hasDamage: this.hasDamage,
hasHealing: this.hasDamagePart && this.type === 'healing', hasHealing: this.hasHealing,
hasEffect: !!this.effects?.length, hasEffect: !!this.effects?.length,
hasSave: this.hasSave, hasSave: this.hasSave,
isDirect: !!this.damage?.direct, isDirect: !!this.damage?.direct,
selectedRollMode: game.settings.get('core', 'rollMode'), selectedRollMode: game.settings.get('core', 'rollMode'),
// isFastForward: event.shiftKey,
data: this.getRollData(), data: this.getRollData(),
evaluate: this.hasRoll evaluate: this.hasRoll
}; };
@ -200,13 +187,10 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
prepareConfig(event) { prepareConfig(event) {
const config = this.prepareBaseConfig(event); const config = this.prepareBaseConfig(event);
Object.values(this.schema.fields).forEach( clsField => { for(const clsField of Object.values(this.schema.fields)) {
if (clsField?.prepareConfig) { if (clsField?.prepareConfig)
// const keep = clsField.prepareConfig.call(this, config); if(clsField.prepareConfig.call(this, config) === false) return false;
// if (config.isFastForward && !keep) return;
if(clsField.prepareConfig.call(this, config) === false) return;
} }
})
return config; return config;
} }
@ -215,8 +199,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
} }
async consume(config, successCost = false) { async consume(config, successCost = false) {
await game.system.api.fields.ActionFields.CostField.consume.call(this, config, successCost); await this.workflow.get("cost")?.execute(config, successCost);
await game.system.api.fields.ActionFields.UsesField.consume.call(this, config, successCost); await this.workflow.get("uses")?.execute(config, successCost);
if (config.roll && !config.roll.success && successCost) { if (config.roll && !config.roll.success && successCost) {
setTimeout(() => { setTimeout(() => {
@ -231,13 +215,21 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
/** /**
* Getters to know which parts the action of composed of. A field can exist but configured to not be used. * Getters to know which parts the action of composed of. A field can exist but configured to not be used.
* @returns {boolean} Does that part in the action. * @returns {boolean} If that part is in the action.
*/ */
get hasRoll() { get hasRoll() {
return !!this.roll?.type; return !!this.roll?.type;
} }
get hasDamage() {
return this.damage?.parts?.length && this.type !== 'healing'
}
get hasHealing() {
return this.damage?.parts?.length && this.type === 'healing'
}
get hasDamagePart() { get hasDamagePart() {
return this.damage?.parts?.length; return this.damage?.parts?.length;
} }

View file

@ -1,6 +1,10 @@
const fields = foundry.data.fields; const fields = foundry.data.fields;
export default class CostField extends fields.ArrayField { export default class CostField extends fields.ArrayField {
/**
* Action Workflow order
*/
order = 150;
/** @inheritDoc */ /** @inheritDoc */
constructor(options = {}, context = {}) { constructor(options = {}, context = {}) {
@ -22,19 +26,71 @@ export default class CostField extends fields.ArrayField {
super(element, options, context); super(element, options, context);
} }
/**
* Cost Consumption Action Workflow part.
* Consume configured action resources.
* Must be called within Action context.
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
* @param {boolean} [successCost=false] Consume only resources configured as "On Success only" if not already consumed.
*/
async execute(config, successCost = false) {
const actor= this.actor.system.partner ?? this.actor,
usefulResources = {
...foundry.utils.deepClone(actor.system.resources),
fear: {
value: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear),
max: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxFear,
reversed: false
}
};
for (var cost of config.costs) {
if (cost.keyIsID) {
usefulResources[cost.key] = {
value: cost.value,
target: this.parent.parent,
keyIsID: true
};
}
}
const resources = CostField.getRealCosts(config.costs)
.filter(
c =>
(!successCost && (!c.consumeOnSuccess || config.roll?.success)) ||
(successCost && c.consumeOnSuccess)
)
.reduce((a, c) => {
const resource = usefulResources[c.key];
if (resource) {
a.push({
key: c.key,
value: (c.total ?? c.value) * (resource.isReversed ? 1 : -1),
target: resource.target,
keyIsID: resource.keyIsID
});
return a;
}
}, []);
await actor.modifyResource(resources);
}
/** /**
* Update Action Workflow config object. * Update Action Workflow config object.
* Must be called within Action context. * Must be called within Action context.
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods. * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
* @returns {boolean} Return false if fast-forwarded and no more uses.
*/ */
prepareConfig(config) { prepareConfig(config) {
const costs = this.cost?.length ? foundry.utils.deepClone(this.cost) : []; const costs = this.cost?.length ? foundry.utils.deepClone(this.cost) : [];
config.costs = CostField.calcCosts.call(this, costs); config.costs = CostField.calcCosts.call(this, costs);
const hasCost = CostField.hasCost.call(this, config.costs); const hasCost = CostField.hasCost.call(this, config.costs);
if (config.isFastForward && !hasCost) if (config.dialog.configure === false && !hasCost) {
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.insufficientResources')); ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.insufficientResources'));
return hasCost; return hasCost;
} }
}
/** /**
* *
@ -148,53 +204,4 @@ export default class CostField extends fields.ArrayField {
} }
return Number(max); return Number(max);
} }
/**
* Consume configured action resources.
* Must be called within Action context.
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
* @param {boolean} [successCost=false] Consume only resources configured as "On Success only" if not already consumed.
*/
static async consume(config, successCost = false) {
const actor= this.actor.system.partner ?? this.actor,
usefulResources = {
...foundry.utils.deepClone(actor.system.resources),
fear: {
value: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear),
max: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxFear,
reversed: false
}
};
for (var cost of config.costs) {
if (cost.keyIsID) {
usefulResources[cost.key] = {
value: cost.value,
target: this.parent.parent,
keyIsID: true
};
}
}
const resources = CostField.getRealCosts(config.costs)
.filter(
c =>
(!successCost && (!c.consumeOnSuccess || config.roll?.success)) ||
(successCost && c.consumeOnSuccess)
)
.reduce((a, c) => {
const resource = usefulResources[c.key];
if (resource) {
a.push({
key: c.key,
value: (c.total ?? c.value) * (resource.isReversed ? 1 : -1),
target: resource.target,
keyIsID: resource.keyIsID
});
return a;
}
}, []);
await actor.modifyResource(resources);
}
} }

View file

@ -36,10 +36,11 @@ export default class TargetField extends fields.SchemaField {
} }
config.targets = targets.map(t => TargetField.formatTarget.call(this, t)); config.targets = targets.map(t => TargetField.formatTarget.call(this, t));
const hasTargets = TargetField.checkTargets.call(this, this.target.amount, config.targets); const hasTargets = TargetField.checkTargets.call(this, this.target.amount, config.targets);
if (config.isFastForward && !hasTargets) if (config.dialog.configure === false && !hasTargets) {
return ui.notifications.warn('Too many targets selected for that actions.'); ui.notifications.warn('Too many targets selected for that actions.');
return hasTargets; return hasTargets;
} }
}
/** /**
* Check if the number of selected targets respect the amount set in the Action. * Check if the number of selected targets respect the amount set in the Action.

View file

@ -3,6 +3,10 @@ import FormulaField from '../formulaField.mjs';
const fields = foundry.data.fields; const fields = foundry.data.fields;
export default class UsesField extends fields.SchemaField { export default class UsesField extends fields.SchemaField {
/**
* Action Workflow order
*/
order = 160;
/** @inheritDoc */ /** @inheritDoc */
constructor(options = {}, context = {}) { constructor(options = {}, context = {}) {
@ -22,19 +26,38 @@ export default class UsesField extends fields.SchemaField {
super(usesFields, options, context); super(usesFields, options, context);
} }
/**
* Uses Consumption Action Workflow part.
* Increment Action spent uses by 1.
* Must be called within Action context.
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
* @param {boolean} [successCost=false] Consume only resources configured as "On Success only" if not already consumed.
*/
async execute(config, successCost = false) {
if (
config.uses?.enabled &&
((!successCost && (!config.uses?.consumeOnSuccess || config.roll?.success)) ||
(successCost && config.uses?.consumeOnSuccess))
)
this.update({ 'uses.value': this.uses.value + 1 });
}
/** /**
* Update Action Workflow config object. * Update Action Workflow config object.
* Must be called within Action context. * Must be called within Action context.
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods. * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
* @returns {boolean} Return false if fast-forwarded and no more uses.
*/ */
prepareConfig(config) { prepareConfig(config) {
const uses = this.uses?.max ? foundry.utils.deepClone(this.uses) : null; const uses = this.uses?.max ? foundry.utils.deepClone(this.uses) : null;
if (uses && !uses.value) uses.value = 0; if (uses && !uses.value) uses.value = 0;
config.uses = uses; config.uses = uses;
const hasUses = UsesField.hasUses.call(this, config.uses); const hasUses = UsesField.hasUses.call(this, config.uses);
if (config.isFastForward && !hasUses) return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.actionNoUsesRemaining')); if (config.dialog.configure === false && !hasUses) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.actionNoUsesRemaining'));
return hasUses; return hasUses;
} }
}
/** /**
* *
@ -66,19 +89,4 @@ export default class UsesField extends fields.SchemaField {
} }
return (uses.hasOwnProperty('enabled') && !uses.enabled) || uses.value + 1 <= max; return (uses.hasOwnProperty('enabled') && !uses.enabled) || uses.value + 1 <= max;
} }
/**
* Increment Action spent uses by 1.
* Must be called within Action context.
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
* @param {boolean} [successCost=false] Consume only resources configured as "On Success only" if not already consumed.
*/
static async consume(config, successCost = false) {
if (
config.uses?.enabled &&
((!successCost && (!config.uses?.consumeOnSuccess || config.roll?.success)) ||
(successCost && config.uses?.consumeOnSuccess))
)
this.update({ 'uses.value': this.uses.value + 1 });
}
} }