From d799fb1c56aec2f52de23152bed15b7983efd8f4 Mon Sep 17 00:00:00 2001 From: fyorl Date: Wed, 12 Jan 2022 21:03:22 +0000 Subject: [PATCH 1/3] [#25] Ensure resource attribute values are clamped between their min and max. Ensure token attribute update deltas are applied correctly to resource attributes. Draw attribute bars appropriately when attribute min is non-zero. --- module/actor.js | 14 ++++++++++++++ module/helper.js | 17 +++++++++++++++++ module/item.js | 1 + module/simple.js | 5 ++--- module/token.js | 38 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 module/token.js diff --git a/module/actor.js b/module/actor.js index 8288404..acaee33 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); } /* -------------------------------------------- */ @@ -236,4 +237,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..5cfb6a8 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); } /* -------------------------------------------- */ diff --git a/module/simple.js b/module/simple.js index 059bea6..59b663c 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); diff --git a/module/token.js b/module/token.js new file mode 100644 index 0000000..220826b --- /dev/null +++ b/module/token.js @@ -0,0 +1,38 @@ +/** + * Extend the base TokenDocument to allow resource 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; + } +} + + +/* -------------------------------------------- */ + + +/** + * 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); + } +} From db34d156af166cd6f2922fe8c4279e9706f187f3 Mon Sep 17 00:00:00 2001 From: fyorl Date: Thu, 13 Jan 2022 12:10:03 +0000 Subject: [PATCH 2/3] [#27] Do not throw an error when dropping an item onto a hotbar slot. --- module/macro.js | 1 + 1 file changed, 1 insertion(+) 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)); From 9b19f8b5900177bbe0be0b3a9334445a64878f98 Mon Sep 17 00:00:00 2001 From: fyorl Date: Thu, 13 Jan 2022 13:24:44 +0000 Subject: [PATCH 3/3] [#32] Draw available tracked resources from world actors rather than system model. --- module/actor.js | 10 ++++++++++ module/item.js | 10 ++++++++++ module/simple.js | 8 ++++---- module/simpletokendocument.js | 11 ----------- module/token.js | 18 ++++++++++++++++-- 5 files changed, 40 insertions(+), 17 deletions(-) delete mode 100644 module/simpletokendocument.js diff --git a/module/actor.js b/module/actor.js index acaee33..034b26a 100644 --- a/module/actor.js +++ b/module/actor.js @@ -21,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 */ /* -------------------------------------------- */ diff --git a/module/item.js b/module/item.js index 5cfb6a8..99fb5a0 100644 --- a/module/item.js +++ b/module/item.js @@ -20,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/simple.js b/module/simple.js index 59b663c..6ffff4d 100644 --- a/module/simple.js +++ b/module/simple.js @@ -115,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)); @@ -129,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)); @@ -149,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)); @@ -163,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 index 220826b..aca893a 100644 --- a/module/token.js +++ b/module/token.js @@ -1,10 +1,10 @@ /** - * Extend the base TokenDocument to allow resource to support resource type attributes. + * Extend the base TokenDocument to support resource type attributes. * @extends {TokenDocument} */ export class SimpleTokenDocument extends TokenDocument { /** @inheritdoc */ - getBarAttribute(barName, {alternative}={}) { + getBarAttribute(barName, {alternative}={}) { const data = super.getBarAttribute(barName, {alternative}); const attr = alternative || this.data[barName]?.attribute; if ( !data || !attr || !this.actor ) return data; @@ -13,6 +13,20 @@ export class SimpleTokenDocument extends TokenDocument { 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); + } }