From 9da6a130098f09ad03002d841c112afd38573d47 Mon Sep 17 00:00:00 2001 From: Dapoolp Date: Thu, 24 Jul 2025 22:56:23 +0200 Subject: [PATCH] Action Refactor Part #2 --- daggerheart.mjs | 4 +- module/applications/dialogs/d20RollDialog.mjs | 8 +- module/data/action/_module.mjs | 1 - module/data/action/actionDice.mjs | 139 ------------ module/data/action/attackAction.mjs | 4 +- module/data/action/baseAction.mjs | 205 ++---------------- module/data/action/beastformAction.mjs | 2 +- module/data/action/damageAction.mjs | 2 +- module/data/action/effectAction.mjs | 2 +- module/data/action/healingAction.mjs | 2 +- module/data/fields/_module.mjs | 1 + module/data/fields/action/_module.mjs | 7 +- module/data/fields/action/costField.mjs | 64 ++++++ module/data/fields/action/damageField.mjs | 84 +++++++ .../{effectField.mjs => effectsField.mjs} | 2 +- module/data/fields/action/healingField.mjs | 9 + module/data/fields/action/rangeField.mjs | 2 + module/data/fields/action/rollField.mjs | 58 +++++ module/data/fields/action/targetField.mjs | 45 ++++ module/data/fields/action/usesField.mjs | 23 ++ 20 files changed, 324 insertions(+), 340 deletions(-) delete mode 100644 module/data/action/actionDice.mjs rename module/data/fields/action/{effectField.mjs => effectsField.mjs} (82%) diff --git a/daggerheart.mjs b/daggerheart.mjs index 4568c670..3f7dec77 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -3,6 +3,7 @@ import * as applications from './module/applications/_module.mjs'; import * as models from './module/data/_module.mjs'; import * as documents from './module/documents/_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 { enricherConfig, enricherRenderSetup } from './module/enrichers/_module.mjs'; import { getCommandTarget, rollCommandToJSON } from './module/helpers/utils.mjs'; @@ -27,7 +28,8 @@ Hooks.once('init', () => { applications, models, documents, - dice + dice, + fields }; CONFIG.TextEditor.enrichers.push(...enricherConfig); diff --git a/module/applications/dialogs/d20RollDialog.mjs b/module/applications/dialogs/d20RollDialog.mjs index ac075ee2..5cd80c7d 100644 --- a/module/applications/dialogs/d20RollDialog.mjs +++ b/module/applications/dialogs/d20RollDialog.mjs @@ -68,19 +68,19 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio })); 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 => ({ ...x, label: x.keyIsID ? this.action.parent.parent.name : 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; } if (this.config.uses?.max) { - context.uses = this.action.calcUses(this.config.uses); - context.canRoll = context.canRoll && this.action.hasUses(context.uses); + context.uses = game.system.api.fields.ActionFields.UsesField.calcUses.call(this.action, this.config.uses); + context.canRoll = context.canRoll && game.system.api.fields.ActionFields.UsesField.hasUses.call(this.action, context.uses); } if (this.roll) { context.roll = this.roll; diff --git a/module/data/action/_module.mjs b/module/data/action/_module.mjs index bc2c16fd..7f7a2c82 100644 --- a/module/data/action/_module.mjs +++ b/module/data/action/_module.mjs @@ -1,4 +1,3 @@ -export * as ActionDice from './actionDice.mjs'; import AttackAction from './attackAction.mjs'; import BaseAction from './baseAction.mjs'; import BeastformAction from './beastformAction.mjs'; diff --git a/module/data/action/actionDice.mjs b/module/data/action/actionDice.mjs deleted file mode 100644 index 1d2b204b..00000000 --- a/module/data/action/actionDice.mjs +++ /dev/null @@ -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' - } - ) - }; - } -} diff --git a/module/data/action/attackAction.mjs b/module/data/action/attackAction.mjs index ddcccfca..83613a9e 100644 --- a/module/data/action/attackAction.mjs +++ b/module/data/action/attackAction.mjs @@ -1,8 +1,8 @@ -import { DHDamageData } from './actionDice.mjs'; +import { DHDamageData } from '../fields/action/damageField.mjs'; import DHDamageAction from './damageAction.mjs'; export default class DHAttackAction extends DHDamageAction { - static extraSchemas = [...super.extraSchemas, ...['roll', 'save']]; + static extraSchemas = [...super.extraSchemas, 'roll', 'save']; static getRollType(parent) { return parent.parent.type === 'weapon' ? 'attack' : 'spellcast'; diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index 9f6d088d..86f8cef2 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -1,8 +1,6 @@ -import { DHActionDiceData, DHActionRollData, DHDamageData, DHDamageField, DHResourceData } from './actionDice.mjs'; import DhpActor from '../../documents/actor.mjs'; import D20RollDialog from '../../applications/dialogs/d20RollDialog.mjs'; import { ActionMixin } from '../fields/actionField.mjs'; -import * as ActionFields from '../fields/action/_module.mjs'; const fields = foundry.data.fields; @@ -19,8 +17,7 @@ const fields = foundry.data.fields; */ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel) { - static baseFields = ['cost', 'uses', 'range']; - static extraFields = []; + static extraSchemas = ['cost', 'uses', 'range']; static defineSchema() { const schemaFields = { @@ -38,27 +35,17 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel }) }; - [...this.baseFields, ...extraFields].forEach(s => { - // const clsField = + this.extraSchemas.forEach(s => { + let clsField; + if(clsField = this.getActionField(s)) schemaFields[s] = new clsField(); }); return schemaFields; } - static defineExtraSchema() { - const extraFields = { - damage: new DHDamageField(), - 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; + static getActionField(name) { + const field = game.system.api.fields.ActionFields[`${name.capitalize()}Field`]; + return fields.DataField.isPrototypeOf(field) && field; } prepareData() { @@ -130,38 +117,14 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel async use(event, ...args) { 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); - // Prepare base Config - const initConfig = this.initActionConfig(event); - - // Prepare Targets - const targetConfig = this.prepareTarget(); - 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 - }; + let config = this.prepareConfig(event); + for(let i = 0; i < this.constructor.extraSchemas.length; i++) { + let clsField = this.constructor.getActionField(this.constructor.extraSchemas[i]); + if(clsField?.prepareConfig) { + const keep = clsField.prepareConfig.call(this, config); + if(config.isFastForward && !keep) 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 { event, title: this.item.name, @@ -207,7 +170,9 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel hasHealing: !!this.healing, hasEffect: !!this.effects?.length, 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); } - 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() { const roll = { modifiers: this.modifiers, @@ -316,108 +251,6 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel } /* 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 */ async applyEffects(event, data, targets) { targets ??= data.system.targets; diff --git a/module/data/action/beastformAction.mjs b/module/data/action/beastformAction.mjs index bb926dac..ad09e2fb 100644 --- a/module/data/action/beastformAction.mjs +++ b/module/data/action/beastformAction.mjs @@ -2,7 +2,7 @@ import BeastformDialog from '../../applications/dialogs/beastformDialog.mjs'; import DHBaseAction from './baseAction.mjs'; export default class DhBeastformAction extends DHBaseAction { - static extraSchemas = ['beastform']; + static extraSchemas = [...super.extraSchemas, 'beastform']; async use(event, ...args) { const beastformConfig = this.prepareBeastformConfig(); diff --git a/module/data/action/damageAction.mjs b/module/data/action/damageAction.mjs index 988e1844..422745ec 100644 --- a/module/data/action/damageAction.mjs +++ b/module/data/action/damageAction.mjs @@ -2,7 +2,7 @@ import { setsEqual } from '../../helpers/utils.mjs'; import DHBaseAction from './baseAction.mjs'; export default class DHDamageAction extends DHBaseAction { - static extraSchemas = ['damage', 'target', 'effects']; + static extraSchemas = [...super.extraSchemas, 'damage', 'target', 'effects']; getFormulaValue(part, data) { let formulaValue = part.value; diff --git a/module/data/action/effectAction.mjs b/module/data/action/effectAction.mjs index 65425a6f..de84224c 100644 --- a/module/data/action/effectAction.mjs +++ b/module/data/action/effectAction.mjs @@ -1,7 +1,7 @@ import DHBaseAction from './baseAction.mjs'; export default class DHEffectAction extends DHBaseAction { - static extraSchemas = ['effects', 'target']; + static extraSchemas = [...super.extraSchemas, 'effects', 'target']; async trigger(event, data) { if(this.effects.length) { diff --git a/module/data/action/healingAction.mjs b/module/data/action/healingAction.mjs index b7cc4a75..b2cdf385 100644 --- a/module/data/action/healingAction.mjs +++ b/module/data/action/healingAction.mjs @@ -1,7 +1,7 @@ import DHBaseAction from './baseAction.mjs'; export default class DHHealingAction extends DHBaseAction { - static extraSchemas = ['target', 'effects', 'healing', 'roll']; + static extraSchemas = [...super.extraSchemas, 'target', 'effects', 'healing', 'roll']; static getRollType(parent) { return 'spellcast'; diff --git a/module/data/fields/_module.mjs b/module/data/fields/_module.mjs index 78de9e98..8d36b76d 100644 --- a/module/data/fields/_module.mjs +++ b/module/data/fields/_module.mjs @@ -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'; diff --git a/module/data/fields/action/_module.mjs b/module/data/fields/action/_module.mjs index 2ac58aa7..f9a24d69 100644 --- a/module/data/fields/action/_module.mjs +++ b/module/data/fields/action/_module.mjs @@ -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'; \ No newline at end of file +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'; \ No newline at end of file diff --git a/module/data/fields/action/costField.mjs b/module/data/fields/action/costField.mjs index c9e179bc..e96a88e1 100644 --- a/module/data/fields/action/costField.mjs +++ b/module/data/fields/action/costField.mjs @@ -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; + } } \ No newline at end of file diff --git a/module/data/fields/action/damageField.mjs b/module/data/fields/action/damageField.mjs index e69de29b..f08fcd5e 100644 --- a/module/data/fields/action/damageField.mjs +++ b/module/data/fields/action/damageField.mjs @@ -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' + } + ) + }; + } +} \ No newline at end of file diff --git a/module/data/fields/action/effectField.mjs b/module/data/fields/action/effectsField.mjs similarity index 82% rename from module/data/fields/action/effectField.mjs rename to module/data/fields/action/effectsField.mjs index 381da6f9..968d753c 100644 --- a/module/data/fields/action/effectField.mjs +++ b/module/data/fields/action/effectsField.mjs @@ -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(), diff --git a/module/data/fields/action/healingField.mjs b/module/data/fields/action/healingField.mjs index e69de29b..926663b9 100644 --- a/module/data/fields/action/healingField.mjs +++ b/module/data/fields/action/healingField.mjs @@ -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); + } +} \ No newline at end of file diff --git a/module/data/fields/action/rangeField.mjs b/module/data/fields/action/rangeField.mjs index 89942721..111b6da0 100644 --- a/module/data/fields/action/rangeField.mjs +++ b/module/data/fields/action/rangeField.mjs @@ -9,4 +9,6 @@ export default class RangeField extends fields.StringField { }; super(options, context); } + + static prepareConfig(config) {} } \ No newline at end of file diff --git a/module/data/fields/action/rollField.mjs b/module/data/fields/action/rollField.mjs index e69de29b..597e804b 100644 --- a/module/data/fields/action/rollField.mjs +++ b/module/data/fields/action/rollField.mjs @@ -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); + } +} \ No newline at end of file diff --git a/module/data/fields/action/targetField.mjs b/module/data/fields/action/targetField.mjs index 45dcd6ca..18876c54 100644 --- a/module/data/fields/action/targetField.mjs +++ b/module/data/fields/action/targetField.mjs @@ -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 + }; + } } \ No newline at end of file diff --git a/module/data/fields/action/usesField.mjs b/module/data/fields/action/usesField.mjs index 6e5c7502..5f05adcb 100644 --- a/module/data/fields/action/usesField.mjs +++ b/module/data/fields/action/usesField.mjs @@ -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; + } } \ No newline at end of file