Add some jsdoc

This commit is contained in:
Dapoolp 2025-08-21 23:12:01 +02:00
parent a54efaeb48
commit 5c73b45193
23 changed files with 501 additions and 555 deletions

View file

@ -6,6 +6,5 @@ export { default as EffectsField } from './effectsField.mjs';
export { default as SaveField } from './saveField.mjs';
export { default as BeastformField } from './beastformField.mjs';
export { default as DamageField } from './damageField.mjs';
export { default as HealingField } from './healingField.mjs';
export { default as RollField } from './rollField.mjs';
export { default as MacroField } from './macroField.mjs';

View file

@ -1,6 +1,8 @@
const fields = foundry.data.fields;
export default class CostField extends fields.ArrayField {
/** @inheritDoc */
constructor(options = {}, context = {}) {
const element = new fields.SchemaField({
key: new fields.StringField({
@ -20,6 +22,11 @@ export default class CostField extends fields.ArrayField {
super(element, options, context);
}
/**
* Update Action Workflow config object.
* Must be called within Action context.
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
*/
prepareConfig(config) {
const costs = this.cost?.length ? foundry.utils.deepClone(this.cost) : [];
config.costs = CostField.calcCosts.call(this, costs);
@ -29,6 +36,12 @@ export default class CostField extends fields.ArrayField {
return hasCost;
}
/**
*
* Must be called within Action context.
* @param {*} costs
* @returns
*/
static calcCosts(costs) {
const resources = CostField.getResources.call(this, costs);
return costs.map(c => {
@ -47,6 +60,12 @@ export default class CostField extends fields.ArrayField {
});
}
/**
* Check if the current Actor currently has all needed resources.
* Must be called within Action context.
* @param {*} costs
* @returns {boolean}
*/
static hasCost(costs) {
const realCosts = CostField.getRealCosts.call(this, costs),
hasFearCost = realCosts.findIndex(c => c.key === 'fear');
@ -73,6 +92,12 @@ export default class CostField extends fields.ArrayField {
);
}
/**
* Get all Actor resources + parent Item potential one.
* Must be called within Action context.
* @param {*} costs
* @returns
*/
static getResources(costs) {
const actorResources = foundry.utils.deepClone(this.actor.system.resources);
if (this.actor.system.partner)
@ -93,6 +118,11 @@ export default class CostField extends fields.ArrayField {
};
}
/**
*
* @param {*} costs
* @returns
*/
static getRealCosts(costs) {
const realCosts = costs?.length ? costs.filter(c => c.enabled) : [];
let mergedCosts = [];
@ -104,6 +134,12 @@ export default class CostField extends fields.ArrayField {
return mergedCosts;
}
/**
* Format scalable max cost, inject Action datas if it's a formula.
* Must be called within Action context.
* @param {number|string} max Configured maximum for that resource.
* @returns {number} The max cost value.
*/
static formatMax(max) {
max ??= 0;
if (isNaN(max)) {
@ -112,4 +148,53 @@ export default class CostField extends fields.ArrayField {
}
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

@ -1,10 +1,15 @@
import FormulaField from '../formulaField.mjs';
import { setsEqual } from '../../../helpers/utils.mjs';
const fields = foundry.data.fields;
export default class DamageField extends fields.SchemaField {
/**
* Action Workflow order
*/
order = 20;
/** @inheritDoc */
constructor(options, context = {}) {
const damageFields = {
parts: new fields.ArrayField(new fields.EmbeddedDataField(DHDamageData)),
@ -17,60 +22,66 @@ export default class DamageField extends fields.SchemaField {
super(damageFields, options, context);
}
async execute(data, force = false) {
/**
* Roll Damage/Healing Action Workflow part.
* Must be called within Action context.
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
* @param {string} [messageId=null] ChatMessage Id where the clicked button belong.
* @param {boolean} [force=false] If the method should be executed outside of Action workflow, for ChatMessage button for example.
*/
async execute(config, messageId = null, force = false) {
if((this.hasRoll && DamageField.getAutomation() === CONFIG.DH.SETTINGS.actionAutomationChoices.never.id) && !force) return;
const systemData = data.system ?? data;
let formulas = this.damage.parts.map(p => ({
formula: DamageField.getFormulaValue.call(this, p, systemData).getFormula(this.actor),
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
}));
if (!formulas.length) return false;
formulas = DamageField.formatFormulas.call(this, formulas, systemData);
formulas = DamageField.formatFormulas.call(this, formulas, config);
delete systemData.evaluate;
const config = {
...systemData,
const damageConfig = {
...config,
roll: formulas,
dialog: {},
data: this.getRollData()
};
if(DamageField.getAutomation() === CONFIG.DH.SETTINGS.actionAutomationChoices.always.id) config.dialog.configure = false;
if (this.hasSave) config.onSave = this.save.damageMod;
delete damageConfig.evaluate;
if(DamageField.getAutomation() === CONFIG.DH.SETTINGS.actionAutomationChoices.always.id) damageConfig.dialog.configure = false;
if (config.hasSave) config.onSave = damageConfig.onSave = this.save.damageMod;
if (data.message || data.system) {
config.source.message = data.message?._id ?? data._id;
config.directDamage = false;
} else {
config.directDamage = true;
}
damageConfig.source.message = config.message?._id ?? messageId;
damageConfig.directDamage = !!damageConfig.source?.message;
if(config.source?.message && game.modules.get('dice-so-nice')?.active)
await game.dice3d.waitFor3DAnimationByMessageID(config.source.message);
if(damageConfig.source?.message && game.modules.get('dice-so-nice')?.active)
await game.dice3d.waitFor3DAnimationByMessageID(damageConfig.source.message);
if(!(await CONFIG.Dice.daggerheart.DamageRoll.build(config))) return false;
if(DamageField.getAutomation()) {
}
const damageResult = await CONFIG.Dice.daggerheart.DamageRoll.build(damageConfig);
if(!damageResult) return false;
config.damage = damageResult.damage;
}
async applyDamage(config, targets) {
console.log(config, this)
/**
* Apply Damage/Healing Action Worflow part.
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
* @param {*[]} targets Arrays of targets to bypass pre-selected ones.
* @param {boolean} force If the method should be executed outside of Action workflow, for ChatMessage button for example.
*/
static async applyDamage(config, targets = null, force = false) {
targets ??= config.targets;
if(!config.damage || !targets?.length || !DamageField.getApplyAutomation()) return;
if(!config.damage || !targets?.length || (!DamageField.getApplyAutomation() && !force)) return;
for (let target of targets) {
const actor = fromUuidSync(target.actorId);
if(!actor) continue;
if (
!this.hasHealing &&
this.onSave &&
this.system.hitTargets.find(t => t.id === target.id)?.saved?.success === true
!config.hasHealing &&
config.onSave &&
target.saved?.success === true
) {
const mod = CONFIG.DH.ACTIONS.damageOnSave[this.onSave]?.mod ?? 1;
const mod = CONFIG.DH.ACTIONS.damageOnSave[config.onSave]?.mod ?? 1;
Object.entries(config.damage).forEach(([k, v]) => {
v.total = 0;
v.parts.forEach(part => {
@ -80,12 +91,18 @@ export default class DamageField extends fields.SchemaField {
});
}
// this.consumeOnSuccess();
if (this.hasHealing) actor.takeHealing(config.damage);
else actor.takeDamage(config.damage, this.isDirect);
if (config.hasHealing) actor.takeHealing(config.damage);
else actor.takeDamage(config.damage, config.isDirect);
}
}
/**
*
* Must be called within Action context.
* @param {*} part
* @param {*} data
* @returns
*/
static getFormulaValue(part, data) {
let formulaValue = part.value;
@ -100,11 +117,18 @@ export default class DamageField extends fields.SchemaField {
return formulaValue;
}
static formatFormulas(formulas, systemData) {
/**
*
* Must be called within Action context.
* @param {*} formulas
* @param {*} systemData
* @returns
*/
static formatFormulas(formulas, data) {
const formattedFormulas = [];
formulas.forEach(formula => {
if (isNaN(formula.formula))
formula.formula = Roll.replaceFormulaData(formula.formula, this.getRollData(systemData));
formula.formula = Roll.replaceFormulaData(formula.formula, this.getRollData(data));
const same = formattedFormulas.find(
f => setsEqual(f.damageTypes, formula.damageTypes) && f.applyTo === formula.applyTo
);
@ -114,10 +138,19 @@ export default class DamageField extends fields.SchemaField {
return formattedFormulas;
}
/**
* Return the automation setting for execute method for current user role
* @returns {string} Id from settingsConfig.mjs actionAutomationChoices
*/
static getAutomation() {
return (game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damage.gm) || (!game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damage.players)
}
/**
* Return the automation setting for applyDamage method for current user role
* @returns {boolean} If applyDamage should be triggered automatically
*/
static getApplyAutomation() {
return (game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damageApply.gm) || (!game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damageApply.players)
}

View file

@ -1,8 +1,12 @@
const fields = foundry.data.fields;
export default class EffectsField extends fields.ArrayField {
/**
* Action Workflow order
*/
order = 100;
/** @inheritDoc */
constructor(options = {}, context = {}) {
const element = new fields.SchemaField({
_id: new fields.DocumentIdField(),
@ -11,27 +15,39 @@ export default class EffectsField extends fields.ArrayField {
super(element, options, context);
}
async execute(config) {
if(!this.hasEffect) return;
if(!config.message) {
/**
* Apply Effects Action Workflow part.
* Must be called within Action context.
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
* @param {object[]} [targets=null] Array of targets to override pre-selected ones.
* @param {boolean} [force=false] If the method should be executed outside of Action workflow, for ChatMessage button for example.
*/
async execute(config, targets = null, force = false) {
if(!config.hasEffect) return;
let message = config.message ?? ui.chat.collection.get(config.parent?._id);
if(!message) {
const roll = new CONFIG.Dice.daggerheart.DHRoll('');
roll._evaluated = true;
config.message = await CONFIG.Dice.daggerheart.DHRoll.toMessage(roll, config);
message = config.message = await CONFIG.Dice.daggerheart.DHRoll.toMessage(roll, config);
}
if(EffectsField.getAutomation()) {
EffectsField.applyEffects.call(this, config.targets);
if(EffectsField.getAutomation() || force) {
targets ??= config.targets.filter(t => !config.hasRoll || t.hit);
EffectsField.applyEffects.call(this, config.targets.filter(t => !config.hasRoll || t.hit));
}
}
/**
*
* Must be called within Action context.
* @param {*} targets
* @returns
*/
static async applyEffects(targets) {
const force = true; /* Where should this come from? */
if (!this.effects?.length || !targets?.length) return;
let effects = this.effects;
targets.forEach(async token => {
if (!token.hit && !force) return;
if (this.hasSave && token.saved.success === true) {
if (this.hasSave && token.saved.success === true)
effects = this.effects.filter(e => e.onSave === true);
}
if (!effects.length) return;
effects.forEach(async e => {
const actor = canvas.tokens.get(token.id)?.actor,
@ -42,6 +58,12 @@ export default class EffectsField extends fields.ArrayField {
});
}
/**
*
* @param {*} effect
* @param {*} actor
* @returns
*/
static async applyEffect(effect, actor) {
const existingEffect = actor.effects.find(e => e.origin === effect.uuid);
if (existingEffect) {
@ -63,6 +85,10 @@ export default class EffectsField extends fields.ArrayField {
await ActiveEffect.implementation.create(effectData, { parent: actor });
}
/**
* Return the automation setting for execute method for current user role
* @returns {boolean} If execute should be triggered automatically
*/
static getAutomation() {
return (game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.effect.gm) || (!game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.effect.players)
}

View file

@ -1,12 +0,0 @@
import { DHDamageData } from './damageField.mjs';
const fields = foundry.data.fields;
export default class HealingField extends fields.SchemaField {
constructor(options, context = {}) {
const healingFields = {
parts: new fields.ArrayField(new fields.EmbeddedDataField(DHDamageData))
};
super(healingFields, options, context);
}
}

View file

@ -3,10 +3,15 @@ const fields = foundry.data.fields;
export default class MacroField extends fields.DocumentUUIDField {
order = 70;
/** @inheritDoc */
constructor(context = {}) {
super({ type: "Macro" }, context);
}
/**
* Macro Action Workflow part.
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods. Currently not used.
*/
async execute(config) {
const fixUUID = !this.macro.includes('Macro.') ? `Macro.${this.macro}` : this.macro,
macro = await fromUuid(fixUUID);

View file

@ -1,6 +1,8 @@
const fields = foundry.data.fields;
export default class RangeField extends fields.StringField {
/** @inheritDoc */
constructor(context = {}) {
const options = {
choices: CONFIG.DH.GENERAL.range,
@ -11,7 +13,12 @@ export default class RangeField extends fields.StringField {
super(options, context);
}
/**
* Update Action Workflow config object.
* NOT YET IMPLEMENTED.
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
*/
prepareConfig(config) {
return true;
return;
}
}

View file

@ -112,20 +112,31 @@ export class DHActionRollData extends foundry.abstract.DataModel {
export default class RollField extends fields.EmbeddedDataField {
order = 10;
/** @inheritDoc */
constructor(options, context = {}) {
super(DHActionRollData, options, context);
}
/**
* Roll Action Workflow part.
* Must be called within Action context.
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
*/
async execute(config) {
if(!config.hasRoll) return;
config = await this.actor.diceRoll(config);
if(!config) return false;
}
/**
* Update Action Workflow config object.
* Must be called within Action context.
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
*/
prepareConfig(config) {
if(!config.hasRoll) return true;
if(!config.hasRoll) return;
config.dialog.configure = !RollField.getAutomation();
config.dialog.configure = RollField.getAutomation() ? !config.dialog.configure : config.dialog.configure;
const roll = {
baseModifiers: this.roll.getModifier(),
@ -138,9 +149,12 @@ export default class RollField extends fields.EmbeddedDataField {
if (this.roll.type === 'diceSet' || !this.hasRoll) roll.lite = true;
config.roll = roll;
return true;
}
/**
* Return the automation setting for execute method for current user role
* @returns {boolean} If execute should be triggered automatically
*/
static getAutomation() {
return (game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.roll.gm) || (!game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.roll.players)
}

View file

@ -1,11 +1,14 @@
import { abilities } from "../../../config/actorConfig.mjs";
import { emitAsGM, GMUpdateEvent } from "../../../systemRegistration/socket.mjs";
const fields = foundry.data.fields;
export default class SaveField extends fields.SchemaField {
/**
* Action Workflow order
*/
order = 50;
/** @inheritDoc */
constructor(options = {}, context = {}) {
const saveFields = {
trait: new fields.StringField({
@ -22,52 +25,61 @@ export default class SaveField extends fields.SchemaField {
super(saveFields, options, context);
}
async execute(config) {
if(!this.hasSave) return;
if(!config.message) {
/**
* Reaction Roll Action Workflow part.
* Must be called within Action context.
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
* @param {object[]} [targets=null] Array of targets to override pre-selected ones.
* @param {boolean} [force=false] If the method should be executed outside of Action workflow, for ChatMessage button for example.
*/
async execute(config, targets = null, force = false) {
if(!config.hasSave) return;
let message = config.message ?? ui.chat.collection.get(config.parent?._id);
if(!message) {
const roll = new CONFIG.Dice.daggerheart.DHRoll('');
roll._evaluated = true;
config.message = await CONFIG.Dice.daggerheart.DHRoll.toMessage(roll, config);
message = config.message = await CONFIG.Dice.daggerheart.DHRoll.toMessage(roll, config);
}
if(SaveField.getAutomation() !== CONFIG.DH.SETTINGS.actionAutomationChoices.never.id) {
SaveField.rollAllSave.call(this, config.targets, config.event, config.message);
if(SaveField.getAutomation() !== CONFIG.DH.SETTINGS.actionAutomationChoices.never.id || force) {
targets ??= config.targets.filter(t => !config.hasRoll || t.hit);
SaveField.rollAllSave.call(this, targets, config.event, message);
}
}
/**
* Roll a Reaction Roll for all targets. Send a query to the owner if the User is not.
* Must be called within Action context.
* @param {object[]} targets Array of formatted targets.
* @param {Event} event Triggering event
* @param {ChatMessage} message The ChatMessage the triggered button comes from.
*/
static async rollAllSave(targets, event, message) {
if(!targets) return;
if(!targets || !game.user.isGM) return;
targets.forEach(target => {
const actor = fromUuidSync(target.actorId);
if(actor) {
if (game.user === actor.owner)
const rollSave = game.user === actor.owner ?
SaveField.rollSave.call(this, actor, event, message)
.then(result =>
emitAsGM(
GMUpdateEvent.UpdateSaveMessage,
SaveField.updateSaveMessage.bind(this, result, message, target.id),
{
action: this.uuid,
message: message._id,
token: target.id,
result
}
)
);
else
actor.owner
: actor.owner
.query('reactionRoll', {
actionId: this.uuid,
actorId: actor.uuid,
event,
message
})
.then(result => SaveField.updateSaveMessage.call(this, result, message, target.id));
});
rollSave.then(result => SaveField.updateSaveMessage.call(this, result, message, target.id));
}
});
}
static async rollSave(actor, event, message) {
/**
* Roll a Reaction Roll for the specified Actor against the Action difficulty.
* Must be called within Action context.
* @param {*} actor Actor document
* @param {Event} event Triggering event
* @returns {object} Actor diceRoll config result.
*/
static async rollSave(actor, event) {
if (!actor) return;
const title = actor.isNPC
? game.i18n.localize('DAGGERHEART.GENERAL.reactionRoll')
@ -90,30 +102,55 @@ export default class SaveField extends fields.SchemaField {
return actor.diceRoll(rollConfig);
}
/**
* Update a Roll ChatMessage for a token according to his Reaction Roll result.
* @param {object} result Result from the Reaction Roll
* @param {object} message ChatMessage to update
* @param {string} targetId Token ID
*/
static updateSaveMessage(result, message, targetId) {
if (!result) return;
const updateMsg = this.updateChatMessage.bind(this, message, targetId, {
flags: {
[game.system.id]: {
reactionRolls: {
[targetId]:
{
result: result.roll.total,
success: result.roll.success
const updateMsg = function(message, targetId, result) {
setTimeout(async () => {
const chatMessage = ui.chat.collection.get(message._id),
changes = {
flags: {
[game.system.id]: {
reactionRolls: {
[targetId]:
{
result: result.roll.total,
success: result.roll.success
}
}
}
}
}
}
});
};
await chatMessage.update(changes);
}, 100);
};
if (game.modules.get('dice-so-nice')?.active)
game.dice3d.waitFor3DAnimationByMessageID(result.message.id ?? result.message._id).then(() => updateMsg());
else updateMsg();
game.dice3d.waitFor3DAnimationByMessageID(result.message.id ?? result.message._id).then(() => updateMsg(message, targetId, result));
else updateMsg(message, targetId, result);
}
/**
* Return the automation setting for execute method for current user role
* @returns {string} Id from settingsConfig.mjs actionAutomationChoices
*/
static getAutomation() {
return (game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.save.gm) || (!game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.save.players)
}
/**
* Send a query to an Actor owner to roll a Reaction Roll then send back the result.
* @param {object} param0
* @param {string} param0.actionId Action ID
* @param {string} param0.actorId Actor ID
* @param {Event} param0.event Triggering event
* @param {ChatMessage} param0.message Chat Message to update
* @returns
*/
static rollSaveQuery({ actionId, actorId, event, message }) {
return new Promise(async (resolve, reject) => {
const actor = await fromUuid(actorId),

View file

@ -1,6 +1,8 @@
const fields = foundry.data.fields;
export default class TargetField extends fields.SchemaField {
/** @inheritDoc */
constructor(options = {}, context = {}) {
const targetFields = {
type: new fields.StringField({
@ -13,16 +15,22 @@ export default class TargetField extends fields.SchemaField {
super(targetFields, options, context);
}
/**
* Update Action Workflow config object.
* Must be called within Action context.
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
*/
prepareConfig(config) {
if (!this.target?.type) return config.targets = [];
config.hasTarget = true;
let targets;
// If the Action is configured as self-targeted, set targets as the owner.
if (this.target?.type === CONFIG.DH.GENERAL.targetTypes.self.id)
targets = [this.actor.token ?? this.actor.prototypeToken];
else {
targets = Array.from(game.user.targets);
if (this.target.type !== CONFIG.DH.GENERAL.targetTypes.any.id) {
targets = targets.filter(t => TargetField.isTargetFriendly.call(this, t));
targets = targets.filter(target => TargetField.isTargetFriendly(this.actor, target, this.target.type));
if (this.target.amount && targets.length > this.target.amount) targets = [];
}
}
@ -33,24 +41,43 @@ export default class TargetField extends fields.SchemaField {
return hasTargets;
}
/**
* Check if the number of selected targets respect the amount set in the Action.
* NOT YET IMPLEMENTED. Will be with Target Picker.
* @param {number} amount Max amount of targets configured in the action.
* @param {*[]} targets Array of targeted tokens.
* @returns {boolean} If the amount of targeted tokens does not exceed action configured one.
*/
static checkTargets(amount, targets) {
return true;
// return !amount || (targets.length > amount);
}
static isTargetFriendly(target) {
const actorDisposition = this.actor.token
? this.actor.token.disposition
: this.actor.prototypeToken.disposition,
/**
* Compare 2 Actors disposition between each other
* @param {*} actor First actor document.
* @param {*} target Second actor document.
* @param {string} type Disposition id to compare (friendly/hostile).
* @returns {boolean} If both actors respect the provided type.
*/
static isTargetFriendly(actor, target, type) {
const actorDisposition = actor.token
? actor.token.disposition
: actor.prototypeToken.disposition,
targetDisposition = target.document.disposition;
return (
(this.target.type === CONFIG.DH.GENERAL.targetTypes.friendly.id &&
(type === CONFIG.DH.GENERAL.targetTypes.friendly.id &&
actorDisposition === targetDisposition) ||
(this.target.type === CONFIG.DH.GENERAL.targetTypes.hostile.id &&
(type === CONFIG.DH.GENERAL.targetTypes.hostile.id &&
actorDisposition + targetDisposition === 0)
);
}
/**
* Format actor to useful datas for Action roll workflow.
* @param {*} actor Actor object to format.
* @returns {*} Formatted Actor.
*/
static formatTarget(actor) {
return {
id: actor.id,

View file

@ -3,6 +3,8 @@ import FormulaField from '../formulaField.mjs';
const fields = foundry.data.fields;
export default class UsesField extends fields.SchemaField {
/** @inheritDoc */
constructor(options = {}, context = {}) {
const usesFields = {
value: new fields.NumberField({ nullable: true, initial: null }),
@ -20,6 +22,11 @@ export default class UsesField extends fields.SchemaField {
super(usesFields, options, context);
}
/**
* Update Action Workflow config object.
* Must be called within Action context.
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
*/
prepareConfig(config) {
const uses = this.uses?.max ? foundry.utils.deepClone(this.uses) : null;
if (uses && !uses.value) uses.value = 0;
@ -29,6 +36,12 @@ export default class UsesField extends fields.SchemaField {
return hasUses;
}
/**
*
* Must be called within Action context.
* @param {*} uses
* @returns {object}
*/
static calcUses(uses) {
if (!uses) return null;
return {
@ -38,6 +51,12 @@ export default class UsesField extends fields.SchemaField {
};
}
/**
* Check if the Action still get atleast one unspent uses.
* Must be called within Action context.
* @param {*} uses
* @returns {boolean}
*/
static hasUses(uses) {
if (!uses) return true;
let max = uses.max ?? 0;
@ -47,4 +66,19 @@ export default class UsesField extends fields.SchemaField {
}
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 });
}
}