From 527a0cd419a6defbb9a172714077d58b5b8830a2 Mon Sep 17 00:00:00 2001 From: Joaquin Pereyra Date: Sat, 31 May 2025 12:46:26 -0300 Subject: [PATCH] FEAT: new FormulaField class FEAT: add getRollData on BaseDataItem Class FEAT: weapon FIX: remove inventoryWeapon field on Weapon Data Model --- module/applications/sheets/pc.mjs | 2 + module/data/fields/_module.mjs | 1 + module/data/fields/formulaField.mjs | 79 +++++++++++++++++++++++++++++ module/data/items/base.mjs | 13 ++++- module/data/items/weapon.mjs | 8 +-- module/data/pc.mjs | 11 ---- 6 files changed, 96 insertions(+), 18 deletions(-) create mode 100644 module/data/fields/_module.mjs create mode 100644 module/data/fields/formulaField.mjs diff --git a/module/applications/sheets/pc.mjs b/module/applications/sheets/pc.mjs index 4a91b0e5..153d1e58 100644 --- a/module/applications/sheets/pc.mjs +++ b/module/applications/sheets/pc.mjs @@ -1066,10 +1066,12 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) { await itemObject.update({ 'system.active': true }); break; case 'inventory-weapon-section': + /* FIXME inventoryWeapon is no longer a field const existingInventoryWeapon = this.document.items.find(x => x.system.inventoryWeapon); await existingInventoryWeapon?.update({ 'system.inventoryWeapon': false }); await itemObject.update({ 'system.inventoryWeapon': true }); break; + */ case 'inventory-armor-section': const existingInventoryArmor = this.document.items.find(x => x.system.inventoryArmor); await existingInventoryArmor?.update({ 'system.inventoryArmor': false }); diff --git a/module/data/fields/_module.mjs b/module/data/fields/_module.mjs new file mode 100644 index 00000000..4621ae2b --- /dev/null +++ b/module/data/fields/_module.mjs @@ -0,0 +1 @@ +export { default as FormulaField } from "./formulaField.mjs" \ No newline at end of file diff --git a/module/data/fields/formulaField.mjs b/module/data/fields/formulaField.mjs new file mode 100644 index 00000000..ee9a7f2d --- /dev/null +++ b/module/data/fields/formulaField.mjs @@ -0,0 +1,79 @@ +/** + * @typedef {StringFieldOptions} FormulaFieldOptions + * @property {boolean} [deterministic=false] Is this formula not allowed to have dice values? + */ + +/** + * Special case StringField which represents a formula. + * + * @param {FormulaFieldOptions} [options={}] Options which configure the behavior of the field. + * @property {boolean} deterministic=false Is this formula not allowed to have dice values? + */ +export default class FormulaField extends foundry.data.fields.StringField { + + /** @inheritDoc */ + static get _defaults() { + return foundry.utils.mergeObject(super._defaults, { + deterministic: false + }); + } + + /* -------------------------------------------- */ + + /** @inheritDoc */ + _validateType(value) { + const roll = new Roll(value.replace(/@([a-z.0-9_-]+)/gi, "1")); + roll.evaluateSync({ strict: false }); + if ( this.options.deterministic && !roll.isDeterministic ) throw new Error(`must not contain dice terms: ${value}`); + super._validateType(value); + } + + /* -------------------------------------------- */ + /* Active Effect Integration */ + /* -------------------------------------------- */ + + /** @override */ + _castChangeDelta(delta) { + return this._cast(delta).trim(); + } + + /* -------------------------------------------- */ + + /** @override */ + _applyChangeAdd(value, delta, model, change) { + if ( !value ) return delta; + const operator = delta.startsWith("-") ? "-" : "+"; + delta = delta.replace(/^[+-]/, "").trim(); + return `${value} ${operator} ${delta}`; + } + + /* -------------------------------------------- */ + + /** @override */ + _applyChangeMultiply(value, delta, model, change) { + if ( !value ) return delta; + const terms = new Roll(value).terms; + if ( terms.length > 1 ) return `(${value}) * ${delta}`; + return `${value} * ${delta}`; + } + + /* -------------------------------------------- */ + + /** @override */ + _applyChangeUpgrade(value, delta, model, change) { + if ( !value ) return delta; + const terms = new Roll(value).terms; + if ( (terms.length === 1) && (terms[0].fn === "max") ) return value.replace(/\)$/, `, ${delta})`); + return `max(${value}, ${delta})`; + } + + /* -------------------------------------------- */ + + /** @override */ + _applyChangeDowngrade(value, delta, model, change) { + if ( !value ) return delta; + const terms = new Roll(value).terms; + if ( (terms.length === 1) && (terms[0].fn === "min") ) return value.replace(/\)$/, `, ${delta})`); + return `min(${value}, ${delta})`; + } +} diff --git a/module/data/items/base.mjs b/module/data/items/base.mjs index 6c842662..a10c4d69 100644 --- a/module/data/items/base.mjs +++ b/module/data/items/base.mjs @@ -24,9 +24,20 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel { if (this.metadata.hasDescription) schema.description = new fields.HTMLField({ required: true, nullable: true }); - if (this.metadata.isQuantifiable) + if (this.metadata.isQuantifiable) schema.quantity = new fields.NumberField({ integer: true, initial: 1, min: 0, required: true }); return schema; } + + /** + * Obtain a data object used to evaluate any dice rolls associated with this Item + * @param {object} [options] + * @returns {object} + */ + getRollData(options = {}) { + const actorRollData = this.parent.actor?.getRollData() ?? {}; + const data = { ...actorRollData, item: { ...this } }; + return data; + } } \ No newline at end of file diff --git a/module/data/items/weapon.mjs b/module/data/items/weapon.mjs index 13714ef5..5265aa97 100644 --- a/module/data/items/weapon.mjs +++ b/module/data/items/weapon.mjs @@ -1,4 +1,5 @@ import BaseDataItem from "./base.mjs"; +import FormulaField from "../fields/formulaField.mjs"; export default class DHWeapon extends BaseDataItem { /** @inheritDoc */ @@ -18,10 +19,6 @@ export default class DHWeapon extends BaseDataItem { ...super.defineSchema(), equipped: new fields.BooleanField({ initial: false }), - //TODO: NEED REVISION - //It makes more sense to control the inventory from the actor - inventoryWeapon: new fields.NumberField({ initial: null, nullable: true, integer: true }), - //SETTINGS secondary: new fields.BooleanField({ initial: false }), trait: new fields.StringField({ required: true, choices: SYSTEM.ACTOR.abilities, initial: 'agility' }), @@ -30,8 +27,7 @@ export default class DHWeapon extends BaseDataItem { //DAMAGE damage: new fields.SchemaField({ - //TODO: CREATE FORMULA FIELD - value: new fields.StringField({ initial: 'd6' }), + value: new FormulaField({ initial: 'd6' }), type: new fields.StringField({ required: true, choices: SYSTEM.GENERAL.damageTypes, diff --git a/module/data/pc.mjs b/module/data/pc.mjs index 5a7a2d04..4905f6e5 100644 --- a/module/data/pc.mjs +++ b/module/data/pc.mjs @@ -281,17 +281,6 @@ export default class DhpPC extends foundry.abstract.TypeDataModel { } } - get inventoryWeapons() { - const inventoryWeaponFirst = this.parent.items.find(x => x.type === 'weapon' && x.system.inventoryWeapon === 1); - const inventoryWeaponSecond = this.parent.items.find( - x => x.type === 'weapon' && x.system.inventoryWeapon === 2 - ); - return { - first: this.#weaponData(inventoryWeaponFirst), - second: this.#weaponData(inventoryWeaponSecond) - }; - } - get totalAttributeMarks() { return Object.keys(this.levelData.levelups).reduce((nr, level) => { const nrAttributeMarks = Object.keys(this.levelData.levelups[level]).reduce((nr, tier) => {