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 .
×
Create a free account

DC Heroes tables lookup API missing?

A while back Dave asked if anyone could make an API to look up tables for the DC Heroes/Blood of Heroes system. GiGs made one, but the links are now dead. Could GiGs or Dave or anyone who has access to the API repost it? I want to run a DC Heroes one-shot this Christmas and this would be a great addition. Original forum post:&nbsp; <a href="https://app.roll20.net/forum/post/8151776/can-it-be-done-api-to-create-automated-table-lookup/" rel="nofollow">https://app.roll20.net/forum/post/8151776/can-it-be-done-api-to-create-automated-table-lookup/</a>
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 = (() =&gt; { '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) =&gt; (score &gt; 60) ? Math.ceil((score-60)/editionFactor) +18 : scale.findIndex(lvl =&gt; score &lt;= lvl); const getRollLevel =(score) =&gt; (score &gt; 80) ? Math.ceil((score-80)/editionFactor) +22 : rollTarget.findIndex(lvl =&gt; score &lt; lvl); // get roll ev - doesnt account for shifts off the edge of the table const getDieString = (die1, die2) =&gt; `${(die1 === 10 ? 0 : die1)}${(die2 === 10 ? 0 : die2)}`; const getRoll = () =&gt; { 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 &gt; 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 =&gt; { 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 =&gt; { 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 `&lt;span style="${die1===die2 ? (die1===1 ? 'color: red; ' : 'color: green; ') : '' }font-size:2.2em; font-family: 'dicefontd10';"&gt;${letters}&lt;/span&gt;`; }; const parseInput = (content) =&gt; { const valid_values = ['av', 'ov', 'ev', 'rv', 'omod', 'rmod']; const valid_descriptors = ['attacker', 'defender', 'note', 'description', 'title', 'shifts']; const output = content.replace(/&lt;br\/&gt;/g, '') // delete added HTML line breaks .replace(/\s+$/g, '') // delete trailing whitespace .split(/\s+--/) .slice(1) .reduce((m, arg) =&gt; { 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 &gt; 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) =&gt; { /* 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) =&gt; { const base_level = getRollLevel(roll); const target_level = getRollLevel(target &lt; 11 ? 9 : target) ; return base_level &gt; target_level ? base_level - target_level : 0; }; const rollAction = (av, ov, mod = 0) =&gt; { const output = {}; let av_rank = getLevel(av); let ov_rank = getLevel(ov) + mod; if(ov_rank &lt; 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 &gt; 22 ? 80 + editionFactor * (difference -22) : rollTarget[Math.max(0, difference)]; } output.roll = getRoll(); output.hit = (output.roll.total &gt;= output.target) ? 1 : 0; output.shifts = (output.hit &amp;&amp; output.roll.total &gt;10) ? getShifts(output.target, output.roll.total) : 0; return output; }; const rollEffect = (ev, rv, mod = 0, shifts = 0) =&gt; { 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 &gt; 18 || rv_rank &gt; 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 &lt;= 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) =&gt; { log(props); log(attack); const templateline = (key, value) =&gt; `{{${key}=${value}}}`; const titleline = (entry) =&gt; `{{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') &amp; props.hasOwnProperty('av_value')) ? 1: 0; const defendvalues = (props.hasOwnProperty('ev_value') &amp; props.hasOwnProperty('rv_value')) ? 1: 0; const print = []; print.push(`&amp;{template:default}`); let title = ''; if(props.hasOwnProperty('title')) title += props.title; if(characternames &gt; 0) { if(props.hasOwnProperty('title')) title += ': '; title += props.attacker; if(characternames &gt;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 &gt; 0) attackline = `${attacknames &gt; 0 ? props.av_name: 'AV'}: ${props.av_value}`; if(hit &amp;&amp; attackvalues &gt; 0) attackline += `; ${attacknames &gt; 1 ? props.ev_name: 'EV'}: ${props.ev_value}`; print.push(templateline('Attack', attackline)); let defendline = ''; if(defendvalues &gt; 0) defendline = `${defendnames &gt; 0 ? props.ov_name: 'OV'}: ${props.ov_value}`; if(hit &amp;&amp; defendvalues &gt; 0) defendline += `; ${defendnames &gt; 1 ? props.rv_name: 'EV'}: ${props.rv_value}`; print.push(templateline('Defence', defendline)); // add line for manoevers if(attackvalues &gt; 0) { if(props.hasOwnProperty('omod_value') &amp;&amp; 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 =&gt; 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 &amp;&amp; defendvalues &gt; 0 &amp;&amp; attack.hasOwnProperty('raps')) { if(props.hasOwnProperty('rmod_value') &amp;&amp; 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) =&gt; { if (msg.type === 'api' &amp;&amp; msg.content.startsWith('!megs') ) { const sender = getSender(msg); const props = parseInput(msg.content); log(props); let attack = {}; if(props.hasOwnProperty('av_value') &amp;&amp; props.hasOwnProperty('ov_value')) { attack = rollAction(props.av_value, props.ov_value, props.omod_value); } // temporary printout for testing if(props.hasOwnProperty('ev_value') &amp;&amp; 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 = () =&gt; { on('chat:message', handleInput); }; on('ready', () =&gt; { 'use strict'; registerEventHandlers(); }); })();
Thanks!