Skip to content

Instantly share code, notes, and snippets.

@birdbrainiac
Last active September 18, 2021 05:45
Show Gist options
  • Save birdbrainiac/4e92f46651b3d8cf8db917e4f7b09a7c to your computer and use it in GitHub Desktop.
Save birdbrainiac/4e92f46651b3d8cf8db917e4f7b09a7c to your computer and use it in GitHub Desktop.
[Roll20] Pacesetter Dice Rolls Script
/* Structure
Input is in the form !pace --name: --type: --score: --defense: --defensecolumn:
name: is optyional, and can be anything, of any length, just avoid including -- characetrs,
Can use attributes, like @{selected|token_name}
type: armed, unarmed or fear; can also use a or u or f.
score: a number, the score you are rolling against; can be an attribute, like @{selected|blaster}
use only one of the following:
defense/defence: the score you are rolling against - will be converted into a column
defensecolumn/defencecolumn: the column you are rolling against. Can supply this instead of a score.
Create a macro:
!pace --type:?{Attack type?|Armed|Unarmed|Fear} --score:?{Attack Value?|0} --defense:?{Defense Value?|0}
*/
const pacesetter = (() => {
'use strict';
/* CONFIG SETTINGs */
const defence = 'defense'; // use defence or defense - the spelling used for defense/defense, and defencecolumn/defensecolumn in the abobe
const hidedefence = 0; // 0 shows defence row, 1 hides it
const terseKB = 1; // 0 shows full knockdown text, 1 shows a terser version.
const failtext = 'Fail'; // enter the text you want to show on a failed or missed roll.
/* dont change anything below this line */
const effectTable = [
['LK', 'L', 'L', 'L', 'L', 'S', 'S', 'S', 'S', 'S'],
['M', 'M', 'L', 'LK', 'L', 'LK', 'L', 'S', 'S', 'S'],
['H', 'MK', 'M', 'L', 'L', 'L', 'L', 'L', 'LK', 'S'],
['C', 'H', 'MK', 'M', 'M', 'M', 'LK', 'L', 'L', 'L'],
['C', 'C', 'H', 'M', 'MK', 'M', 'M', 'MK', 'L', 'LK'],
['CK', 'CK', 'C', 'H', 'H', 'H', 'M', 'M', 'M', 'M', ],
['CK', 'CK', 'CK', 'CK', 'H', 'H', 'H', 'H', 'M', 'M', ],
['CK', 'CK', 'CK', 'CK', 'CK', 'HK', 'H', 'H', 'H', 'H', ],
['CK', 'CK', 'CK', 'CK', 'CK', 'CK', 'HK', 'HK', 'H', 'H', ],
['CK', 'CK', 'CK', 'CK', 'CK', 'CK', 'CK', 'HK', 'HK', 'H']
];
const tableResults = {
a: {
S: {type: 'Scratch', stun: '1d2', wounds: 1},
L: {type: 'Light Wound', stun: '1d10*2', wound: 2},
M: {type: 'Medium Wound', stun: '2d10*2', wound: 3},
H: {type: 'Heavy Wound', stun: '3d10*2', wound: 4},
C: {type: 'Crippling Wound', stun: '3d10*2', wound: 5},
K: ['{{Knockdown=Defender knocked down and back}}',
'{{Knockdown=Down and back.}}',]
},
u: {
S: {type: 'Scant Damage', stun: '1d2', wound: 0},
L: {type: 'Light Damage', stun: '1d10*2', wound: 0},
M: {type: 'Medium Damage', stun: '2d10*2', wound: 0},
H: {type: 'Harsh Damage', stun: '3d10*2', wound: 1},
C: {type: 'Crushing Damage', stun: '3d10*2', wound: 2}, // or called shot
K: ['{{Knockdown=Defender knocked down and back 5 feet; all uncompleted actions this round are cancelled.}}',
'{{Knockdown=Down and back 5 feet, lose action.}}']
},
f: {
S: {type: 'Scared', stun: '1d10', wound: 0, text: 'Runs away as fast as possible or hides for 1 round'},
L: {type: 'Loathing', stun: '1d10/2', wound: 0, text: 'Runs away as fast as possible or hides for 1 round'},
M: {type: 'Misgivings', stun: '1d10/2', wound: 0, text: 'Does not run away'},
H: {type: 'Heroism', stun: '0', wound: 0, text: 'All fear is overcome'},
C: {type: 'Heroism', stun: '0', wound: 0, text: 'All fear is overcome'}
}
};
const attackIndex = margin => margin >= 100 ? 9 : [0, 4, 9, 29, 49, 69, 89, 94, 99].findIndex(element => margin <= element);
const getSender = msg => {
const character = findObjs({ type: 'character', name: msg.who })[0];
const player = getObj('player', msg.playerid);
if (character) return 'character|'+character.id;
else return 'player|'+player.id;
};
const getPlayer = msg => getObj('player', msg.playerid).get('displayname');
const getFromArgs = (args, property, notfound = 0) => {
const found = args.find(element => element.includes(`${property}:`));
const result = (found && found.includes(':')) ? found.split(':')[1] : notfound;
return result;
};
const defaultName = type => type === 'u' ? 'Unarmed Attack' : type === 'a' ? 'Armed Attack' : 'Fear Check';
const handleInput = (msg) => {
if (msg.type === 'api' && msg.content.startsWith('!pace') )
{
const sender = getSender(msg);
const args = msg.content.split(/\s+--/);
const props = {};
const validTypes = ['a', 'u', 'f'];
props.attacktype = validTypes.includes(getFromArgs(args,'type')[0].toLowerCase()) ? getFromArgs(args,'type')[0].toLowerCase() : 'u';
props.attackvalue = +getFromArgs(args,'score', 0);
props.defence = +getFromArgs(args,defence, 0);
props.defencecolumn = props.defence > 0 ? Math.ceil(props.defence /15) : +getFromArgs(args,'column', 0);
props.name = getFromArgs(args,'name', defaultName(props.attacktype));
if (!props.hasOwnProperty('defencecolumn') || !props.hasOwnProperty('attackvalue') || !validTypes.includes(props.attacktype)) {
sendChat(`Pacesetter`, `/w "${getPlayer(msg)}" Error in Script Values`); //
return;
}
sendChat("PaceRoll", "[[1d100]]", function(ops) {
const output = {};
output.roll = ops[0].inlinerolls[0].results.rolls[0].results[0].v;
output.attackvalue = props.attackvalue - output.roll;
output.damageresult = output.attackvalue >= 0 ? effectTable[attackIndex(output.attackvalue)][props.defencecolumn -1] : failtext;
const table = tableResults[props.attacktype][output.damageresult[0]];
output.template = `&{template:default} {{name=${props.name} (${props.name !== defaultName(props.attacktype) ? `${defaultName(props.attacktype).split(' ')[0]}:` : ''} ${props.attackvalue})}} {{Roll=**${output.roll}**}}`
if(output.damageresult === failtext) {
if(props.attacktype === 'f') {
output.template += `{{Result=${failtext}}} `;
output.template += `{{action=Runs away as fast as possible or hides for 1 round}} `;
output.template += `{{Willpower=[[2d10]]}}`;
} else {
output.template += `{{Result=${failtext} }}`;
}
// neeed to put willpower roll here if type = f
} else {
if(props.attacktype === 'f') {
output.template += hidedefence ? '' : `{{${defence}=${props.defence > 0 ? `${props.defence} [` : ''} ${props.defencecolumn}${props.defence > 0 ? ` ]` : ''} }}` ;
output.template += `{{Result=${table.type} (${output.damageresult})}} {{action=${table.text}}} {{Willpower=[[ceil(${table.stun})]]}}`;
} else {
output.template += hidedefence ? '' : `{{${defence}=${props.defence > 0 ? `${props.defence} [` : ''} ${props.defencecolumn}${props.defence > 0 ? ` ]` : ''} }}` ;
output.template += `{{Result=${table.type} (${output.damageresult}) }} {{STA=[[${table.stun}]]}}`;
output.template += table.wound > 0 ? `{{Wounds=**${table.wound}** }}` : '';
output.template += output.damageresult.includes('K') ? tableResults[props.attacktype].K[terseKB] : '';
}
}
sendChat(sender,output.template);
});
}
};
const registerEventHandlers = () => {
on('chat:message', handleInput);
};
on('ready', () => {
'use strict';
registerEventHandlers();
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment