Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×

Multiversal Sheet Worker Generator - fix for string attributes

1608152598
David M.
Sheet Author
API Scripter
Thanks to GIGs for the Multiversals script post. I have created a character sheet for Maximum Apocalypse and have used it a lot. I did have one problem with rules where the attributes were not numbers. Specifying "values: -1" did not appear to work, so I made a small change the getData & processFunction code. (see below) I thought this might be a useful fix for the community, if you haven't come across it in the mean time. David. /* ====================================================== BEGIN MULTIVERSAL SHEET WORKER GENERATOR ====================================================== const multistats = { luck_uses_max: {rule: 'luck_uses', attributes: ['luck', 'has_faith', "has_luck_stars"], modifier: 10}, build: {rule: 'build', attributes: ['str', 'for', 'has_giant_genetics'], modifier: 1}, build_dc: {rule: 'build_dc', attributes: ['build']}, health_max: {rule: 'special_stat', attributes: ['build'], modifier: 5}, starving: {rule: 'hungry', attributes: ['hunger', 'for_quarter', 'has_tightening_the_belt'], modifier: 4}, emaciated: {rule: 'hungry', attributes: ['hunger', 'for_half', 'has_tightening_the_belt'], modifier: 8}, dead: {rule: 'greater_than_or_equal', attributes: ['hunger', 'for']}, init: {rule: 'init', values: -1, attributes: ['agi', 'luck', 'init_temp', 'encumbered', 'shaken', 'acute', 'starving', 'emaciated', 'has_moving_target'], modifier: 10}, init_5: {rule: 'add', attributes: ['init'], modifier: -5}, init_10: {rule: 'add', attributes: ['init'], modifier: -10}, init_15: {rule: 'add', attributes: ['init'], modifier: -15}, salvage: {rule: 'special_stat', attributes: ['ins', 'luck'], modifier: 2}, resilience: {rule: 'special_stat', attributes: ['str', 'fort'], modifier: 2}, resolve: {rule: 'special_stat', attributes: ['int', 'ins'], modifier: 2}, immunity: {rule: 'special_stat', attributes: ['for', 'luck'], modifier: 2}, dodge: {rule: 'dodge', attributes: ['agi', 'ins', 'has_reflex', 'has_moving_target'], modifier: 2}, full_dodge: {rule: 'full_dodge', attributes: ['dodge', 'str']}, parry: {rule: 'max', attributes: ['blades', 'brawl', 'clubs']}, riposte: {rule: 'max', attributes: ['blades', 'brawl', 'clubs']}, encum_max: {rule: 'add', attributes: ['build'], modifier: 200}, cp_total: {rule: 'sum', attributes: ['cp_inferior', 'cp', 'cp_superior', 'cx']}, encum: {rule: 'sum', attributes: ['armor_cp_total', 'attack_cp_total', 'gear_cp_total', 'cp_total']}, encumbered: {rule: 'encumbered', attributes: ['encum', 'encum_max', 'armor_max']} }; 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. build_dc: (arr) => Math.floor(arr[0]/50)-1, add: (arr, val) => arr[0]+val, full_dodge: (arr) => Math.floor(arr[0]+(arr[1]/2)), max: (arr) => Math.max(...arr), encumbered: (arr) => arr[0] > arr[1] || arr[2] >= 12 ? 'on' : '0', greater_than_or_equal: (arr) => arr[0] >= arr[1] ? 'on' : '0', luck_uses: function(arr, divisor) { let luck = arr[0]; let has_faith = arr[1]; let has_luck_stars = arr[2]; let luck_uses = Math.floor(luck / divisor) luck_uses += has_faith > 0 ? 3 : 0; luck_uses += has_luck_stars > 0 ? 3 : 0; return luck_uses; }, dodge: function(arr, divisor) { let agi = arr[0]; let ins = arr[1]; let has_reflex = arr[2]; let has_moving_target = arr[3]; let dodge = Math.floor((agi + ins) / divisor) dodge += has_reflex > 0 ? 10 : 0; dodge += has_moving_target > 0 ? 5 : 0; return dodge; }, special_stat: function(arr, divisor) { let sum = multifunctions.sum(arr); let stat = Math.floor(sum / divisor); return stat; }, build: function(arr) { let str = arr[0]; let fort = arr[1]; let hasGiantGenetics = arr[2]; return str + fort + (hasGiantGenetics > 0 ? 50 : 0); }, hungry: function(arr, modifier) { let hunger = arr[0]; let baseThreshold = arr[1]; let hasTighteningTheBelt = arr[2]; let threshold = baseThreshold + (hasTighteningTheBelt > 0 ? modifier : 0); return hunger >= threshold ? 'on' : '0'; }, init: function(arr, divisor) { let agi = parseInt(arr[0]) || 0; let luck = parseInt(arr[1]) || 0; let initTemp = parseInt(arr[2]) || 0; let encumbered = arr[3]; let shaken = arr[4]; let acute = arr[5]; let starving = arr[6]; let emaciated = arr[7]; let hasMovingTarget = arr[8]; let init = Math.floor((agi+luck) / divisor); init += initTemp; init -= encumbered === 'on' ? 1 : 0; init -= shaken === 'on' ? 3 : 0; init -= acute === 'on' ? 5 : 0; init -= starving === 'on' ? 2 : 0; init -= emaciated === 'on' ? 2 : 0; init += hasMovingTarget > 0 ? 1 : 0; return init; } }; // ======================================================*/ // 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 isnumbers = multistats[destination].values || 0; const v = getData(values, data, isnumbers); 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); }); }); } }); // ======================================================*/ // END Multiversal Sheet Worker Generator // // ======================================================*/
1608803606

Edited 1608803660
GiGs
Pro
Sheet Author
API Scripter
Great! I'm happy to see this being used :) And thanks for fixing a fault. I'll have to check out the change and update the source.