/* ======================================================
BEGIN MULTIVERSAL SHEET WORKER GENERATOR
======================================================
Use this script to build sheet workers automatically.
Place this section in your script block, preferably at the end.
Fill the two data objects, multistats and multifunctions, with your details.
VERSION 1.
READMORE: https://app.roll20.net/forum/permalink/7664925/
======================================================*/
const multistats = {
};
const multifunctions = {
sum: (arr) => arr.reduce((total, add) => total + add), // add up any number of attributes
pick_from_list: (arr) => arr[ arr[0] ], // get the value of an attribute, from a select.
};
// ======================================================*/
// DO NOT EDIT BELOW THIS LINE //
// ======================================================*/
const mvlog = (title, text, color = 'green', style='font-size:12px; font-weight:normal;', headerstyle = 'font-size:13px; font-weight:bold;') => {
let titleStyle = `color:${color}; ${headerstyle} text-decoration:underline;`;
let textStyle = `color:${color}; ${style}`;
const output = `%c${title}:%c ${text}`;
console.log(output,titleStyle,textStyle);
};
// can use $ placeholder in attribute names. This converts '$_stat' to 'repeating_section_stat'
const rep = '$'; //placeholder for repeating_section_
const makeRepeatingName = (attribute, section) => attribute.startsWith(rep) ? attribute.replace(rep, `repeating_${section}_`) : attribute;
const makeRepeatingAttributes = (attributes, section) => attributes.map(a => makeRepeatingName(a, section));
const makeRepeatingID = (a, section, id) => a.replace(`repeating_${section}_`,`repeating_${section}_${id}_`);
// given array of attributes, find if any have repeating_ and return the section name
// section name will be 2nd element of name split on "_"
const findSection = (arr) => {
const s = arr.find(a => a.includes('repeating_'));
const section = (s ? s.split('_')[1] : null);
return section;
};
// check if attribute is one where a repeating section attribute depends on attributes both inside and outside the repeating section
const isMixed = (attributes, destination) => {
const some = someRepeating(attributes);
const all = allRepeating(attributes);
const repeatingdestination = destination.startsWith('repeating_');
return (some && !all && repeatingdestination);
};
const allRepeating = attributes => attributes.every(r => r.startsWith('repeating_'));
const someRepeating = attributes => attributes.some(r => r.startsWith('repeating_'));
const defaultDataType = 'array'; // might change this to object
const getData = (values, data = 'a', isnumbers = 0) => {
// only a is functional right now, so this function is redundant.
switch(data.charAt(0).toLowerCase()) {
case 'o': return values;
case 'a': return Object.values(values).map(i => 1 === isnumbers ? parseInt(i) ||0 : (0 === isnumbers ? +i || 0 : i));
case 'v': return Object.values(values)[0];
}
};
const processMax = (destination, result, max) => {
const settings = {};
if(max === 'current' || max === 'both') settings[destination] = result;
if(max === 'max' || max === 'both') settings[`${destination}_max`] = result;
return settings;
};
const isFunction = value => value && (Object.prototype.toString.call(value) === '[object Function]' || 'function' === typeof value || value instanceof Function);
const processFunction = (destination, values, section) => {
const rule = multistats[destination].rule;
const func = isFunction(rule) ? rule: multifunctions[rule]; // need to test if this works for arrow functions
const data = multistats[destination].data || defaultDataType;
const v = getData(values, data);
const modifier = multistats[destination].modifier || null;
const result = func(v, modifier);
mvlog(`${makeRepeatingName(destination,section).toUpperCase()} MULTIFUNCTION`, `RULE: ${rule}; VALUES: ${JSON.stringify(values)}; RESULT: ${result}`);
return result;
};
Object.keys(multistats).forEach(destination => {
// get the section name if it exists. It is needed for mixed workers
const attributes_base = multistats[destination].attributes;
const section = multistats[destination].section || findSection(attributes_base) || null;
const attributes = makeRepeatingAttributes(attributes_base, section);
const realdestination = makeRepeatingName(destination, section); // needed in case of $ in destination
mvlog(`MULTIVERSAL- ${realdestination}`,`${attributes.join(', ')}`,'green');
if (isMixed(attributes, realdestination)) {
const changes = attributes.reduce((change, step) => `${change} change:${step.replace('repeating_' + section + '_','repeating_' +section + ':')}`,
`remove:repeating_${section} sheet:opened`);
on(changes.toLowerCase(), function (event) {
const trigger = event.sourceAttribute || '';
const triggerRow = (trigger && trigger.includes('_') && trigger.length >2) ? trigger.split('_')[2] : '';
// if triggerRow, only update initial row
getSectionIDs(`repeating_${section}`, function (ids) {
const sectionAtts = attributes.filter(f => f.startsWith(`repeating_${section}`));
const fixedAtts = attributes.filter(f => !f.startsWith(`repeating_${section}`));
if (triggerRow) ids = [triggerRow];
const fieldNames = ids.reduce( (m,id) => [...m, ...(sectionAtts.map(field => makeRepeatingID(field,section,id) ))],[]);
getAttrs([...fieldNames,...fixedAtts], function (values) {
let settings = {};
const max = multistats[destination].max || 'current';
const fixedValues = fixedAtts.reduce((obj, a) => {
obj[a] = values[a];
return obj;
}, {});
ids.forEach(id => {
// first get all relevant attributes for this row of the section
const sectionValues = sectionAtts.reduce((obj, a) => {
const att = makeRepeatingID(a, section, id);
obj[att] = values[att];
return obj;}, {});
// now apply the formula for this row and add to settings
const combinedValues = {...sectionValues,...fixedValues};
const result = processFunction(destination,combinedValues, section);
const tempDestination = makeRepeatingID(realdestination,section,id);
const tempSettings = processMax(tempDestination, result, max);
settings = Object.assign({}, settings, tempSettings);
});
setAttrs(settings);
});
});
});
} else {
const changes = attributes.reduce((change, step) => `${change} change:${step.replace('repeating_' + section + '_','repeating_' +section + ':')}`,
`${someRepeating([...attributes,realdestination]) ? '' : 'sheet:opened '}${section ? `remove:repeating_${section}` : ''}`);
on(changes.toLowerCase(), function () {
getAttrs(attributes, function (values) {
const result = processFunction(destination,values, section);
const max = multistats[destination].max || 'current';
const settings = processMax(realdestination, result, max);
setAttrs(settings);
});
});
}
});