Add Document class changes to make the Token bars work with my attributes object

This commit is contained in:
Oliver-Akins 2025-06-29 00:46:30 -06:00
parent ae525ce1b8
commit 959f75d55c
2 changed files with 139 additions and 0 deletions

View file

@ -0,0 +1,28 @@
import { Logger } from "../utils/Logger.mjs";
const { Actor } = foundry.documents;
export class TAFActor extends Actor {
async modifyTokenAttribute(attribute, value, isDelta = false, isBar = true) {
Logger.table({ attribute, value, isDelta, isBar });
const attr = foundry.utils.getProperty(this.system, attribute);
const current = isBar ? attr.value : attr;
const update = isDelta ? current + value : value;
if ( update === current ) {
return this;
};
// Determine the updates to make to the actor data
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;
}
};

111
module/documents/Token.mjs Normal file
View file

@ -0,0 +1,111 @@
import { Logger } from "../utils/Logger.mjs";
const { TokenDocument } = foundry.documents;
const { getProperty, getType, hasProperty, isSubclass } = foundry.utils;
export class TAFTokenDocument extends TokenDocument {
/**
* @override
* This override's purpose is to make it so that Token attributes and bars can
* be accessed from the data model's values directly instead of relying on only
* the schema, which doesn't account for my TypedObjectField of attributes.
*/
static getTrackedAttributes(data, _path = []) {
// Case 1 - Infer attributes from schema structure.
if ( (data instanceof foundry.abstract.DataModel) || isSubclass(data, foundry.abstract.DataModel) ) {
return this._getTrackedAttributesFromObject(data, _path);
}
if ( data instanceof foundry.data.fields.SchemaField ) {
return this._getTrackedAttributesFromSchema(data, _path);
}
// Case 2 - Infer attributes from object structure.
if ( [`Object`, `Array`].includes(getType(data)) ) {
return this._getTrackedAttributesFromObject(data, _path);
}
// Case 3 - Retrieve explicitly configured attributes.
if ( !data || (typeof data === `string`) ) {
const config = this._getConfiguredTrackedAttributes(data);
if ( config ) {
return config;
}
data = undefined;
}
// Track the path and record found attributes
if ( data !== undefined ) {
return {bar: [], value: []};
}
// Case 4 - Infer attributes from system template.
const bar = new Set();
const value = new Set();
for ( const [type, model] of Object.entries(game.model.Actor) ) {
const dataModel = CONFIG.Actor.dataModels?.[type];
const inner = this.getTrackedAttributes(dataModel ?? model, _path);
inner.bar.forEach(attr => bar.add(attr.join(`.`)));
inner.value.forEach(attr => value.add(attr.join(`.`)));
}
return {
bar: Array.from(bar).map(attr => attr.split(`.`)),
value: Array.from(value).map(attr => attr.split(`.`)),
};
};
/**
* @override
*/
getBarAttribute(barName, {alternative} = {}) {
const attribute = alternative || this[barName]?.attribute;
Logger.log(barName, attribute);
if (!attribute || !this.actor) {
return null;
};
const system = this.actor.system;
// Get the current attribute value
const data = getProperty(system, attribute);
if (data == null) {
return null;
};
if (Number.isNumeric(data)) {
let editable = hasProperty(system, attribute);
return {
type: `value`,
attribute,
value: Number(data),
editable,
};
};
if (`value` in data && `max` in data) {
let editable = hasProperty(system, `${attribute}.value`);
const isRange = getProperty(system, `${attribute}.isRange`);
if (isRange) {
return {
type: `bar`,
attribute,
value: parseInt(data.value || 0),
max: parseInt(data.max || 0),
editable,
};
} else {
return {
type: `value`,
attribute: `${attribute}.value`,
value: Number(data.value),
editable,
};
};
};
// Otherwise null
return null;
};
};