diff --git a/module/actor.js b/module/actor.js
index 8288404..034b26a 100644
--- a/module/actor.js
+++ b/module/actor.js
@@ -11,6 +11,7 @@ export class SimpleActor extends Actor {
super.prepareDerivedData();
this.data.data.groups = this.data.data.groups || {};
this.data.data.attributes = this.data.data.attributes || {};
+ EntitySheetHelper.clampResourceValues(this.data.data.attributes);
}
/* -------------------------------------------- */
@@ -20,6 +21,16 @@ export class SimpleActor extends Actor {
return EntitySheetHelper.createDialog.call(this, data, options);
}
+ /* -------------------------------------------- */
+
+ /**
+ * Is this Actor used as a template for other Actors?
+ * @type {boolean}
+ */
+ get isTemplate() {
+ return !!this.getFlag("worldbuilding", "isTemplate");
+ }
+
/* -------------------------------------------- */
/* Roll Data Preparation */
/* -------------------------------------------- */
@@ -236,4 +247,17 @@ export class SimpleActor extends Actor {
}
}
}
+
+ /* -------------------------------------------- */
+
+ /** @inheritdoc */
+ async modifyTokenAttribute(attribute, value, isDelta = false, isBar = true) {
+ const current = foundry.utils.getProperty(this.data.data, attribute);
+ if ( !isBar || !isDelta || (current?.dtype !== "Resource") ) {
+ return super.modifyTokenAttribute(attribute, value, isDelta, isBar);
+ }
+ const updates = {[`data.${attribute}.value`]: Math.clamped(current.value + value, current.min, current.max)};
+ const allowed = Hooks.call("modifyTokenAttribute", {attribute, value, isDelta, isBar}, updates);
+ return allowed !== false ? this.update(updates) : this;
+ }
}
diff --git a/module/helper.js b/module/helper.js
index 4997798..dd04769 100644
--- a/module/helper.js
+++ b/module/helper.js
@@ -587,4 +587,21 @@ export class EntitySheetHelper {
options: options
});
}
+
+ /* -------------------------------------------- */
+
+ /**
+ * Ensure the resource values are within the specified min and max.
+ * @param {object} attrs The Document's attributes.
+ */
+ static clampResourceValues(attrs) {
+ const flat = foundry.utils.flattenObject(attrs);
+ for ( const [attr, value] of Object.entries(flat) ) {
+ const parts = attr.split(".");
+ if ( parts.pop() !== "value" ) continue;
+ const current = foundry.utils.getProperty(attrs, parts.join("."));
+ if ( current?.dtype !== "Resource" ) continue;
+ foundry.utils.setProperty(attrs, attr, Math.clamped(value, current.min || 0, current.max || 0));
+ }
+ }
}
diff --git a/module/item.js b/module/item.js
index b4e0cce..99fb5a0 100644
--- a/module/item.js
+++ b/module/item.js
@@ -11,6 +11,7 @@ export class SimpleItem extends Item {
super.prepareDerivedData();
this.data.data.groups = this.data.data.groups || {};
this.data.data.attributes = this.data.data.attributes || {};
+ EntitySheetHelper.clampResourceValues(this.data.data.attributes);
}
/* -------------------------------------------- */
@@ -19,4 +20,14 @@ export class SimpleItem extends Item {
static async createDialog(data={}, options={}) {
return EntitySheetHelper.createDialog.call(this, data, options);
}
+
+ /* -------------------------------------------- */
+
+ /**
+ * Is this Item used as a template for other Items?
+ * @type {boolean}
+ */
+ get isTemplate() {
+ return !!this.getFlag("worldbuilding", "isTemplate");
+ }
}
diff --git a/module/macro.js b/module/macro.js
index 4f07cbf..067b580 100644
--- a/module/macro.js
+++ b/module/macro.js
@@ -6,6 +6,7 @@
* @returns {Promise}
*/
export async function createWorldbuildingMacro(data, slot) {
+ if ( !data.roll || !data.label ) return false;
const command = `const roll = new Roll("${data.roll}", actor ? actor.getRollData() : {});
roll.toMessage({speaker, flavor: "${data.label}"});`;
let macro = game.macros.find(m => (m.name === data.label) && (m.command === command));
diff --git a/module/simple.js b/module/simple.js
index 059bea6..6ffff4d 100644
--- a/module/simple.js
+++ b/module/simple.js
@@ -10,7 +10,7 @@ import { SimpleItemSheet } from "./item-sheet.js";
import { SimpleActorSheet } from "./actor-sheet.js";
import { preloadHandlebarsTemplates } from "./templates.js";
import { createWorldbuildingMacro } from "./macro.js";
-import { SimpleTokenDocument } from "./simpletokendocument.js";
+import { SimpleToken, SimpleTokenDocument } from "./token.js";
/* -------------------------------------------- */
/* Foundry VTT Initialization */
@@ -40,9 +40,8 @@ Hooks.once("init", async function() {
// Define custom Document classes
CONFIG.Actor.documentClass = SimpleActor;
CONFIG.Item.documentClass = SimpleItem;
-
- // Update TokenDocument with overrided getBarAttribute method
CONFIG.Token.documentClass = SimpleTokenDocument;
+ CONFIG.Token.objectClass = SimpleToken;
// Register sheet application classes
Actors.unregisterSheet("core", ActorSheet);
@@ -116,7 +115,7 @@ Hooks.on("getActorDirectoryEntryContext", (html, options) => {
icon: '',
condition: li => {
const actor = game.actors.get(li.data(idAttr));
- return !actor.getFlag("worldbuilding", "isTemplate");
+ return !actor.isTemplate;
},
callback: li => {
const actor = game.actors.get(li.data(idAttr));
@@ -130,7 +129,7 @@ Hooks.on("getActorDirectoryEntryContext", (html, options) => {
icon: '',
condition: li => {
const actor = game.actors.get(li.data(idAttr));
- return actor.getFlag("worldbuilding", "isTemplate");
+ return actor.isTemplate;
},
callback: li => {
const actor = game.actors.get(li.data(idAttr));
@@ -150,7 +149,7 @@ Hooks.on("getItemDirectoryEntryContext", (html, options) => {
icon: '',
condition: li => {
const item = game.items.get(li.data(idAttr));
- return !item.getFlag("worldbuilding", "isTemplate");
+ return !item.isTemplate;
},
callback: li => {
const item = game.items.get(li.data(idAttr));
@@ -164,7 +163,7 @@ Hooks.on("getItemDirectoryEntryContext", (html, options) => {
icon: '',
condition: li => {
const item = game.items.get(li.data(idAttr));
- return item.getFlag("worldbuilding", "isTemplate");
+ return item.isTemplate;
},
callback: li => {
const item = game.items.get(li.data(idAttr));
diff --git a/module/simpletokendocument.js b/module/simpletokendocument.js
deleted file mode 100644
index dd63f65..0000000
--- a/module/simpletokendocument.js
+++ /dev/null
@@ -1,11 +0,0 @@
-export class SimpleTokenDocument extends TokenDocument {
-
- /** @inheritdoc */
- getBarAttribute(barName, {alternative}={}) {
- const attr = super.getBarAttribute(barName, {alternative});
- if ( attr === null ) return null;
- attr.editable = true; // Attribute always editable, super requires attr to exist in actor template
- return attr;
- }
-
-}
\ No newline at end of file
diff --git a/module/token.js b/module/token.js
new file mode 100644
index 0000000..aca893a
--- /dev/null
+++ b/module/token.js
@@ -0,0 +1,52 @@
+/**
+ * Extend the base TokenDocument to support resource type attributes.
+ * @extends {TokenDocument}
+ */
+export class SimpleTokenDocument extends TokenDocument {
+ /** @inheritdoc */
+ getBarAttribute(barName, {alternative}={}) {
+ const data = super.getBarAttribute(barName, {alternative});
+ const attr = alternative || this.data[barName]?.attribute;
+ if ( !data || !attr || !this.actor ) return data;
+ const current = foundry.utils.getProperty(this.actor.data.data, attr);
+ if ( "min" in current ) data.min = parseInt(current.min || 0);
+ data.editable = true;
+ return data;
+ }
+
+ /* -------------------------------------------- */
+
+ static getTrackedAttributes(data, _path=[]) {
+ if ( data || _path.length ) return super.getTrackedAttributes(data, _path);
+ data = {};
+ for ( const model of Object.values(game.system.model.Actor) ) {
+ foundry.utils.mergeObject(data, model);
+ }
+ for ( const actor of game.actors ) {
+ if ( actor.isTemplate ) foundry.utils.mergeObject(data, actor.toObject().data);
+ }
+ return super.getTrackedAttributes(data);
+ }
+}
+
+
+/* -------------------------------------------- */
+
+
+/**
+ * Extend the base Token class to implement additional system-specific logic.
+ * @extends {Token}
+ */
+export class SimpleToken extends Token {
+ _drawBar(number, bar, data) {
+ if ( "min" in data ) {
+ // Copy the data to avoid mutating what the caller gave us.
+ data = {...data};
+ // Shift the value and max by the min to ensure that the bar's percentage is drawn accurately if this resource has
+ // a non-zero min.
+ data.value -= data.min;
+ data.max -= data.min;
+ }
+ return super._drawBar(number, bar, data);
+ }
+}