Action Refactor Part #2

This commit is contained in:
Dapoolp 2025-07-24 22:56:23 +02:00
parent 479e147640
commit 9da6a13009
20 changed files with 324 additions and 340 deletions

View file

@ -3,3 +3,4 @@ export { default as FormulaField } from './formulaField.mjs';
export { default as ForeignDocumentUUIDField } from './foreignDocumentUUIDField.mjs';
export { default as ForeignDocumentUUIDArrayField } from './foreignDocumentUUIDArrayField.mjs';
export { default as MappingField } from './mappingField.mjs';
export * as ActionFields from './action/_module.mjs';

View file

@ -2,6 +2,9 @@ export { default as CostField } from './costField.mjs';
export { default as UsesField } from './usesField.mjs';
export { default as RangeField } from './rangeField.mjs';
export { default as TargetField } from './targetField.mjs';
export { default as EffectField } from './effectField.mjs';
export { default as EffectsField } from './effectsField.mjs';
export { default as SaveField } from './saveField.mjs';
export { default as BeastformField } from './beastformField.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';

View file

@ -15,4 +15,68 @@ export default class CostField extends fields.ArrayField {
});
super(element, options, context);
}
static prepareConfig(config) {
const costs = this.cost?.length ? foundry.utils.deepClone(this.cost) : [];
config.costs = CostField.calcCosts.call(this, costs);
const hasCost = CostField.hasCost.call(this, config.costs);
if(config.isFastForward && !hasCost)
return ui.notifications.warn("You don't have the resources to use that action.");
return hasCost;
}
static calcCosts(costs) {
return costs.map(c => {
c.scale = c.scale ?? 1;
c.step = c.step ?? 1;
c.total = c.value * c.scale * c.step;
c.enabled = c.hasOwnProperty('enabled') ? c.enabled : true;
return c;
});
}
static hasCost(costs) {
const realCosts = CostField.getRealCosts.call(this, costs),
hasFearCost = realCosts.findIndex(c => c.key === 'fear');
if (hasFearCost > -1) {
const fearCost = realCosts.splice(hasFearCost, 1)[0];
if (
!game.user.isGM ||
fearCost.total > game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear)
)
return false;
}
/* isReversed is a sign that the resource is inverted, IE it counts upwards instead of down */
const resources = CostField.getResources.call(this, realCosts);
return realCosts.reduce(
(a, c) =>
a && resources[c.key].isReversed
? resources[c.key].value + (c.total ?? c.value) <= resources[c.key].max
: resources[c.key]?.value >= (c.total ?? c.value),
true
);
}
static getResources(costs) {
const actorResources = this.actor.system.resources;
const itemResources = {};
for (var itemResource of costs) {
if (itemResource.keyIsID) {
itemResources[itemResource.key] = {
value: this.parent.resource.value ?? 0
};
}
}
return {
...actorResources,
...itemResources
};
}
static getRealCosts(costs) {
const realCosts = costs?.length ? costs.filter(c => c.enabled) : [];
return realCosts;
}
}

View file

@ -0,0 +1,84 @@
import FormulaField from "../formulaField.mjs";
const fields = foundry.data.fields;
export default class DamageField extends fields.SchemaField {
constructor(options, context = {}) {
const damageFields = {
parts: new fields.ArrayField(new fields.EmbeddedDataField(DHDamageData)),
includeBase: new fields.BooleanField({
initial: false,
label: 'DAGGERHEART.ACTIONS.Settings.includeBase.label'
})
};
super(damageFields, options, context);
}
}
export class DHActionDiceData extends foundry.abstract.DataModel {
/** @override */
static defineSchema() {
return {
multiplier: new fields.StringField({
choices: CONFIG.DH.GENERAL.multiplierTypes,
initial: 'prof',
label: 'Multiplier'
}),
flatMultiplier: new fields.NumberField({ nullable: true, initial: 1, label: 'Flat Multiplier' }),
dice: new fields.StringField({ choices: CONFIG.DH.GENERAL.diceTypes, initial: 'd6', label: 'Dice' }),
bonus: new fields.NumberField({ nullable: true, initial: null, label: 'Bonus' }),
custom: new fields.SchemaField({
enabled: new fields.BooleanField({ label: 'Custom Formula' }),
formula: new FormulaField({ label: 'Formula' })
})
};
}
getFormula() {
const multiplier = this.multiplier === 'flat' ? this.flatMultiplier : `@${this.multiplier}`,
bonus = this.bonus ? (this.bonus < 0 ? ` - ${Math.abs(this.bonus)}` : ` + ${this.bonus}`) : '';
return this.custom.enabled ? this.custom.formula : `${multiplier ?? 1}${this.dice}${bonus}`;
}
}
export class DHResourceData extends foundry.abstract.DataModel {
/** @override */
static defineSchema() {
return {
applyTo: new fields.StringField({
choices: CONFIG.DH.GENERAL.healingTypes,
required: true,
blank: false,
initial: CONFIG.DH.GENERAL.healingTypes.hitPoints.id,
label: 'DAGGERHEART.ACTIONS.Settings.applyTo.label'
}),
resultBased: new fields.BooleanField({
initial: false,
label: 'DAGGERHEART.ACTIONS.Settings.resultBased.label'
}),
value: new fields.EmbeddedDataField(DHActionDiceData),
valueAlt: new fields.EmbeddedDataField(DHActionDiceData)
};
}
}
export class DHDamageData extends DHResourceData {
/** @override */
static defineSchema() {
return {
...super.defineSchema(),
base: new fields.BooleanField({ initial: false, readonly: true, label: 'Base' }),
type: new fields.SetField(
new fields.StringField({
choices: CONFIG.DH.GENERAL.damageTypes,
initial: 'physical',
nullable: false,
required: true
}),
{
label: 'Type'
}
)
};
}
}

