diff --git a/module/data/Actor/player.mjs b/module/data/Actor/player.mjs index 4f07317..dfe0821 100644 --- a/module/data/Actor/player.mjs +++ b/module/data/Actor/player.mjs @@ -1,6 +1,11 @@ import { __ID__ } from "../../consts.mjs"; +import { Logger } from "../../utils/Logger.mjs"; +import { EphemeralObjectField } from "../fields/EphemeralObjectField.mjs"; + +const { getProperty, hasProperty } = foundry.utils; export class PlayerData extends foundry.abstract.TypeDataModel { + // #region Schema static defineSchema() { const fields = foundry.data.fields; return { @@ -14,15 +19,16 @@ export class PlayerData extends foundry.abstract.TypeDataModel { nullable: true, initial: null, }), - attr: new fields.ObjectField({ persisted: false }), + attr: new EphemeralObjectField({ initial: {} }), }; }; + // #endregion Schema // #region Lifecycle /** - * This makes sure that the actor gets created with the global attributes if - * they exist, while still allowing programmatic creation through the API with - * specific attributes. + * This makes sure that the actor gets created with the global + * attributes if they exist, while still allowing programmatic + * creation through the API with specific attributes. */ async _preCreate(data, options, user) { @@ -35,9 +41,62 @@ export class PlayerData extends foundry.abstract.TypeDataModel { return super._preCreate(data, options, user); }; + + /** + * Ensures that the required data structures exist in order for the + * derived data to be able to populate itself correctly. + */ + prepareBaseData() { + this.attr = {}; + }; + + /** + * For every attribute item that the character has, we want that data + * accessible in the system data, so we create objects dynamically that + * the rest of Foundry can read in to emulate the attributes being on + * the Actor directly. + */ + prepareDerivedData() { + const attrs = this.parent.items?.filter(item => item.type === `attribute`); + for (const attr of attrs) { + if (attr.system.isRange) { + this.attr[attr.system.key] = { + value: attr.system.value, + max: attr.system.max, + }; + } else { + this.attr[attr.system.key] = attr.system.value; + }; + }; + }; + + /** + * This handler makes it so that when a user updates the attributes + * using a "system.attr.*" property they correctly get removed from the + * update and are forwarded to the correct Item document instead + */ + async _preUpdate(data, options, user) { + if (hasProperty(data, `system.attr`)) { + Logger.info(`Forwarding attribute update(s) to embedded Item(s)`); + const items = this.parent.itemTypes?.attribute ?? []; + for (const attr of items) { + const key = `system.attr.${attr.system.key}`; + if (hasProperty(data, key)) { + let value = getProperty(data, key); + if (attr.system.isRange) { + attr.update({ system: value }); + } else { + attr.update({ system: { value }}); + }; + }; + }; + }; + }; // #endregion Lifecycle + // #region Getters get hasAttributes() { return Object.keys(this.attr).length > 0; }; + // #endregion Getters };