worldbuilding/module/actor.js
Matt Smith 7c676b14ee 9: Add formula (derived) attributes
- Added a new "Formula" attribute type for both actors and items.
- When iterating through attributes, formula attributes are noted in an
  array so that they can be iterated through in a second pass to
  evaluate their referenced attributes. The second iteration just
  handles attribute replacement rather than rolling the formula, that's
  deferred until the attributes are used in an actual roll formula.
- Items also have the same behavior. If an attribute isn't on the item,
  it will fall back to check the parent actor instead.
- Likely areas with issues would be formula attributes that reference
  other formula attributes, or item formula attributes that reference
  actor formula attributes.
2020-08-06 23:21:15 -05:00

126 lines
3.9 KiB
JavaScript

/**
* Extend the base Actor entity by defining a custom roll data structure which is ideal for the Simple system.
* @extends {Actor}
*/
export class SimpleActor extends Actor {
/** @override */
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;
// 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 ( 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;
}
}