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,6 +3,7 @@ import * as applications from './module/applications/_module.mjs';
import * as models from './module/data/_module.mjs'; import * as models from './module/data/_module.mjs';
import * as documents from './module/documents/_module.mjs'; import * as documents from './module/documents/_module.mjs';
import * as dice from './module/dice/_module.mjs'; import * as dice from './module/dice/_module.mjs';
import * as fields from './module/data/fields/_module.mjs'
import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs'; import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs';
import { enricherConfig, enricherRenderSetup } from './module/enrichers/_module.mjs'; import { enricherConfig, enricherRenderSetup } from './module/enrichers/_module.mjs';
import { getCommandTarget, rollCommandToJSON } from './module/helpers/utils.mjs'; import { getCommandTarget, rollCommandToJSON } from './module/helpers/utils.mjs';
@ -27,7 +28,8 @@ Hooks.once('init', () => {
applications, applications,
models, models,
documents, documents,
dice dice,
fields
}; };
CONFIG.TextEditor.enrichers.push(...enricherConfig); CONFIG.TextEditor.enrichers.push(...enricherConfig);

View file

@ -68,19 +68,19 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
})); }));
if (this.config.costs?.length) { if (this.config.costs?.length) {
const updatedCosts = this.action.calcCosts(this.config.costs); const updatedCosts = game.system.api.fields.ActionFields.CostField.calcCosts.call(this.action, this.config.costs);
context.costs = updatedCosts.map(x => ({ context.costs = updatedCosts.map(x => ({
...x, ...x,
label: x.keyIsID label: x.keyIsID
? this.action.parent.parent.name ? this.action.parent.parent.name
: game.i18n.localize(CONFIG.DH.GENERAL.abilityCosts[x.key].label) : game.i18n.localize(CONFIG.DH.GENERAL.abilityCosts[x.key].label)
})); }));
context.canRoll = this.action.hasCost(updatedCosts); context.canRoll = game.system.api.fields.ActionFields.CostField.hasCost.call(this.action, updatedCosts);
this.config.data.scale = this.config.costs[0].total; this.config.data.scale = this.config.costs[0].total;
} }
if (this.config.uses?.max) { if (this.config.uses?.max) {
context.uses = this.action.calcUses(this.config.uses); context.uses = game.system.api.fields.ActionFields.UsesField.calcUses.call(this.action, this.config.uses);
context.canRoll = context.canRoll && this.action.hasUses(context.uses); context.canRoll = context.canRoll && game.system.api.fields.ActionFields.UsesField.hasUses.call(this.action, context.uses);
} }
if (this.roll) { if (this.roll) {
context.roll = this.roll; context.roll = this.roll;

View file

@ -1,4 +1,3 @@
export * as ActionDice from './actionDice.mjs';
import AttackAction from './attackAction.mjs'; import AttackAction from './attackAction.mjs';
import BaseAction from './baseAction.mjs'; import BaseAction from './baseAction.mjs';
import BeastformAction from './beastformAction.mjs'; import BeastformAction from './beastformAction.mjs';

View file

@ -1,139 +0,0 @@
import FormulaField from '../fields/formulaField.mjs';
const fields = foundry.data.fields;
/* Roll Field */
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;
}
}
/* Damage & Healing Field */
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 DHDamageField 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 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,8 +1,8 @@
import { DHDamageData } from './actionDice.mjs'; import { DHDamageData } from '../fields/action/damageField.mjs';
import DHDamageAction from './damageAction.mjs'; import DHDamageAction from './damageAction.mjs';
export default class DHAttackAction extends DHDamageAction { export default class DHAttackAction extends DHDamageAction {
static extraSchemas = [...super.extraSchemas, ...['roll', 'save']]; static extraSchemas = [...super.extraSchemas, 'roll', 'save'];
static getRollType(parent) { static getRollType(parent) {
return parent.parent.type === 'weapon' ? 'attack' : 'spellcast'; return parent.parent.type === 'weapon' ? 'attack' : 'spellcast';

View file

@ -1,8 +1,6 @@
import { DHActionDiceData, DHActionRollData, DHDamageData, DHDamageField, DHResourceData } from './actionDice.mjs';
import DhpActor from '../../documents/actor.mjs'; import DhpActor from '../../documents/actor.mjs';
import D20RollDialog from '../../applications/dialogs/d20RollDialog.mjs'; import D20RollDialog from '../../applications/dialogs/d20RollDialog.mjs';
import { ActionMixin } from '../fields/actionField.mjs'; import { ActionMixin } from '../fields/actionField.mjs';
import * as ActionFields from '../fields/action/_module.mjs';
const fields = foundry.data.fields; const fields = foundry.data.fields;
@ -19,8 +17,7 @@ const fields = foundry.data.fields;
*/ */
export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel) { export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel) {
static baseFields = ['cost', 'uses', 'range']; static extraSchemas = ['cost', 'uses', 'range'];
static extraFields = [];
static defineSchema() { static defineSchema() {
const schemaFields = { const schemaFields = {
@ -38,27 +35,17 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
}) })
}; };
[...this.baseFields, ...extraFields].forEach(s => { this.extraSchemas.forEach(s => {
// const clsField = let clsField;
if(clsField = this.getActionField(s)) schemaFields[s] = new clsField();
}); });
return schemaFields; return schemaFields;
} }
static defineExtraSchema() { static getActionField(name) {
const extraFields = { const field = game.system.api.fields.ActionFields[`${name.capitalize()}Field`];
damage: new DHDamageField(), return fields.DataField.isPrototypeOf(field) && field;
roll: new fields.EmbeddedDataField(DHActionRollData),
save: new ActionFields.SaveField(),
target: new ActionFields.TargetField(),
effects: new ActionFields.EffectField(),
healing: new fields.EmbeddedDataField(DHResourceData),
beastform: new ActionFields.BeastformField()
},
extraSchemas = {};
this.extraSchemas.forEach(s => (extraSchemas[s] = extraFields[s]));
return extraSchemas;
} }
prepareData() { prepareData() {
@ -130,38 +117,14 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
async use(event, ...args) { async use(event, ...args) {
if (!this.actor) throw new Error("An Action can't be used outside of an Actor context."); if (!this.actor) throw new Error("An Action can't be used outside of an Actor context.");
const isFastForward = event.shiftKey || (!this.hasRoll && !this.hasSave); let config = this.prepareConfig(event);
// Prepare base Config for(let i = 0; i < this.constructor.extraSchemas.length; i++) {
const initConfig = this.initActionConfig(event); let clsField = this.constructor.getActionField(this.constructor.extraSchemas[i]);
if(clsField?.prepareConfig) {
// Prepare Targets const keep = clsField.prepareConfig.call(this, config);
const targetConfig = this.prepareTarget(); if(config.isFastForward && !keep) return;
if (isFastForward && !targetConfig) return ui.notifications.warn('Too many targets selected for that actions.'); }
}
// Prepare Range
const rangeConfig = this.prepareRange();
// Prepare Costs
const costsConfig = this.prepareCost();
if (isFastForward && !(await this.hasCost(costsConfig)))
return ui.notifications.warn("You don't have the resources to use that action.");
// Prepare Uses
const usesConfig = this.prepareUse();
if (isFastForward && !this.hasUses(usesConfig))
return ui.notifications.warn("That action doesn't have remaining uses.");
// Prepare Roll Data
const actorData = this.getRollData();
let config = {
...initConfig,
targets: targetConfig,
range: rangeConfig,
costs: costsConfig,
uses: usesConfig,
data: actorData
};
if (Hooks.call(`${CONFIG.DH.id}.preUseAction`, this, config) === false) return; if (Hooks.call(`${CONFIG.DH.id}.preUseAction`, this, config) === false) return;
@ -193,7 +156,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
} }
/* */ /* */
initActionConfig(event) { prepareConfig(event) {
return { return {
event, event,
title: this.item.name, title: this.item.name,
@ -207,7 +170,9 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
hasHealing: !!this.healing, hasHealing: !!this.healing,
hasEffect: !!this.effects?.length, hasEffect: !!this.effects?.length,
hasSave: this.hasSave, hasSave: this.hasSave,
selectedRollMode: game.settings.get('core', 'rollMode') selectedRollMode: game.settings.get('core', 'rollMode'),
isFastForward: event.shiftKey || (!this.hasRoll && !this.hasSave),
data: this.getRollData()
}; };
} }
@ -215,36 +180,6 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
return !config.event.shiftKey && !this.hasRoll && (config.costs?.length || config.uses); return !config.event.shiftKey && !this.hasRoll && (config.costs?.length || config.uses);
} }
prepareCost() {
const costs = this.cost?.length ? foundry.utils.deepClone(this.cost) : [];
return this.calcCosts(costs);
}
prepareUse() {
const uses = this.uses?.max ? foundry.utils.deepClone(this.uses) : null;
if (uses && !uses.value) uses.value = 0;
return uses;
}
prepareTarget() {
if (!this.target?.type) return [];
let targets;
if (this.target?.type === CONFIG.DH.ACTIONS.targetTypes.self.id)
targets = this.constructor.formatTarget(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 => this.isTargetFriendly(t));
if (this.target.amount && targets.length > this.target.amount) targets = [];
}
targets = targets.map(t => this.constructor.formatTarget(t));
return targets;
}
prepareRange() {
const range = this.range ?? null;
return range;
}
prepareRoll() { prepareRoll() {
const roll = { const roll = {
modifiers: this.modifiers, modifiers: this.modifiers,
@ -316,108 +251,6 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
} }
/* SAVE */ /* SAVE */
/* COST */
getRealCosts(costs) {
const realCosts = costs?.length ? costs.filter(c => c.enabled) : [];
return realCosts;
}
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;
});
}
async 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
};
}
/* COST */
async hasCost(costs) {
const realCosts = this.getRealCosts(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 = await this.getResources(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
);
}
/* USES */
calcUses(uses) {
if (!uses) return null;
return {
...uses,
enabled: uses.hasOwnProperty('enabled') ? uses.enabled : true
};
}
hasUses(uses) {
if (!uses) return true;
return (uses.hasOwnProperty('enabled') && !uses.enabled) || uses.value + 1 <= uses.max;
}
/* TARGET */
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
};
}
/* TARGET */
/* RANGE */
/* RANGE */
/* EFFECTS */ /* EFFECTS */
async applyEffects(event, data, targets) { async applyEffects(event, data, targets) {
targets ??= data.system.targets; targets ??= data.system.targets;

View file

@ -2,7 +2,7 @@ import BeastformDialog from '../../applications/dialogs/beastformDialog.mjs';
import DHBaseAction from './baseAction.mjs'; import DHBaseAction from './baseAction.mjs';
export default class DhBeastformAction extends DHBaseAction { export default class DhBeastformAction extends DHBaseAction {
static extraSchemas = ['beastform']; static extraSchemas = [...super.extraSchemas, 'beastform'];
async use(event, ...args) { async use(event, ...args) {
const beastformConfig = this.prepareBeastformConfig(); const beastformConfig = this.prepareBeastformConfig();

View file

@ -2,7 +2,7 @@ import { setsEqual } from '../../helpers/utils.mjs';
import DHBaseAction from './baseAction.mjs'; import DHBaseAction from './baseAction.mjs';
export default class DHDamageAction extends DHBaseAction { export default class DHDamageAction extends DHBaseAction {
static extraSchemas = ['damage', 'target', 'effects']; static extraSchemas = [...super.extraSchemas, 'damage', 'target', 'effects'];
getFormulaValue(part, data) { getFormulaValue(part, data) {
let formulaValue = part.value; let formulaValue = part.value;

View file

@ -1,7 +1,7 @@
import DHBaseAction from './baseAction.mjs'; import DHBaseAction from './baseAction.mjs';
export default class DHEffectAction extends DHBaseAction { export default class DHEffectAction extends DHBaseAction {
static extraSchemas = ['effects', 'target']; static extraSchemas = [...super.extraSchemas, 'effects', 'target'];
async trigger(event, data) { async trigger(event, data) {
if(this.effects.length) { if(this.effects.length) {

View file

@ -1,7 +1,7 @@
import DHBaseAction from './baseAction.mjs'; import DHBaseAction from './baseAction.mjs';
export default class DHHealingAction extends DHBaseAction { export default class DHHealingAction extends DHBaseAction {
static extraSchemas = ['target', 'effects', 'healing', 'roll']; static extraSchemas = [...super.extraSchemas, 'target', 'effects', 'healing', 'roll'];
static getRollType(parent) { static getRollType(parent) {
return 'spellcast'; return 'spellcast';

View file

@ -3,3 +3,4 @@ export { default as FormulaField } from './formulaField.mjs';
export { default as ForeignDocumentUUIDField } from './foreignDocumentUUIDField.mjs'; export { default as ForeignDocumentUUIDField } from './foreignDocumentUUIDField.mjs';
export { default as ForeignDocumentUUIDArrayField } from './foreignDocumentUUIDArrayField.mjs'; export { default as ForeignDocumentUUIDArrayField } from './foreignDocumentUUIDArrayField.mjs';
export { default as MappingField } from './mappingField.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 UsesField } from './usesField.mjs';
export { default as RangeField } from './rangeField.mjs'; export { default as RangeField } from './rangeField.mjs';
export { default as TargetField } from './targetField.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 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); 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; const fields = foundry.data.fields;
export default class EffectField extends fields.ArrayField { export default class EffectsField extends fields.ArrayField {
constructor(options={}, context={}) { constructor(options={}, context={}) {
const element = new fields.SchemaField({ const element = new fields.SchemaField({
_id: new fields.DocumentIdField(), _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); 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); 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); 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;
}
} }