Last active
October 17, 2015 14:29
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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