View file

@ -1,6 +1,6 @@
const fields = foundry.data.fields;
export default class EffectField extends fields.ArrayField {
export default class EffectsField extends fields.ArrayField {
constructor(options={}, context={}) {
const element = new fields.SchemaField({
_id: new fields.DocumentIdField(),

View file

@ -0,0 +1,9 @@
import { DHDamageData } from "./damageField.mjs";
const fields = foundry.data.fields;
export default class HealingField extends fields.EmbeddedDataField {
constructor(options, context = {}) {
super(DHDamageData, options, context);
}
}

View file

@ -9,4 +9,6 @@ export default class RangeField extends fields.StringField {
};
super(options, context);
}
static prepareConfig(config) {}
}

View file

@ -0,0 +1,58 @@
const fields = foundry.data.fields;
export class DHActionRollData extends foundry.abstract.DataModel {
/** @override */
static defineSchema() {
return {
type: new fields.StringField({ nullable: true, initial: null, choices: CONFIG.DH.GENERAL.rollTypes }),
trait: new fields.StringField({ nullable: true, initial: null, choices: CONFIG.DH.ACTOR.abilities }),
difficulty: new fields.NumberField({ nullable: true, initial: null, integer: true, min: 0 }),
bonus: new fields.NumberField({ nullable: true, initial: null, integer: true }),
advState: new fields.StringField({ choices: CONFIG.DH.ACTIONS.advandtageState, initial: 'neutral' }),
diceRolling: new fields.SchemaField({
multiplier: new fields.StringField({
choices: CONFIG.DH.GENERAL.diceSetNumbers,
initial: 'prof',
label: 'Dice Number'
}),
flatMultiplier: new fields.NumberField({ nullable: true, initial: 1, label: 'Flat Multiplier' }),
dice: new fields.StringField({
choices: CONFIG.DH.GENERAL.diceTypes,
initial: 'd6',
label: 'Dice Type'
}),
compare: new fields.StringField({
choices: CONFIG.DH.ACTIONS.diceCompare,
initial: 'above',
label: 'Should be'
}),
treshold: new fields.NumberField({ initial: 1, integer: true, min: 1, label: 'Treshold' })
}),
useDefault: new fields.BooleanField({ initial: false })
};
}
getFormula() {
if (!this.type) return;
let formula = '';
switch (this.type) {
case 'diceSet':
const multiplier =
this.diceRolling.multiplier === 'flat'
? this.diceRolling.flatMultiplier
: `@${this.diceRolling.multiplier}`;
formula = `${multiplier}${this.diceRolling.dice}cs${CONFIG.DH.ACTIONS.diceCompare[this.diceRolling.compare].operator}${this.diceRolling.treshold}`;
break;
default:
formula = '';
break;
}
return formula;
}
}
export default class RollField extends fields.EmbeddedDataField {
constructor(options, context = {}) {
super(DHActionRollData, options, context);
}
}

View file

@ -13,4 +13,49 @@ export default class TargetField extends fields.SchemaField {
};
super(targetFields, options, context);
}
static prepareConfig(config) {
if (!this.target?.type) return [];
let targets;
if (this.target?.type === CONFIG.DH.ACTIONS.targetTypes.self.id)
targets = TargetField.formatTarget.call(this, this.actor.token ?? this.actor.prototypeToken);
targets = Array.from(game.user.targets);
if (this.target.type !== CONFIG.DH.ACTIONS.targetTypes.any.id) {
targets = targets.filter(t => TargetField.isTargetFriendly.call(this, t));
if (this.target.amount && targets.length > this.target.amount) targets = [];
}
config.targets = targets.map(t => TargetField.formatTarget.call(this, t));
const hasTargets = TargetField.checkTargets.call(this, this.target.amount, config.targets);
if(config.isFastForward && !hasTargets)
return ui.notifications.warn('Too many targets selected for that actions.');
return hasTargets;
}
static checkTargets(amount, targets) {
return !amount || (targets.length > amount);
}
static isTargetFriendly(target) {
const actorDisposition = this.actor.token
? this.actor.token.disposition
: this.actor.prototypeToken.disposition,
targetDisposition = target.document.disposition;
return (
(this.target.type === CONFIG.DH.ACTIONS.targetTypes.friendly.id &&
actorDisposition === targetDisposition) ||
(this.target.type === CONFIG.DH.ACTIONS.targetTypes.hostile.id &&
actorDisposition + targetDisposition === 0)
);
}
static formatTarget(actor) {
return {
id: actor.id,
actorId: actor.actor.uuid,
name: actor.actor.name,
img: actor.actor.img,
difficulty: actor.actor.system.difficulty,
evasion: actor.actor.system.evasion
};
}
}

View file

@ -13,4 +13,27 @@ export default class UsesField extends fields.SchemaField {
};
super(usesFields, options, context);
}
static prepareConfig(config) {
const uses = this.uses?.max ? foundry.utils.deepClone(this.uses) : null;
if (uses && !uses.value) uses.value = 0;
config.uses = uses;
const hasUses = UsesField.hasUses.call(this, config.uses);
if(config.isFastForward && !hasUses)
return ui.notifications.warn("That action doesn't have remaining uses.");
return hasUses;
}
static calcUses(uses) {
if (!uses) return null;
return {
...uses,
enabled: uses.hasOwnProperty('enabled') ? uses.enabled : true
};
}
static hasUses(uses) {
if (!uses) return true;
return (uses.hasOwnProperty('enabled') && !uses.enabled) || uses.value + 1 <= uses.max;
}
}