Attribute Item Subtype #76

Merged
Oliver merged 50 commits from feature/attribute-items into main 2026-04-27 02:12:56 +00:00
3 changed files with 63 additions and 66 deletions
Showing only changes of commit 5073c972e8 - Show all commits

View file

@ -1,8 +1,4 @@
import { __ID__ } from "../../consts.mjs"; 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 { export class PlayerData extends foundry.abstract.TypeDataModel {
// #region Schema // #region Schema
@ -19,7 +15,7 @@ export class PlayerData extends foundry.abstract.TypeDataModel {
nullable: true, nullable: true,
initial: null, initial: null,
}), }),
attr: new EphemeralObjectField({ initial: {} }), attr: new fields.ObjectField({ persisted: false, initial: {} }),
}; };
}; };
// #endregion Schema // #endregion Schema
@ -42,14 +38,6 @@ export class PlayerData extends foundry.abstract.TypeDataModel {
return super._preCreate(data, options, user); 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 * For every attribute item that the character has, we want that data
* accessible in the system data, so we create objects dynamically that * accessible in the system data, so we create objects dynamically that
@ -69,29 +57,6 @@ export class PlayerData extends foundry.abstract.TypeDataModel {
}; };
}; };
}; };
/**
* 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 // #endregion Lifecycle
// #region Getters // #region Getters

View file

@ -1,4 +1,5 @@
import { __ID__ } from "../consts.mjs"; import { __ID__ } from "../consts.mjs";
import { clamp } from "../utils/clamp.mjs";
const { Actor } = foundry.documents; const { Actor } = foundry.documents;
const { deepClone, setProperty } = foundry.utils; const { deepClone, setProperty } = foundry.utils;
@ -28,7 +29,30 @@ export class TAFActor extends Actor {
// #endregion Lifecycle // #endregion Lifecycle
// #region Token Attributes // #region Token Attributes
/**
* @override
* This override exists in order to support making updates to the Actor's
* embedded attribute Items from the token, or falling back to the default
* handling if it's not one of our attributes.
*/
async modifyTokenAttribute(attribute, value, isDelta = false, isBar = true) { async modifyTokenAttribute(attribute, value, isDelta = false, isBar = true) {
if (attribute.startsWith(`attr.`)) {
const key = attribute.slice(5);
const attr = this.getAttribute(key);
value = isDelta ? attr.system.value + value : value;
value = clamp(attr.system.min, value, attr.system.max);
const updates = { system: { value } };
const allowed = Hooks.call(
`modifyTokenAttribute`,
{ attribute, value, isDelta, isBar, isEmbedded: true },
updates,
this
);
return allowed !== false ? await attr.update(updates) : this;
};
const attr = foundry.utils.getProperty(this.system, attribute); const attr = foundry.utils.getProperty(this.system, attribute);
const current = isBar ? attr.value : attr; const current = isBar ? attr.value : attr;
const update = isDelta ? current + value : value; const update = isDelta ? current + value : value;
@ -36,18 +60,7 @@ export class TAFActor extends Actor {
return this; return this;
}; };
// Determine the updates to make to the actor data return super.modifyTokenAttribute(attribute, value, isDelta, isBar);
let updates;
if (isBar) {
updates = {[`system.${attribute}.value`]: Math.clamp(update, 0, attr.max)};
} else {
updates = {[`system.${attribute}`]: update};
};
// Allow a hook to override these changes
const allowed = Hooks.call(`modifyTokenAttribute`, {attribute, value, isDelta, isBar}, updates, this);
return allowed !== false ? this.update(updates) : this;
}; };
// #endregion Token Attributes // #endregion Token Attributes
@ -80,8 +93,15 @@ export class TAFActor extends Actor {
}; };
// #endregion Roll Data // #endregion Roll Data
// #region Getters // #region Methods
#sortedTypes = null; #sortedTypes = null;
/**
* @override
* This override is intended to allow the "generic" item subtype to instead
* populate the Item types based on their "Group" property, for any other item
* subtype this function operates the same way that the default Foundry
* implementation does.
*/
get itemTypes() { get itemTypes() {
if (this.#sortedTypes) { return this.#sortedTypes }; if (this.#sortedTypes) { return this.#sortedTypes };
const types = {}; const types = {};
@ -97,7 +117,29 @@ export class TAFActor extends Actor {
}; };
return this.#sortedTypes = types; return this.#sortedTypes = types;
}; };
// #endregion Getters
/**
* Retrieves an attribute Item from the actor, used to more easily
*
* @param {string} key The unique identifier of the attribute
* @returns The attribute's Item document, or undefined if not found
*/
getAttribute(key) {
const attrs = this.itemTypes.attribute ?? [];
return attrs.find(attr => attr.system.key === key);
};
/**
* Updates an embedded attribute Item with a new value.
*
* @param {string} key The unique identifier of the attribute
* @param {number} value The value to set the attribute to
*/
async setAttributeValue(key, value) {
const item = this.getAttribute(key);
await item?.update({system: { value }});
};
// #endregion Methods
// #region Data Migration // #region Data Migration
/** /**

View file

@ -83,22 +83,12 @@ export class TAFTokenDocument extends TokenDocument {
if (`value` in data && `max` in data) { if (`value` in data && `max` in data) {
let editable = hasProperty(system, `${attribute}.value`); let editable = hasProperty(system, `${attribute}.value`);
const isRange = getProperty(system, `${attribute}.isRange`); return {
if (isRange) { type: `bar`,
return { attribute,
type: `bar`, value: parseInt(data.value || 0),
attribute, max: parseInt(data.max || 0),
value: parseInt(data.value || 0), editable,
max: parseInt(data.max || 0),
editable,
};
} else {
return {
type: `value`,
attribute: `${attribute}.value`,
value: Number(data.value),
editable,
};
}; };
}; };