diff --git a/module/actor-sheet.js b/module/actor-sheet.js index dd5add0..e8a153a 100644 --- a/module/actor-sheet.js +++ b/module/actor-sheet.js @@ -21,7 +21,7 @@ export class SimpleActorSheet extends ActorSheet { /** @override */ getData() { const data = super.getData(); - data.dtypes = ["String", "Number", "Boolean"]; + data.dtypes = ["String", "Number", "Boolean", "Formula"]; for ( let attr of Object.values(data.data.attributes) ) { attr.isCheckbox = attr.dtype === "Boolean"; } @@ -112,7 +112,7 @@ export class SimpleActorSheet extends ActorSheet { obj[k] = v; return obj; }, {}); - + // Remove attributes which are no longer used for ( let k of Object.keys(this.object.data.data.attributes) ) { if ( !attributes.hasOwnProperty(k) ) attributes[`-=${k}`] = null; @@ -123,7 +123,7 @@ export class SimpleActorSheet extends ActorSheet { obj[e[0]] = e[1]; return obj; }, {_id: this.object._id, "data.attributes": attributes}); - + // Update the Actor return this.object.update(formData); } diff --git a/module/actor.js b/module/actor.js index 0f85c7f..ae4a321 100644 --- a/module/actor.js +++ b/module/actor.js @@ -8,28 +8,119 @@ export class SimpleActor extends Actor { getRollData() { const data = super.getRollData(); const shorthand = game.settings.get("worldbuilding", "macroShorthand"); + const formulaAttributes = []; // Re-map all attributes onto the base roll data if ( !!shorthand ) { for ( let [k, v] of Object.entries(data.attributes) ) { - if ( !(k in data) ) data[k] = v.value; + if ( !(k in data) ) { + data[k] = v.value; + // Make an array of formula attributes for later reference. + if ( v.dtype == "Formula" ) formulaAttributes.push(k); + } } delete data.attributes; } + // Handle formula attributes when the short syntax is disabled. + else { + for ( let [k, v] of Object.entries(data.attributes) ) { + // Make an array of formula attributes for later reference. + if ( v.dtype == "Formula" ) formulaAttributes.push(k); + } + } // Map all items data using their slugified names data.items = this.data.items.reduce((obj, i) => { let key = i.name.slugify({strict: true}); let itemData = duplicate(i.data); + const itemAttributes = []; + + // Add items as shorthand. if ( !!shorthand ) { for ( let [k, v] of Object.entries(itemData.attributes) ) { - if ( !(k in itemData) ) itemData[k] = v.value; + if ( !(k in itemData) ) { + itemData[k] = v.value; + if ( v.dtype == "Formula" ) itemAttributes.push(k); + } } delete itemData["attributes"]; } + // Add formula items when shorthand isn't enabled. + else { + for ( let [k, v] of Object.entries(itemData.attributes) ) { + if ( v.dtype == "Formula" ) itemAttributes.push(k); + } + } + + // Evaluate formula attributes after all other attributes have been handled. + for ( let k of itemAttributes ) { + // Shorthand. + if ( !!shorthand ) { + if ( itemData[k] ) { + itemData[k] = this._replaceData(itemData[k], itemData, data); + } + } + // Full syntax. + else { + if ( itemData.attributes[k].value ) { + itemData.attributes[k].value = this._replaceData(itemData.attributes[k].value, itemData, data); + } + } + } + obj[key] = itemData; return obj; }, {}); + + // Evaluate formula attributes after all other attributes have been handled, + // including items. + for ( let k of formulaAttributes ) { + // Shorthand. + if ( !!shorthand ) { + if ( data[k] ) { + data[k] = this._replaceData(data[k], data); + } + } + // Full syntax. + else { + if ( data.attributes[k].value ) { + data.attributes[k].value = this._replaceData(data.attributes[k].value, data); + } + } + } + return data; } + + /** + * Replace referenced data attributes in the roll formula with the syntax `@attr` with the corresponding key from + * the provided `data` object. + * @param {String} formula The original formula within which to replace. + * @return {String} The formula with attributes replaced with values. + */ + _replaceData(formula, dataPrimary, dataSecondary = null) { + // Exit early if the formula is invalid. + if ( typeof formula != "string" ) { + return 0; + } + + // Replace attributes with their numeric equivalents. + let dataRgx = new RegExp(/@([a-z.0-9_\-]+)/gi); + let rollFormula = formula.replace(dataRgx, (match, term) => { + // Try the primary data source first (ex: actor, item). + let value = getProperty(dataPrimary, term); + if ( value ) { + return String(value).trim(); + } + // Try the secondary data source next (ex: actor that owns item); + else if (dataSecondary) { + value = getProperty(dataSecondary, term); + return value ? String(value).trim() : "0"; + } + // Otherwise, return 0. + return "0"; + }); + + return rollFormula; + } } diff --git a/module/item-sheet.js b/module/item-sheet.js index 8d378e1..a743a5f 100644 --- a/module/item-sheet.js +++ b/module/item-sheet.js @@ -20,7 +20,7 @@ export class SimpleItemSheet extends ItemSheet { /** @override */ getData() { const data = super.getData(); - data.dtypes = ["String", "Number", "Boolean"]; + data.dtypes = ["String", "Number", "Boolean", "Formula"]; for ( let attr of Object.values(data.data.attributes) ) { attr.isCheckbox = attr.dtype === "Boolean"; } @@ -97,7 +97,7 @@ export class SimpleItemSheet extends ItemSheet { obj[k] = v; return obj; }, {}); - + // Remove attributes which are no longer used for ( let k of Object.keys(this.object.data.data.attributes) ) { if ( !attributes.hasOwnProperty(k) ) attributes[`-=${k}`] = null;