Skip to content

Instantly share code, notes, and snippets.

@shdwjk
Last active October 17, 2015 14:29
Show Gist options
  • Save shdwjk/eda6313c93d51dbbfb8c to your computer and use it in GitHub Desktop.
Save shdwjk/eda6313c93d51dbbfb8c to your computer and use it in GitHub Desktop.
Roll20 API: Modification to Brian's RT script adding preprocessing for HoneyBadger's PowerCard Script with syntax to passed rolled tables with adjustments and values.
var RealRollableTable = RealRollableTable || (function() {
'use strict';
var version = 1.0003,
tables = _.groupBy(findObjs({ type: 'rollabletable' }), function(table) { return table.get('name').toLowerCase(); }),
powerCardFunction = function() {
},
getSystemId = function () {
// Get character id of `system`
var systemChar = findObjs({
type: 'character',
name: 'system',
controlledby: ''
}, { caseInsensitive: true })[0];
return systemChar ? systemChar.id : '';
},
getSystemFrom = function() {
// Get appropriate value of `who` parameter for `sendChat` to send messages by System
var systemChar, systemId;
if (state.RealRollableTable.systemId !== '') {
systemChar = getObj('character', state.RealRollableTable.systemId);
}
if (!systemChar) {
systemId = getSystemId();
state.RealRollableTable.systemId = systemId;
systemChar = getObj('character', state.RealRollableTable.systemId);
}
// If systemChar is undefined here, it doesn't exist
return systemChar ? 'character|' + systemChar.id : 'System';
},
levenshteinDistance = function(a, b) {
var i, j,
matrix = [];
if (a.length === 0) {
return b.length;
}
if (b.length === 0) {
return a.length;
}
// Increment along the first column of each row
for (i = 0; i <= b.length; i++) {
matrix[i] = [i];
}
// Increment each column in the first row
for (j = 0; j <= a.length; j++) {
matrix[0][j] = j;
}
// Fill in the rest of the matrix
for (i = 1; i <= b.length; i++) {
for (j = 1; j <= a.length; j++) {
if (b.charAt(i - 1) === a.charAt(j - 1)) {
matrix[i][j] = matrix[i - 1][j - 1];
} else {
matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, // Substitution
Math.min(matrix[i][j - 1] + 1, // Insertion
matrix[i - 1][j] + 1)); // Deletion
}
}
}
return matrix[b.length][a.length];
},
getWhisperTarget = function(options) {
var nameProperty, targets, type;
options = options || {};
if (options.player) {
nameProperty = 'displayname';
type = 'player';
} else if (options.character) {
nameProperty = 'name';
type = 'character';
} else {
return '';
}
if (options.id) {
targets = [getObj(type, options.id)];
if (targets[0]) {
return '/w ' + targets[0].get(nameProperty).split(' ')[0] + ' ';
}
}
if (options.name) {
targets = _.sortBy(filterObjs(function(obj) {
if (obj.get('type') !== type) {
return false;
}
return obj.get(nameProperty).indexOf(options.name) >= 0;
}), function(obj) {
return Math.abs(levenshteinDistance(obj.get(nameProperty), options.name));
});
if (targets[0]) {
return '/w ' + targets[0].get(nameProperty).split(' ')[0] + ' ';
}
}
return '';
},
getPlayerCharacterFrom = function(name) {
var character = findObjs({
type: 'character',
name: name
})[0],
player = findObjs({
type: 'player',
displayname: name.lastIndexOf(' (GM)') === name.length - 5 ? name.substring(0, name.length - 5) : name
})[0];
if (player) {
return 'player|' + player.id;
}
if (character) {
return 'character|' + character.id;
}
return name;
},
buildTableRoll = function(t,r,playerid) {
var table = tables[t.toLowerCase()],
items = [],
sides = 0,
item,
value,
tableItemIndex;
if (!table) {
table = findObjs({
type: 'rollabletable',
name: t
}, { caseInsensitive: true })[0];
if (table) {
tables[t.toLowerCase()] = table;
} else {
sendChat(getSystemFrom(), getWhisperTarget({ player: true, id: playerid }) +
'Could not find table "' + t + '". Please supply an existing table name.'
);
return r;
}
}
_.each(findObjs({
type: 'tableitem',
rollabletableid: table.id
}), function(element) {
var i,
weight = parseInt(element.get('weight'),10);
sides+= weight;
for (i = 0; i < weight; i++) {
items.push(element);
}
});
tableItemIndex = Math.max(Math.min(((r.results && r.results.total) || 1) - 1, items.length - 1), 0);
item = items[tableItemIndex];
value = parseInt(item.name,10) || 0;
return {
expression: '1t['+t+'] [Roll: '+((r.results && r.results.total) || '--')+']',
results: {
resultType: 'sum',
rolls: [
{
dice: 1,
results: [
{
tableItem: {
avatar: item.get('avatar'),
id: item.get('id'),
name: item.get('name'),
weight: item.get('weight')
},
tableidx: tableItemIndex,
v: value
}
],
sides: sides,
table: t,
type: 'R'
}
],
total: value,
type: 'V'
},
signature: false
};
},
commands = {
'rtpower': function(args, msg_orig) {
var msg = _.clone(msg_orig),
postMap,
tableMap={},
expr,
player_obj = getObj("player", msg.playerid);
if (! args.length ) {
commands.help_rtpower(args, msg_orig);
return;
}
// Get the API Chat Command
msg.who = msg.who.replace(" (GM)", "");
msg.content = msg.content.replace("(GM) ", "");
postMap = _.reduce(
msg.content.match(/\[#\[(.*?)\]#\]/g),
function(gmap,g){
var attrmap=_.reduce(g.match(/@#\{(.*?)\}/g), function(amap,a){
var parts=a.match(/@#\{([^|]*)\|([^\|]*)\|?(.*)?\}/),
char = findObjs({
type:'character',
name: parts[1]
})[0],
attr;
amap[a]=0;
if(char) {
attr = findObjs({
type: 'attribute',
characterid: char.id,
name: parts[2]
})[0];
if(attr) {
amap[a]=attr.get(parts[3]==='max'?'max':'current');
}
}
return amap;
},{});
attrmap['[#[']='[[';
attrmap[']#]']=']]';
gmap[g]=_.reduce(attrmap,function(gmemo,v,k){
return gmemo.replace(k,v);
},g);
return gmap;
},{});
postMap = _.reduce(
msg.content.match(/\[rt:([^\[]*)\[(.*?)\]#\]/g),
function(gmap,g){
var tableParts=g.match(/\[rt:([^\[]*)\[/),
attrmap=_.reduce(g.match(/@#\{(.*?)\}/g), function(amap,a){
var parts=a.match(/@#\{([^|]*)\|([^\|]*)\|?(.*)?\}/),
char = findObjs({
type:'character',
name: parts[1]
})[0],
attr;
amap[a]=0;
if(char) {
attr = findObjs({
type: 'attribute',
characterid: char.id,
name: parts[2]
})[0];
if(attr) {
amap[a]=attr.get(parts[3]==='max'?'max':'current');
}
}
return amap;
},{});
tableMap[g]=tableParts[1];
attrmap[tableParts[0]]='[[';
attrmap[']#]']=']]';
gmap[g]=_.reduce(attrmap,function(gmemo,v,k){
return gmemo.replace(k,v);
},g);
return gmap;
},postMap);
expr=_.reduce(postMap,function(m,g){return m+g;},'');
if(expr) {
sendChat('',expr,function(res){
var num = (msg.inlinerolls && msg.inlinerolls.length) || 0,
extraInlineRolls = _.toArray(res[0].inlinerolls);
msg.inlinerolls = msg.inlinerolls || [];
msg.content=_.reduce(postMap,function(msgCon,v,k){
var roll = extraInlineRolls.shift();
if(tableMap[k]) {
roll = buildTableRoll(tableMap[k],roll,msg.playerid);
}
msg.inlinerolls.push(roll);
return msgCon.replace(k,'$[['+(num++)+']]');
},msg.content);
powerCardFunction(msg,player_obj);
});
} else {
powerCardFunction(msg,player_obj);
}
},
rt: function(args, msg) {
var i, items, roll, table;
if (args.length < 2) {
commands.help_rt(args, msg);
return;
}
table = tables[args[0].toLowerCase()];
if (!table) {
table = findObjs({
type: 'rollabletable',
name: args[0]
}, { caseInsensitive: true })[0];
if (table) {
tables[args[0].toLowerCase()] = table;
} else {
sendChat(getSystemFrom(), getWhisperTarget({ player: true, id: msg.playerid }) +
'Could not find table "' + args[0] + '". Please supply an existing table name.');
return;
}
}
items = [];
_.each(findObjs({
type: 'tableitem',
rollabletableid: table.id
}), function(element) {
var i,
weight = parseInt(element.get('weight'),10);
for (i = 0; i < weight; i++) {
items.push(element);
}
});
roll = _.rest(args).join(' ');
if (msg.inlinerolls) {
for (i = 0; i < msg.inlinerolls.length; i++) {
roll = roll.replace('$[[' + i + ']]', msg.inlinerolls[i].results.total);
}
}
sendChat('', '/r ' + roll, function(ops) {
var rollresult = JSON.parse(ops[0].content),
actualRoll = roll,
displayTotal = rollresult.total,
dieSize, tableItem, tableItemAvatar, tableItemIndex, tableItemName;
if (rollresult.resultType === 'M') {
// Math-only roll
dieSize = Math.min(parseInt(items.length - rollresult.total, 10), 0);
actualRoll = '1d' + dieSize + ' + ' + roll;
displayTotal = (dieSize > 0 ? randomInteger(dieSize) : 0) + rollresult.total;
tableItemIndex = Math.max(Math.min(displayTotal - 1, items.length - 1), 0);
} else {
tableItemIndex = Math.max(Math.min(rollresult.total - 1, items.length - 1), 0);
}
tableItem = items[tableItemIndex];
tableItemAvatar = /*tableItem.get('avatar') ? '\n<img src="' + tableItem.get('avatar') + '" />' :*/ '';
//tableItemAvatar = tableItemAvatar.replace(/med|max/, 'thumb');
tableItemName = tableItem.get('name') ? '\n<span style="padding:3px;background-color:yellow"><b>' +
tableItem.get('name') + '</b></span>' : '';
sendChat(getPlayerCharacterFrom(msg.who), 'Rolling ' + actualRoll + ' (' + displayTotal +
') on table "' + args[0] + '":' + tableItemAvatar + tableItemName);
});
},
help: function(command, args, msg) {
if (_.isFunction(commands['help_' + command])) {
commands['help_' + command](args, msg);
}
},
help_rt: function(args, msg) {
sendChat(getSystemFrom(), getWhisperTarget({ player: true, id: msg.playerid }) +
'<div style="border:1px solid black;background:white;padding:3px 3px;">' +
'<div style="font-weight:bold;border-bottom:1px solid black;font-size:130%;">' +
'Real Rollable Tables v' + state.RealRollableTable.version +
'</div>' +
'<span style="font-family:consolas"><b>!rt</b> <i>table-name roll</i></span>' +
'<div style="padding-left:10px;margin-bottom:3px;">' +
'<p>Supply a rolltable table\'s <span style="font-family:consolas">table-name</span> and a ' +
'<span style="font-family:consolas">roll</span> to run on that table. If ' +
'<span style="font-family:consolas">roll</span> is a math expression rather than a roll ' +
'expression, a single die of appropriate size will be selected such that rolling the maximum ' +
'value on the die will result in the last value in the table. If table items have weights ' +
'greater than 1, they will be treated as consecutive entries in the table (eg, a table with ' +
'items a:1, b:3, c:1, d:1 would be equivalent to a table with items a, b, b, b, c, d). ' +
'<b>Fractional weights are ignored!</b></p>' +
'<p>If the result of <span style="font-family:consolas">roll</span> would be lower than 1 or ' +
'greater than the number of elements in the table, the first or last element of the table will be ' +
'used as the result, as appropriate.</p>' +
'</div>');
},
help_rtpower: function(args, msg) {
sendChat(getSystemFrom(), getWhisperTarget({ player: true, id: msg.playerid }) +
'<div style="border:1px solid black;background:white;padding:3px 3px;">' +
'<div style="font-weight:bold;border-bottom:1px solid black;font-size:130%;">' +
'Real Rollable Tables v' + state.RealRollableTable.version +
'</div>' +
'<span style="font-family:consolas"><b>!rtpower</b> <i>powercard arguments</i></span>' +
'<div style="padding-left:10px;margin-bottom:3px;">' +
'Takes the arguments that HoneyBadger\'s PowerCard Script takes. You can use the '+
'following special syntaxes which will be expanded before the commands are '+
'passed to the PowerCard Script: '+
'<div style="padding-left:10px;margin-bottom:3px;">' +
' '+
'<div style="font-family:consolas"><b>[#[ formula ]#]</b></div> '+
'This behaves just like an inline roll, except it is expanded during the API '+
'execution. You can write attribute references as @#{char|attr|part}, '+
'causing them to be looked up at script execution time, rather than command '+
'entry time. This allows scripts in the same macro to make changes and have '+
'those changes reflected in the inline roll. '+
'</div>' +
'<div style="padding-left:10px;margin-bottom:3px;">' +
'<div style="font-family:consolas"><b>[rt:table name[ formula ]#]</b></div> '+
' This will be replaced by the results of the formula, applied as if it were '+
' the roll for referene on the table. The actual result will be supplied as '+
' a label in the mouseover. @#{char|attr|part} references may be used as in '+
' the preceding syntax. '+
'</div>' +
'</div>' +
'</div>');
}
};
function splitArgs(input, separator) {
var singleQuoteOpen = false,
doubleQuoteOpen = false,
tokenBuffer = [],
ret = [],
arr = input.split(''),
element, i, matches;
separator = separator || /\s/g;
for (i = 0; i < arr.length; i++) {
element = arr[i]; matches = element.match(separator);
if (element === '\'') {
if (!doubleQuoteOpen) {
singleQuoteOpen = !singleQuoteOpen;
continue;
}
} else if (element === '"') {
if (!singleQuoteOpen) {
doubleQuoteOpen = !doubleQuoteOpen;
continue;
}
}
if (!singleQuoteOpen && !doubleQuoteOpen) {
if (matches) {
if (tokenBuffer && tokenBuffer.length > 0) {
ret.push(tokenBuffer.join(''));
tokenBuffer = [];
}
} else {
tokenBuffer.push(element);
}
} else if (singleQuoteOpen || doubleQuoteOpen) {
tokenBuffer.push(element);
}
}
if (tokenBuffer && tokenBuffer.length > 0) {
ret.push(tokenBuffer.join(''));
}
return ret;
}
function handleInput(msg) {
var args, arg0, command, isApi, isHelp;
isApi = msg.type === 'api';
args = splitArgs(msg.content.trim());
if (isApi) {
// Call !command or help message for !command
command = args.shift().substring(1).toLowerCase();
arg0 = args.shift();
if (arg0) {
arg0 = arg0.toLowerCase();
}
isHelp = arg0 === 'help' || arg0 === 'h';
if (!isHelp) {
if (arg0 && arg0.length > 0) {
args.unshift(arg0);
}
if (_.isFunction(commands[command])) {
commands[command](args, msg);
}
} else if (_.isFunction(commands.help)) {
commands.help(command, args, msg);
}
} else if (_.isFunction(commands['msg_' + msg.type])) {
// Handle non-api command input
commands['msg_' + msg.type](args, msg);
}
}
function checkInstall() {
// Initialize default `state`
if (!state.RealRollableTable ||
!state.RealRollableTable.version ||
state.RealRollableTable.version !== version) {
state.RealRollableTable = {
version: version,
systemId: getSystemId()
};
}
if("undefined" !== typeof PowerCard && _.isFunction(PowerCard.Process)) {
powerCardFunction = PowerCard.Process;
} else if("undefined" !== typeof PowerCardScript && _.isFunction(PowerCardScript.Process)) {
powerCardFunction = PowerCardScript.Process;
} else {
log('No Powercard Script Found.');
}
}
function registerEventHandlers() {
on('chat:message', handleInput);
}
return {
checkInstall: checkInstall,
registerEventHandlers: registerEventHandlers
};
}());
on('ready', function() {
'use strict';
RealRollableTable.checkInstall();
RealRollableTable.registerEventHandlers();
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment