// GroupCheck version 1.0
// Last Updated: 2016-11-22
// A script to roll checks for many tokens at once with one command.
var groupCheck = groupCheck || (function() {
'use strict';
const version = '1.0',
stateVersion = 5,
// Roll appearance
outputStyle = function () {
const makeBox = function (header, subheader, freetext, content) {
return '
' +
makeHeader(header) + makeSubheader(subheader) + '
' +
makeContent(content) + makeFreetext(freetext) + '
';
},
makeContent = function (text) {
return '';
},
makeHeader = function (text) {
return '' + text + '
';
},
makeSubheader = function (text) {
return text ? '' + text + '
' : '';
},
makeFreetext = function (text) {
return text ? '' + text + '
' : '';
},
makeRow = function (pic, name, roll2, r0, r1, appendix) {
return '' + makeName(pic,name) +
(roll2 ? makeRoll2(r0, r1) : makeRoll(r0, appendix)) + '
';
},
makeName = function (pic, name) {
return '' +
(pic ? ' ' : '') + ' | ' +
name + ' |
| ';
},
makeRoll = function (text, appendix) {
return '${text}${appendix} | `;
},
makeRoll2 = function (r0,r1) {
return makeRoll(r0,'') + makeRoll(r1,'');
},
makeInlineroll = function(roll, hideformula) {
let boundary = '';
switch (detectCritical(roll.results)) {
case 'crit' :
boundary = ';border:2px solid #3FB315';
break;
case 'mixed' :
boundary = ';border:2px solid #4A57ED';
break;
case 'fumble' :
boundary = ';border:2px solid #B31515';
}
return '' +
(roll.results.total || 0) + '
';
},
rollToText = function(roll) {
switch (roll.type) {
case 'R' :
let c = (roll.mods && roll.mods.customCrit) ||
[{ comp : '==', point : roll.sides}],
f = (roll.mods && roll.mods.customFumble) ||
[{ comp : '==', point : 1}],
styledRolls = _.map(roll.results, function (r) {
let style = rollIsCrit(r.v, c[0].comp, c[0].point) ?
' critsuccess' :
(rollIsCrit(r.v, f[0].comp, f[0].point) ?
' critfail' : '')
return `${r.v}`;
});
return `(${styledRolls.join('+')})`;
break;
case 'M' :
return roll.expr.toString().replace(/(\+|-)/g,'$1 ');
break;
case 'V' :
return _.map(roll.rolls, rollToText).join(' ');
break;
case 'G' :
return '(' + _.map(roll.rolls, a => _.map(a, rollToText).join(' '))
.join(' ') + ')';
break;
default :
return '';
}
};
return {
makeBox: makeBox,
makeInlineroll: makeInlineroll,
makeRow: makeRow
};
}(),
// Data variables
importData = {
'5E-Shaped' : {
'Strength Save': { 'name' : 'Strength Saving Throw', 'formula' : '[[d20 + %strength_saving_throw_mod%]]' },
'Dexterity Save': { 'name' : 'Dexterity Saving Throw', 'formula' : '[[d20 + %dexterity_saving_throw_mod%]]' },
'Constitution Save': { 'name' : 'Constitution Saving Throw', 'formula' : '[[d20 + %constitution_saving_throw_mod%]]' },
'Intelligence Save': { 'name' : 'Intelligence Saving Throw', 'formula' : '[[d20 + %intelligence_saving_throw_mod%]]' },
'Wisdom Save': { 'name' : 'Wisdom Saving Throw', 'formula' : '[[d20 + %wisdom_saving_throw_mod%]]' },
'Charisma Save': { 'name' : 'Charisma Saving Throw', 'formula' : '[[d20 + %charisma_saving_throw_mod%]]' },
// 'Fortitude Save': { 'name' : 'Fortitude Saving Throw', 'formula' : '[[d20 + %fortitude_saving_throw_mod%]]' },
// 'Reflex Save': { 'name' : 'Reflex Saving Throw', 'formula' : '[[d20 + %reflex_saving_throw_mod%]]' },
// 'Will Save': { 'name' : 'Will Saving Throw', 'formula' : '[[d20 + %will_saving_throw_mod%]]' },
'Strength Check': { 'name' : 'Strength Check', 'formula' : '[[d20 + %strength_check_mod_formula%]]' },
'Dexterity Check': { 'name' : 'Dexterity Check', 'formula' : '[[d20 + %dexterity_check_mod_formula%]]' },
'Constitution Check': { 'name' : 'Constitution Check', 'formula' : '[[d20 + %constitution_check_mod_formula%]]' },
'Intelligence Check': { 'name' : 'Intelligence Check', 'formula' : '[[d20 + %intelligence_check_mod_formula%]]' },
'Wisdom Check': { 'name' : 'Wisdom Check', 'formula' : '[[d20 + %wisdom_check_mod_formula%]]' },
'Charisma Check': { 'name' : 'Charisma Check', 'formula' : '[[d20 + %charisma_check_mod_formula%]]' },
'Acrobatics': { 'name' : 'Dexterity (Acrobatics) Check', 'formula' : '[[d20 + %repeating_skill_$0_formula%]]' },
'Animal Handling': { 'name' : 'Wisdom (Animal Handling) Check', 'formula' : '[[d20 + %repeating_skill_$1_formula%]]' },
'Arcana': { 'name' : 'Intelligence (Arcana) Check', 'formula' : '[[d20 + %repeating_skill_$2_formula%]]' },
'Athletics': { 'name' : 'Strength (Athletics) Check', 'formula' : '[[d20 + %repeating_skill_$3_formula%]]' },
'Deception': { 'name' : 'Charisma (Deception) Check', 'formula' : '[[d20 + %repeating_skill_$4_formula%]]' },
'History': { 'name' : 'Intelligence (History) Check', 'formula' : '[[d20 + %repeating_skill_$5_formula%]]' },
'Insight': { 'name' : 'Wisdom (Insight) Check', 'formula' : '[[d20 + %repeating_skill_$6_formula%]]' },
'Intimidation': { 'name' : 'Charisma (Intimidation) Check', 'formula' : '[[d20 + %repeating_skill_$7_formula%]]' },
'Investigation': { 'name' : 'Intelligence (Investigation) Check', 'formula' : '[[d20 + %repeating_skill_$8_formula%]]' },
'Medicine': { 'name' : 'Wisdom (Medicine) Check', 'formula' : '[[d20 + %repeating_skill_$9_formula%]]' },
'Nature': { 'name' : 'Intelligence (Nature) Check', 'formula' : '[[d20 + %repeating_skill_$10_formula%]]' },
'Perception': { 'name' : 'Wisdom (Perception) Check', 'formula' : '[[d20 + %repeating_skill_$11_formula%]]' },
'Performance': { 'name' : 'Charisma (Performance) Check', 'formula' : '[[d20 + %repeating_skill_$12_formula%]]' },
'Persuasion': { 'name' : 'Charisma (Persuasion) Check', 'formula' : '[[d20 + %repeating_skill_$13_formula%]]' },
'Religion': { 'name' : 'Intelligence (Religion) Check', 'formula' : '[[d20 + %repeating_skill_$14_formula%]]' },
'Sleight of Hand': { 'name' : 'Dexterity (Sleight of Hand) Check', 'formula' : '[[d20 + %repeating_skill_$15_formula%]]' },
'Stealth': { 'name' : 'Dexterity (Stealth) Check', 'formula' : '[[d20 + %repeating_skill_$16_formula%]]' },
'Survival': { 'name' : 'Wisdom (Survival) Check', 'formula' : '[[d20 + %repeating_skill_$17_formula%]]' },
'AC' : { 'name' : 'Armor Class', 'formula' : '%AC%'}
},
'Pathfinder' : {
'Fortitude Save': { 'name' : 'Fortitude Saving Throw', 'formula' : '[[d20 + %Fort%]]' },
'Reflex Save': { 'name' : 'Reflex Saving Throw', 'formula' : '[[d20 + %Ref%]]' },
'Will Save': { 'name' : 'Will Saving Throw', 'formula' : '[[d20 + %Will%]]' },
'Strength Check': { 'name' : 'Strength Check', 'formula' : '[[d20 + %STR-mod% + %checks-cond%]]' },
'Dexterity Check': { 'name' : 'Dexterity Check', 'formula' : '[[d20 + %DEX-mod% + %checks-cond%]]' },
'Constitution Check': { 'name' : 'Constitution Check', 'formula' : '[[d20 + %CON-mod% + %checks-cond%]]' },
'Intelligence Check': { 'name' : 'Intelligence Check', 'formula' : '[[d20 + %INT-mod% + %checks-cond%]]' },
'Wisdom Check': { 'name' : 'Wisdom Check', 'formula' : '[[d20 + %WIS-mod% + %checks-cond%]]' },
'Charisma Check': { 'name' : 'Charisma Check', 'formula' : '[[d20 + %CHA-mod% + %checks-cond%]]' },
'Perception': { 'name' : 'Perception Check', 'formula' : '[[d20 + %Perception%]]'},
'Stealth' : { 'name' : 'Stealth Check', 'formula' : '[[d20 + %Stealth%]]'},
'AC' : { 'name' : 'Armor Class', 'formula' : '%AC%'}
},
'5E-OGL' : {
'Strength Save': { 'name' : 'Strength Saving Throw', 'formula' : '[[d20 + %strength_save_bonus% + %globalsavemod%]]' },
'Dexterity Save': { 'name' : 'Dexterity Saving Throw', 'formula' : '[[d20 + %dexterity_save_bonus% + %globalsavemod%]]' },
'Constitution Save': { 'name' : 'Constitution Saving Throw', 'formula' : '[[d20 + %constitution_save_bonus% + %globalsavemod%]]' },
'Intelligence Save': { 'name' : 'Intelligence Saving Throw', 'formula' : '[[d20 + %intelligence_save_bonus% + %globalsavemod%]]' },
'Wisdom Save': { 'name' : 'Wisdom Saving Throw', 'formula' : '[[d20 + %wisdom_save_bonus% + %globalsavemod%]]' },
'Charisma Save': { 'name' : 'Charisma Saving Throw', 'formula' : '[[d20 + %charisma_save_bonus% + %globalsavemod%]]' },
'Strength Check': { 'name' : 'Strength Check', 'formula' : '[[d20 + %strength_mod%]]' },
'Dexterity Check': { 'name' : 'Dexterity Check', 'formula' : '[[d20 + %dexterity_mod%]]' },
'Constitution Check': { 'name' : 'Constitution Check', 'formula' : '[[d20 + %constitution_mod%]]' },
'Intelligence Check': { 'name' : 'Intelligence Check', 'formula' : '[[d20 + %intelligence_mod%]]' },
'Wisdom Check': { 'name' : 'Wisdom Check', 'formula' : '[[d20 + %wisdom_mod%]]' },
'Charisma Check': { 'name' : 'Charisma Check', 'formula' : '[[d20 + %charisma_mod%]]' },
'Acrobatics': { 'name' : 'Dexterity (Acrobatics) Check', 'formula' : '[[d20 + %acrobatics_bonus%]]' },
'Animal Handling': { 'name' : 'Wisdom (Animal Handling) Check', 'formula' : '[[d20 + %animal_handling_bonus%]]' },
'Arcana': { 'name' : 'Intelligence (Arcana) Check', 'formula' : '[[d20 + %arcana_bonus%]]' },
'Athletics': { 'name' : 'Strength (Athletics) Check', 'formula' : '[[d20 + %athletics_bonus%]]' },
'Deception': { 'name' : 'Charisma (Deception) Check', 'formula' : '[[d20 + %deception_bonus%]]' },
'History': { 'name' : 'Intelligence (History) Check', 'formula' : '[[d20 + %history_bonus%]]' },
'Insight': { 'name' : 'Wisdom (Insight) Check', 'formula' : '[[d20 + %insight_bonus%]]' },
'Intimidation': { 'name' : 'Charisma (Intimidation) Check', 'formula' : '[[d20 + %intimidation_bonus%]]' },
'Investigation': { 'name' : 'Intelligence (Investigation) Check', 'formula' : '[[d20 + %investigation_bonus%]]' },
'Medicine': { 'name' : 'Wisdom (Medicine) Check', 'formula' : '[[d20 + %medicine_bonus%]]' },
'Nature': { 'name' : 'Intelligence (Nature) Check', 'formula' : '[[d20 + %nature_bonus%]]' },
'Perception': { 'name' : 'Wisdom (Perception) Check', 'formula' : '[[d20 + %perception_bonus%]]' },
'Performance': { 'name' : 'Charisma (Performance) Check', 'formula' : '[[d20 + %performance_bonus%]]' },
'Persuasion': { 'name' : 'Charisma (Persuasion) Check', 'formula' : '[[d20 + %persuasion_bonus%]]' },
'Religion': { 'name' : 'Intelligence (Religion) Check', 'formula' : '[[d20 + %religion_bonus%]]' },
'Sleight of Hand': { 'name' : 'Dexterity (Sleight of Hand) Check', 'formula' : '[[d20 + %sleight_of_hand_bonus%]]' },
'Stealth': { 'name' : 'Dexterity (Stealth) Check', 'formula' : '[[d20 + %stealth_bonus%]]' },
'Survival': { 'name' : 'Wisdom (Survival) Check', 'formula' : '[[d20 + %survival_bonus%]]' },
'AC' : { 'name' : 'Armor Class', 'formula' : '%AC%' }
},
'3.5' : {
'Fortitude Save': { 'name' : 'Fortitude Saving Throw', 'formula' : '[[d20 + %fortitude%]]' },
'Reflex Save': { 'name' : 'Reflex Saving Throw', 'formula' : '[[d20 + %reflex%]]' },
'Will Save': { 'name' : 'Will Saving Throw', 'formula' : '[[d20 + %wisdom%]]' },
'Strength Check': { 'name' : 'Strength Check', 'formula' : '[[d20 + %str-mod%]]' },
'Dexterity Check': { 'name' : 'Dexterity Check', 'formula' : '[[d20 + %dex-mod%]]' },
'Constitution Check': { 'name' : 'Constitution Check', 'formula' : '[[d20 + %con-mod%]]' },
'Intelligence Check': { 'name' : 'Intelligence Check', 'formula' : '[[d20 + %int-mod%]]' },
'Wisdom Check': { 'name' : 'Wisdom Check', 'formula' : '[[d20 + %wis-mod%]]' },
'Charisma Check': { 'name' : 'Charisma Check', 'formula' : '[[d20 + %cha-mod%]]' },
'Hide' : { 'name' : 'Hide Check', 'formula' : '[[d20 + %hide%]]' },
'Listen': { 'name' : 'Listen Check', 'formula' : '[[d20 + %listen%]]' },
'Move Silently' : { 'name' : 'Move Silently Check', 'formula' : '[[d20 + %movesilent%]]' },
'Spot': { 'name' : 'Spot Check', 'formula' : '[[d20 + %spot%]]' },
'AC' : { 'name' : 'Armor Class', 'formula' : '%armorclass%' }
}
},
optsData = {
list : {
ro : {type: 'string', def: 'roll1',
admissible: ['roll1', 'roll2', 'adv', 'dis', 'rollsetting']},
die_adv : {type: 'string', def: '2d20kh1'},
die_dis : {type: 'string', def: '2d20kl1'},
fallback : {type: 'string'},
globalmod : {type: 'string'},
subheader : {type: 'string', local: true},
custom : {type: 'string', local: true},
'apply-change' : {type: 'string', local: true},
whisper : {type: 'bool', def: false, negate : 'public'},
hideformula : {type: 'bool', def: false, negate : 'showformula'},
usetokenname : {type: 'bool', def: true, negate : 'usecharname'},
showpicture : {type: 'bool', def: true, negate : 'hidepicture'},
process : {type: 'bool', def: false, negate : 'direct'},
showaverage : {type: 'bool', local: true}
},
meta : {}
},
// Setup
checkInstall = function() {
if (!state.groupCheck) {
initializeState();
}
else if (state.groupCheck.version < stateVersion) {
updateState();
}
// Build metadata for available options
optsData.meta = {
allopts : _.keys(optsData.list),
str : _.chain(optsData.list)
.pick(v => v.type === 'string')
.keys()
.value(),
glob : _.chain(optsData.list)
.omit(v => v.local)
.keys()
.value(),
bool : _.chain(optsData.list)
.pick(v => v.type === 'bool')
.keys()
.value(),
boolNeg : _.chain(optsData.list)
.pick(v => v.type === 'bool')
.pluck('negate')
.value()
};
log('-=> groupCheck v'+version+' <=-');
},
initializeState = function() {
state.groupCheck = {
'checkList' : {},
'options' : _.chain(optsData.list)
.pick(v => _.has(v,'def'))
.mapObject(v => v.def)
.value(),
'version' : stateVersion
};
log('-=> groupCheck initialized with default settings!<=-');
},
updateState = function() {
if (state.groupCheck.version == 1) {
_.each(state.groupCheck.checkList, function(check) {
let die = check.die || state.groupCheck.options.die;
check.formula = _.union([die], _.map(check.mod, str => '%'+str+'%'))
.join(' + ');
delete check.mod;
});
delete state.groupCheck.options.die;
state.groupCheck.options.hideformula = state.groupCheck.options.hidebonus;
delete state.groupCheck.options.hidebonus;
state.groupCheck.version = 2;
log('-=> groupCheck has updated to a new data format (1=>2). Please make' +
' sure your list of checks has converted correctly.<=-');
}
if (state.groupCheck.version == 2) {
_.each(state.groupCheck.checkList, function(check) {
check.formula = '[[' + check.formula+ ']]';
});
log('-=> groupCheck has updated to a new data format (2=>3). Please make' +
' sure your list of checks has converted correctly.<=-');
state.groupCheck.version = 3;
}
if (state.groupCheck.version == 3) {
state.groupCheck.options.showpicture = true;
state.groupCheck.version = 4;
}
if (state.groupCheck.version == 4) {
state.groupCheck.options.process = false;
state.groupCheck.version = 5;
}
},
// Utility functions
safeReadJSON = function (string) {
try {
let o = JSON.parse(string);
if (o && typeof o === 'object') {
return o;
}
}
catch (e) { }
return false;
},
sendChatNoarchive = function(who, string) {
sendChat(who, string);
},
recoverInlinerollFormulae = function (msg) {
if (_.has(msg, 'inlinerolls')) {
return _.chain(msg.inlinerolls)
.reduce(function(m, v, k) {
m['$[['+k+']]'] = '[['+v.expression+']]';
return m;
},{})
.reduce((m, v, k) => m.replace(k, v), msg.content)
.value();
} else {
return msg.content;
}
},
htmlReplace = function (str) {
let entities = {
'<' : 'lt',
'>' : 'gt',
"'" : '#39',
'@' : '#64',
'{' : '#123',
'|' : '#124',
'}' : '#125',
'[' : '#91',
']' : '#93',
'"' : 'quot',
'-' : 'mdash',
' ' : 'nbsp'
};
return _.map(str.split(''), c => (_.has(entities,c)) ? ('&'+entities[c]+';') : c)
.join('');
},
getPlayerName = function(who) {
let match = who.match(/(.*) \(GM\)/);
return match ? (match[1] || 'GM') : (who || 'GM');
},
handleError = function(who, errorMsg) {
let output = `/w "${who}" `;
sendChatNoarchive('GroupCheck', output);
},
printHelp = function(who) {
let helpString = `/w "${who}" Please refer to the ' +
'
documentation' +
' for help with using GroupCheck, or ask in the API forum thread.
';
sendChatNoarchive('GroupCheck', helpString);
},
printCommandMenu = function(who, opts) {
let optsCommand = _.map(opts, function (value, key) {
return (typeof value === 'boolean') ? `--${key}` : `--${key} ${value}`;
}).join(' ');
let commandOutput = `/w "${who}" ' +
'
Available commands:
' +
_.map(_.keys(state.groupCheck.checkList), function (s) {
return `[${s}](!group-check ${optsCommand} --${s})`;
}).join('') +
'
';
sendChatNoarchive('GroupCheck', commandOutput);
},
getConfigTable = function() {
let output = 'Current Options
| Name | Value |
' +
_.map(state.groupCheck.options, function(value, key) {
return `| ${key} | ${value} |
`;
}).join('') +
'
';
output += 'Checks
| Command | Name | ' +
'Formula |
' +
_.map(state.groupCheck.checkList, function(value, key) {
return `| ${key} | ${value.name} | ` +
`${htmlReplace(value.formula)} |
`;
}).join('') +
'
';
return output;
},
getRollOption = function(charid) {
if (charid) {
switch(getAttrByName(charid,"roll_setting")) {
case "{{ignore=[[0" :
return 'roll1';
break;
case "adv {{ignore=[[0":
return 'adv';
break;
case "dis {{ignore=[[0" :
return 'dis';
break;
}
}
return 'roll2';
},
parseOpts = function (content, hasValue) {
return _.chain(content.replace(/
\n/g, ' ')
.replace(/({{(.*?)\s*}}\s*$)/g, '$2')
.split(/\s+--/))
.rest()
.reduce(function (opts, arg) {
let kv = arg.split(/\s(.+)/);
(_.contains(hasValue, kv[0])) ? (opts[kv[0]] = (kv[1] || '')) :
(opts[arg] = true);
return opts;
}, {})
.value();
},
detectCritical = function(roll) {
let s = [];
if (roll.type === 'V' && _.has(roll, 'rolls')) {
s = _.map(roll.rolls, detectCritical);
} else if (roll.type === 'G' && _.has(roll, 'rolls')) {
s = _.chain(roll.rolls)
.map(a => _.map(a, detectCritical))
.flatten()
.value();
} else if (roll.type === 'R' && _.has(roll,'sides')) {
let crit = (roll.mods && roll.mods.customCrit) ||
[{ comp : '==', point : roll.sides}];
let fumble = (roll.mods && roll.mods.customFumble) ||
[{ comp : '==', point : 1}];
if (_.some(roll.results, r => rollIsCrit(r.v, crit[0].comp, crit[0].point))) {
s.push('crit');
}
if (_.some(roll.results, r => rollIsCrit(r.v, fumble[0].comp, fumble[0].point))) {
s.push('fumble');
}
}
let c = _.contains(s, 'crit');
let f = _.contains(s, 'fumble');
let m = _.contains(s, 'mixed') || (c && f);
return (m ? 'mixed' : (c ? 'crit' : (f ? 'fumble' : (false))));
},
rollIsCrit = function(value, comp, point) {
switch (comp) {
case '==' :
return value == point;
break;
case '<=' :
return value <= point;
break;
case '>=' :
return value >= point;
}
},
//Main functions
processTokenRollData = function(token, checkFormula, opts) {
let displayName, computedFormula, rollAppendix = '', charName, tokenPic;
const characterId = token.get('represents'),
ro = opts.rollOption(characterId),
character = getObj('character', characterId);
if (character) {
charName = character.get('name');
displayName = (opts.usetokenname) ? token.get('name') : charName;
computedFormula = checkFormula.join(charName);
} else if (opts.fallback) {
displayName = token.get('name');
computedFormula = checkFormula
.join('INSERT_NAME')
.replace(/@\{INSERT_NAME\|.*?\}/, opts.fallback)
.replace(/@\{INSERT_NAME\|.*?\}/g, '0');
} else {
return null;
}
tokenPic = (opts.showpicture || !displayName) ? token.get('imgsrc') : false;
switch (ro) {
case 'adv' :
rollAppendix = ' (Advantage)';
computedFormula = computedFormula.replace(/1?d20/, opts.die_adv);
break;
case 'dis' :
rollAppendix = ' (Disadvantage)';
computedFormula = computedFormula.replace(/1?d20/, opts.die_dis);
break;
}
return {
'pic' : tokenPic,
'name' : displayName,
'roll2' : (ro === 'roll2'),
'formula' : computedFormula,
'id' : token.id,
'appendix' : rollAppendix
}
},
sendFinalMessage = function (opts, checkName, rollData, msg) {
let freetext = '', match, inlinerollData = {};
if (_.has(msg[0], 'inlinerolls')) {
inlinerollData = _.reduce(msg[0].inlinerolls, function(r, c, i) {
r[`$[[${i}]]`] = {
result : c.results.total || 0,
styled : outputStyle.makeInlineroll(c, opts.hideformula)
};
return r;
},{});
}
_.each(msg[0].content.split('
'), function (value, j) {
_.each(value.split('####'), function (str,n) {
rollData[j]['result_' + n] = [];
match = str.match(/\$\[\[\d+\]\]/);
while (match) {
rollData[j]['result_' + n].push(inlinerollData[match[0]].result);
str = str.replace(match[0], inlinerollData[match[0]].styled);
match = str.match(/\$\[\[\d+\]\]/);
}
rollData[j]['styled_' + n] = str;
});
});
let rolls = _.map(rollData, function (o) {
return outputStyle.makeRow(o.pic, o.name, o.roll2, o['styled_0'],
o['styled_1'], o.appendix);
});
if (opts.showaverage) {
let fakeRoll = {results :
{
total : (Math.round(10 * (_.chain(rollData)
.map(o => o['result_0'][0]).reduce((p,c) => p + c, 0)).value() /
rollData.length) / 10)
}
};
rolls.push(outputStyle.makeRow('', 'Average of rolls', false,
outputStyle.makeInlineroll(fakeRoll, true), '', ''));
}
if (_.has(opts, 'apply-change')) {
let applyChangeOpts = (opts['apply-change'] || '')
.replace(/~/g, '--')
.replace(/RESULTS/, _.map(rollData, o => o['result_0'][0]).join(',')) +
' --ids ' + _.map(rollData, o => o.id).join(' ');
let button = `[stuff](apply-change ${applyChangeOpts} --ids ${ids})`;
}
let output = (opts.whisper ? '/w GM ' : '') +
outputStyle.makeBox(checkName, opts.subheader, freetext, rolls.join(''));
sendChat(opts.who, output);
},
handleConfig = function (msg) {
const hasValueConfig = ['import','add','delete','set'];
let opts = parseOpts(recoverInlinerollFormulae(msg), hasValueConfig);
let who = getPlayerName(msg.who), output;
if (!playerIsGM(msg.playerid)) {
sendChatNoarchive('GroupCheck', `/w "${who}" Permission denied.`);
return;
}
if (opts.import) {
if (_.has(importData,opts.import)) {
_.extend(state.groupCheck.checkList, importData[opts.import]);
output = `Data set ${opts.import} imported.`;
} else {
handleError(who, `Data set ${opts.import} not found.`);
}
} else if (opts.add) {
let data = safeReadJSON(opts.add);
if (_.isObject(data)) {
_.each(data, function (value, key) {
if (!(_.isObject(value) && _.has(value, 'name') &&
_.has(value,'formula') && _.isString(value.formula))) {
delete data[key];
}
});
_.extend(state.groupCheck.checkList, data);
output = 'Checks added. The imported JSON was:
'
+ htmlReplace(JSON.stringify(data));
} else {
handleError(who, 'Error reading input.');
}
} else if (opts.delete) {
if (_.has(state.groupCheck.checkList, opts.delete)) {
delete state.groupCheck.checkList[opts.delete];
output = `Check ${opts.delete} deleted.`;
} else {
handleError(who, `Check called ${opts.delete} not found.`);
}
} else if (opts.set) {
let kv = opts.set.split(/\s(.+)/);
if (_.indexOf(optsData.meta.str, kv[0]) !== -1 && _.indexOf(optsData.meta.glob, kv[0]) !== -1 ) {
state.groupCheck.options[kv[0]] = kv[1];
} else if (kv[0] === 'ro') {
if (_.indexOf(optsData.list.ro.admissible, kv[1]) !== -1) {
state.groupCheck.options.ro = kv[1];
} else {
handleError(who, `Roll option ${kv[1]} is invalid, sorry.`);
return;
}
} else if (_.indexOf(optsData.meta.bool, kv[0]) !== -1) {
state.groupCheck.options[kv[0]] = true;
} else if (_.indexOf(optsData.meta.boolNeg, kv[0]) !== -1) {
kv[0] = optsData.meta.bool[_.indexOf(optsData.meta.boolNeg, kv[0])];
state.groupCheck.options[kv[0]] = false;
} else {
handleError(who, 'Command not understood.');
return;
}
output = `Option ${kv[0]} set to ${state.groupCheck.options[kv[0]]}.`;
} else if (opts.clear) {
state.groupCheck.checkList = {};
output = 'All checks cleared.';
} else if (opts.defaults) {
state.groupCheck.options = defaultOptions;
output = 'All options reset to defaults.';
} else if (opts.reset) {
initializeState();
output = 'Everything is reset to factory settings.';
} else if (opts.show) {
output = getConfigTable();
} else {
printHelp(who);
}
if (output) {
sendChatNoarchive('GroupCheck', `/w "${who}" ${output}`);
}
return;
},
handleRolls = function (msg) {
// Options processing
let checkName, checkFormula,
who = getPlayerName(msg.who),
opts = parseOpts(recoverInlinerollFormulae(msg), optsData.meta.str),
checkCmd = _.intersection(_.keys(state.groupCheck.checkList), _.keys(opts))[0];
// Print menu if we don't know what to roll
if (opts.help) {
printHelp(who);
return;
}
if (!checkCmd && !opts.custom) {
printCommandMenu(who,opts);
return;
}
// Continue with options processing
if (checkCmd) {
checkFormula = state.groupCheck.checkList[checkCmd].formula
.replace(/\%(\S.*?)\%/g,'@{INSERT_NAME|$1}')
.split('INSERT_NAME');
checkName = state.groupCheck.checkList[checkCmd].name;
}
_.each(optsData.meta.boolNeg, function (name,index) {
if (opts[name]) {
opts[optsData.meta.bool[index]] = false;
}
});
// Handle --custom
if (opts.custom) {
let kv = opts.custom.split(/,\s?/);
if (kv.length < 2) {
handleError(who,"Custom roll format invalid");
return;
}
checkName = kv.shift();
checkFormula = kv.join()
.replace(/\%(\S.*?)\%/g, '@{INSERT_NAME|$1}')
.split('INSERT_NAME');
}
// Remove invalid options and check commands from opts
// Plug in defaults for unspecified options
opts = _.chain(opts)
.pick(optsData.meta.allopts)
.defaults(state.groupCheck.options)
.value();
// Eliminate invalid roll option.
if (_.indexOf(optsData.list.ro.admissible, opts.ro) === -1) {
handleError(who,'Roll option ' + opts.ro + ' is invalid, sorry.');
return;
}
// Get options into desired format
opts.rollOption = (opts.ro === 'rollsetting') ? getRollOption : ( (charid) => opts.ro);
opts.who = who;
// Adjust formula if opts.globalmod is given
if (opts.globalmod) {
if (checkFormula[checkFormula.length -1].search(/\]\](?=$)/) !== -1) {
checkFormula[checkFormula.length -1] = checkFormula[checkFormula.length -1]
.replace(/\]\](?=$)/, ' + ' + opts.globalmod + '[global modifier]]]');
} else {
checkFormula[checkFormula.length -1] += ` + ${opts.globalmod}[global]`;
}
}
// Transform tokens into nice data packages
let rollData = _.chain(msg.selected)
.map(obj => getObj('graphic', obj._id))
.compact()
.map(token => processTokenRollData(token, checkFormula, opts))
.compact()
.value();
try {
if (!opts.process) {
opts.multi = (opts.multi > 1 ) ? parseInt(opts.multi) : 1;
let rolls = _.map(rollData, function(o) {
let f = opts.hideformula ? `[[${o.formula}]]` : o.formula;
return outputStyle.makeRow(o.pic, o.name, o.roll2, f, f, o.appendix)
.repeat(opts.multi);
}).join('');
let output = (opts.whisper ? '/w GM ' : '') +
outputStyle.makeBox(checkName, opts.subheader, '', rolls);
sendChat(who, output);
} else {
let sentFormula = _.map(rollData, function(o) {
return (o.formula + '####' + (o.roll2 ? (o.formula) : ''));
}).join('
'),
callback = _.partial(sendFinalMessage, opts, checkName, rollData);
sendChat('', sentFormula, callback);
}
} catch(err) {
let errorMessage = 'Something went wrong with the roll. The command you tried was:'
+ '
' + msg.content + '
The error message generated by Roll20 is:'
+ '
' + err;
handleError(who, errorMessage);
}
},
handleInput = function(msg) {
if (msg.type === 'api' && msg.content.search(/^!group-check($|\s)/) != -1) {
handleRolls(msg);
} else if (msg.type === 'api' && msg.content.search(/^!group-check-config\b/) != -1) {
handleConfig(msg);
}
},
registerEventHandlers = function() {
on('chat:message', handleInput);
};
return {
CheckInstall: checkInstall,
RegisterEventHandlers: registerEventHandlers
};
}());
on('ready',function() {
'use strict';
groupCheck.CheckInstall();
groupCheck.RegisterEventHandlers();
});