Here's the text of the script, as I have it: /* Structure Input is in the form !megs --av:sword:7 --ov:block:15 !megs --ev:strength:7 --ov:toughness:15 --shifts:3 !megs --av:7 --ov:15 --ev:7 --ov:15 !megs --title:full av --av:sword:7 --ov:block:15 --ev:strength:7 --ov:toughness:15 --attacker:wildstar --defender:batman !megs --title:full av --av:sword:@{selected|sword} --ov:block:@{target|block} --attacker:@{target|character_name} --defender:@{selected|character_name --description:An av roll is made --title: the topmost title --attacker name of the attacker; if present forms part of the title --defender name of the defender; if present forms part of the title --av acting value, can be av:number, or av:name:number --ov opposing value --ev effect value --rv resisting value --omod shifts to the action table column --rmod shifts to the effect table column --description note added after title and befire roll --note note added after roll effects */ // eslint-disable-next-line no-unused-vars const dcheroes = (() => { 'use strict'; const editionFactor = 8; const rollTarget = [ 3, 4, 5, 7, 9, 11, 13, 15, 18, 21, 24, 28, 32, 36, 40, 45, 50, 55, 60, 65, 70, 75, 80]; // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const rollTarget_0 = [6, 5, 4, 4, 3]; // eslint-disable-next-line no-unused-vars const effectTable = [ // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // 0 1-2 3-4 5- 7- 9- 11 13 16 19 22 25 28 31 36 41 46 51 56 [ 'A', 1, 'N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N' ], [ 'A', 2, 1, 'N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N' ], [ 'A', 3, 2, 1, 'N','N','N','N','N','N','N','N','N','N','N','N','N','N','N' ], [ 'A', 5, 4, 3, 2, 'N','N','N','N','N','N','N','N','N','N','N','N','N','N' ], [ 'A', 8, 6, 4, 3, 2, 'N','N','N','N','N','N','N','N','N','N','N','N','N' ], [ 'A', 10, 9, 7, 6, 4, 3, 'N','N','N','N','N','N','N','N','N','N','N','N' ], [ 'A', 12, 11, 9, 8, 7, 5, 3, 'N','N','N','N','N','N','N','N','N','N','N' ], [ 'A', 14, 13, 11, 10, 9, 8, 6, 4, 'N','N','N','N','N','N','N','N','N','N' ], [ 'A', 18, 17, 16, 14, 12, 10, 8, 6, 4, 'N','N','N','N','N','N','N','N','N' ], [ 'A', 21, 20, 19, 17, 15, 13, 11, 9, 7, 5, 'N','N','N','N','N','N','N','N' ], [ 'A', 24, 23, 22, 20, 18, 16, 14, 12, 10, 8, 6, 'N','N','N','N','N','N','N' ], [ 'A', 27, 26, 25, 23, 21, 19, 17, 15, 13, 11, 9, 7, 'N','N','N','N','N','N' ], [ 'A', 30, 29, 28, 26, 24, 22, 20, 18, 16, 14, 12, 10, 8, 'N','N','N','N','N' ], [ 'A', 35, 34, 33, 31, 29, 27, 25, 23, 21, 19, 17, 14, 12, 9, 'N','N','N','N' ], [ 'A', 40, 38, 36, 34, 32, 30, 28, 26, 24, 22, 20, 18, 16, 13, 10, 'N','N','N' ], [ 'A', 45, 43, 41, 40, 38, 36, 34, 31, 28, 26, 24, 22, 20, 17, 14, 11, 'N','N' ], [ 'A', 50, 48, 46, 44, 42, 40, 38, 36, 34, 32, 30, 27, 24, 21, 18, 15, 12, 'N' ], [ 'A', 55, 53, 51, 49, 47, 45, 43, 41, 39, 36, 33, 30, 27, 24, 21, 18, 15, 13 ] ]; const scale = [0, 2, 4, 6, 8, 10, 12, 15, 18, 21, 24, 27, 30, 36, 40, 45, 50, 55, 60]; // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // get the row or column of a character's attribute. const getLevel =(score) => (score > 60) ? Math.ceil((score-60)/editionFactor) +18 : scale.findIndex(lvl => score <= lvl); const getRollLevel =(score) => (score > 80) ? Math.ceil((score-80)/editionFactor) +22 : rollTarget.findIndex(lvl => score < lvl); // get roll ev - doesnt account for shifts off the edge of the table const getDieString = (die1, die2) => `${(die1 === 10 ? 0 : die1)}${(die2 === 10 ? 0 : die2)}`; const getRoll = () => { let result = {total: 0, rolls: []}; let die1 = randomInteger(10); let die2 = randomInteger(10); result.rolls.push(getDieString(die1, die2)); result.total = die1 + die2; if(die1 > 1) { // a double 1 always fails. while(die1 === die2) { die1 = randomInteger(10); die2 = randomInteger(10); result.rolls.push(getDieString(die1, die2)); result.total = result.total + die1 + die2; } } return result; }; const getSender = msg => { const character = findObjs({ type: 'character', name: msg.who })[0], player = getObj('player', msg.playerid); if (character) return 'character|'+character.id; else return 'player|'+player.id; }; const getDieImage = roll => { log('roll: ' + roll); const die = 'JABCDEFGHI'; // 'jabcdefghi' const die1 = parseInt(roll[0]) || 0; const die2 = parseInt(roll[1]) || 0; const letters = `${die[die1]}${die[die2]}`; log(`die1: ${die1}; die2: ${die2}; letters: ${letters}`); return `<span style="${die1===die2 ? (die1===1 ? 'color: red; ' : 'color: green; ') : '' }font-size:2.2em; font-family: 'dicefontd10';">${letters}</span>`; }; const parseInput = (content) => { const valid_values = ['av', 'ov', 'ev', 'rv', 'omod', 'rmod']; const valid_descriptors = ['attacker', 'defender', 'note', 'description', 'title', 'shifts']; const output = content.replace(/<br\/>/g, '') // delete added HTML line breaks .replace(/\s+$/g, '') // delete trailing whitespace .split(/\s+--/) .slice(1) .reduce((m, arg) => { const prop = arg.split(':'); const keybase = prop[0].trim().toLowerCase(); if(prop.length === 1) { m[keybase] = false; // if there is no :, just return the entry with value as false } else if(valid_values.includes(keybase)) { if(prop.length > 2) { // if 3 items, convert to name and value m[`${keybase}_name`] = prop[1]; // eg: av: fist: 7 (probably should convert to a number) m[`${keybase}_value`] = parseInt(prop[2]) || -1; } else { m[`${keybase}_value`] = parseInt(prop[1]) || -1; } } else if(valid_descriptors.includes(keybase)) { m[keybase] = prop[1]; } else { // not valid descriptor of value, add to error. if (m.hasOwnProperty('invalid')) { m.invalid.push(prop[1]); } else { m.invalid = [prop[1]]; } } return m; }, {}); if(!output.hasOwnProperty('shifts')) output.shifts = 0; if(!output.hasOwnProperty('omod_value')) output.omod_value = 0; if(!output.hasOwnProperty('rmod_value')) output.rmod_value = 0; return output; }; const validateProps = (props) => { /* props might contain title attacker defender av_value ov_value ev_value rv_value omod_value rmod_value av_name ov_name ev_name rv_name omod_name rmod_name description props MUST contain at least av_value + ov_value or ev_value + rv_value */ }; // if roll is 11+ need to check if shifts occur // shifts only count from 11 const getShifts =(target, roll) => { const base_level = getRollLevel(roll); const target_level = getRollLevel(target < 11 ? 9 : target) ; return base_level > target_level ? base_level - target_level : 0; }; const rollAction = (av, ov, mod = 0) => { const output = {}; let av_rank = getLevel(av); let ov_rank = getLevel(ov) + mod; if(ov_rank < 0) { av_rank - ov_rank; ov_rank = 0; } if(ov === 0) { output.target = rollTarget_0[Math.min(av_rank, 4)]; } else { // difference = effective column to use; 5 = balanced, representing a needed roll of 11. const difference = 5 + ov_rank - av_rank; output.target = difference > 22 ? 80 + editionFactor * (difference -22) : rollTarget[Math.max(0, difference)]; } output.roll = getRoll(); output.hit = (output.roll.total >= output.target) ? 1 : 0; output.shifts = (output.hit && output.roll.total >10) ? getShifts(output.target, output.roll.total) : 0; return output; }; const rollEffect = (ev, rv, mod = 0, shifts = 0) => { let RAP; let ev_rank = getLevel(ev); let rv_rank = getLevel(rv) + mod; log(`ev rank: ${ev_rank}; rv_rank: ${rv_rank}; shifts: ${shifts}`); if(ev_rank > 18 || rv_rank > 18) { const difference = Math.max(ev_rank, rv_rank) - 18; ev_rank -= difference; rv_rank -= difference; } rv_rank -= shifts; log(`ev rank: ${ev_rank}; rv_rank: ${rv_rank}`); if(rv_rank <= 0) { RAP = ev - rv_rank; } else { RAP = effectTable[ev_rank -1][rv_rank]; } log(`row: ${effectTable[ev_rank -1].join('| ')}`); return RAP; }; const simplePrintout = (props, attack) => { log(props); log(attack); const templateline = (key, value) => `{{${key}=${value}}}`; const titleline = (entry) => `{{name=${entry}}}`; // simplify the following code by checking if some quantities exist const hit = attack.hasOwnProperty('hit') ? attack.hit : 0; // 0 = no names, 1 = attacker only, 2 = attacker and defender const characternames = props.hasOwnProperty('attacker') ? (props.hasOwnProperty('attacker') ? 2: 1) : 0; const attacknames = props.hasOwnProperty('av_name') ? (props.hasOwnProperty('ev_name') ? 2: 1) : 0; const defendnames = props.hasOwnProperty('ov_name') ? (props.hasOwnProperty('rv_name') ? 2: 1) : 0; const attackvalues = (props.hasOwnProperty('ov_value') & props.hasOwnProperty('av_value')) ? 1: 0; const defendvalues = (props.hasOwnProperty('ev_value') & props.hasOwnProperty('rv_value')) ? 1: 0; const print = []; print.push(`&{template:default}`); let title = ''; if(props.hasOwnProperty('title')) title += props.title; if(characternames > 0) { if(props.hasOwnProperty('title')) title += ': '; title += props.attacker; if(characternames >1) title += ` vs ${props.defender}`; if(props.hasOwnProperty('title')) title += ')'; } if(!title) title = 'Action Roll'; print.push(titleline(title)); if(props.hasOwnProperty('description')) print.push(templateline('description', props.description)); let attackline = ''; if(attackvalues > 0) attackline = `${attacknames > 0 ? props.av_name: 'AV'}: ${props.av_value}`; if(hit && attackvalues > 0) attackline += `; ${attacknames > 1 ? props.ev_name: 'EV'}: ${props.ev_value}`; print.push(templateline('Attack', attackline)); let defendline = ''; if(defendvalues > 0) defendline = `${defendnames > 0 ? props.ov_name: 'OV'}: ${props.ov_value}`; if(hit && defendvalues > 0) defendline += `; ${defendnames > 1 ? props.rv_name: 'EV'}: ${props.rv_value}`; print.push(templateline('Defence', defendline)); // add line for manoevers if(attackvalues > 0) { if(props.hasOwnProperty('omod_value') && props.omod_value !== 0) { print.push(templateline('O-Mod', `${props.hasOwnProperty('omod_name') ? `${props.omod_name}: ` : ''}${props.omod_value}`)); } print.push(templateline('Target', attack.target)); let roll = `${attack.roll.rolls.map(r => getDieImage(r)).join(' ')}`; print.push(templateline('Roll', roll)); print.push(templateline('Total', attack.roll.total)); print.push(templateline('Result', hit ? `Hit ${attack.shifts ? ` with ${attack.shifts} Shifts` : ''}` : 'Miss')); } if(hit && defendvalues > 0 && attack.hasOwnProperty('raps')) { if(props.hasOwnProperty('rmod_value') && props.rmod_value !== 0) { print.push(templateline('R-Mod', `${props.hasOwnProperty('rmod_name') ? `${props.rmod_name}: ` : ''}${props.rmod_value}`)); } print.push(templateline('RAPs', attack.raps)); } if(props.hasOwnProperty('note')) print.push(templateline('Note', props.note)); return print.join(' '); }; const handleInput = (msg) => { if (msg.type === 'api' && msg.content.startsWith('!megs') ) { const sender = getSender(msg); const props = parseInput(msg.content); log(props); let attack = {}; if(props.hasOwnProperty('av_value') && props.hasOwnProperty('ov_value')) { attack = rollAction(props.av_value, props.ov_value, props.omod_value); } // temporary printout for testing if(props.hasOwnProperty('ev_value') && props.hasOwnProperty('rv_value')) { attack.raps = rollEffect(props.ev_value, props.rv_value, props.rmod_value, attack.shifts); } const simpleprint = simplePrintout(props, attack); sendChat(sender,simpleprint); } }; const registerEventHandlers = () => { on('chat:message', handleInput); }; on('ready', () => { 'use strict'; registerEventHandlers(); }); })();