Last active
February 17, 2017 06:14
-
-
Save mattkauffman23/cf179c9fe71b6e01c040 to your computer and use it in GitHub Desktop.
Roll 20 API Script for generating complex random items
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
(function() { | |
/* | |
!randomizer - Allows flexible generation of all sorts of randomizable things | |
such as NPCs, loot, weather, etc... | |
Requires the following other scripts to be installed: | |
- Shell (https://wiki.roll20.net/Script:Command_Shell) | |
Instructions: | |
Randomizer relies on special names for rollable tables. It intends to be | |
flexible and easy to use. Its simplest command is “!randomizer <TableName>” | |
which rolls on the table whose name matches <TableName> and reports back to | |
chat. Tables can also be grouped into sets and be rolled together. For | |
instance if the tables “npc-race”, “npc-class”, “npc-personality” and | |
“npc-appearance” exist a GM could use the command “!randomizer npc” to | |
generate a random npc by rolling on each of those tables and combining the | |
results. Further table items in tables can be used to trigger sub-rolls. A | |
good example of this would be loot horde table with a table item whose name | |
is “loot-gold-a*3,loot-items-c”. This table item name would trigger Randomizer | |
to roll 3 times on the table “loot-gold-a” and once on “loot-items-c”. | |
Randomizer supports a --b flag to broadcast results to everyone in chat. By | |
default results are only sent to the person who invoked the command. | |
Randomizer respects weighting assigned to table items. | |
Tables with an item named %%HIDDEN%% will not be displayed in the | |
results. This is useful for tables that just trigger sub-rolls, such as in | |
the loot horde example. This keyword is configurable below. | |
Randomizer is GM only by default. Command_Shell can be used to give players | |
access, however if any table included in a roll is not configured to | |
showplayers the roll will fail. | |
I believe you can “nest” tables as deep as you want to. For instance | |
“weather-july-temp” and “weather-july-percip” could be set up allowing | |
“!Randomizer weather july” to generate some random weather for july. | |
If anyone runs into issues or has any ideas to improve it please let me know! | |
*/ | |
'use strict'; | |
// === Start config === | |
// The name used as the sender in chat | |
var from = 'randomizer'; | |
// The character used to split table names into seperate words for querying | |
var delimiter = '-'; | |
// The string (fancy word for sequence of letters and numbers) used to prevent | |
// a table's results from being displayed | |
var hideKeyWord = '%%HIDDEN%%'; | |
// === End config === | |
// Register the command | |
on('ready', function() { | |
Shell.registerCommand('!randomizer', | |
'!randomizer <type>...]', | |
'Roll on the requested tables.', | |
onInput); | |
}); | |
var resultsTemplate = _.template( | |
'&{template:default} ' + | |
'{{name=<%= title %>}}' + | |
'<% _.each(results, function(result) { %>' + | |
'{{<%= result.name %>=<%= result.item %> (<%= result.val %>)}}' + | |
'<% }); %>' | |
); | |
function onInput(opts, msg) { | |
var receiver, query, response; | |
receiver = _.contains(opts, '--b') ? undefined : msg.who; | |
opts = _.reject(opts, function(opt) { | |
return opt.match(/^--/); | |
}); | |
query = _.rest(opts); | |
if (!query.length) { | |
respond('!randomizer tables found: \n' + listTables().join('\n'), | |
receiver, from); | |
return; | |
} | |
response = buildResponse(query, msg.playerid); | |
respond(response, receiver, from); | |
} | |
function respond(msg, to, from) { | |
Shell.write(msg, to, undefined, from); | |
} | |
function listTables() { | |
return _.map(findTables(), function(table) { | |
return table.get('name').replace(delimiter, ' '); | |
}); | |
} | |
function findTables(query) { | |
query = query || []; | |
return filterObjs(function(obj) { | |
return obj.get('_type') === 'rollabletable' && | |
obj.get('name').indexOf(query.join(delimiter)) === 0; | |
}); | |
} | |
function buildResponse(query, playerId) { | |
var title, tables, results; | |
tables = findTables(query); | |
if (!tables.length) { | |
return '!randomizer: Could not find matching rollable table.'; | |
} | |
// Check permissions | |
if (!playerIsGM(playerId) && | |
_.any(tables, function(table) { return !table.showplayers; })) { | |
return '!randomizer: You don\'t have permissions to make that roll.'; | |
} | |
// If only one table in response omit the last argument of query to ovoid redundant display | |
title = tables.length === 1 ? | |
query.slice(0, -1).join(' ') : query.join(' '); | |
results = getResults(tables, query); | |
results = _.reject(results, function (result) { | |
return result.hide; | |
}); | |
return resultsTemplate({ title: title, results: results }); | |
} | |
function getResults(tables, query) { | |
var results, roll, ordinal, subTables; | |
// Roll on all the tables | |
results = _.reduce(tables, function(memo, table) { | |
roll = rollTable(table); | |
roll.name = getShortName(table, query); | |
memo.push(roll); | |
return memo; | |
}, []); | |
// Create unique names for any table which is rolled on more than once | |
_.each(_.groupBy(results, 'name'), function(group) { | |
if (group.length > 1) { | |
_.each(group, function(result, i) { | |
ordinal = i + 1; | |
// Good enough? Fails after 10 rolls on same table. | |
ordinal += ordinal === 1 ? 'st' : ordinal === 2 ? 'nd' : ordinal === 3 ? 'rd' : 'th'; | |
result.name = ordinal + ' ' + result.name; | |
}); | |
} | |
}); | |
// Recursively roll any table items that have roll commands | |
subTables = findSubTables(results, query); | |
if (subTables.length) { | |
return results.concat(getResults(subTables, query)); | |
} else { | |
return results; | |
} | |
} | |
function rollTable(table) { | |
var items, max, roll, hideFlag; | |
items = findObjs({ _type: 'tableitem', _rollabletableid: table.id }); | |
// Look for specially named items to supress table results display | |
hideFlag = _.find(items, function(item) { | |
return item.get('name') === hideKeyWord; | |
}); | |
items = _.without(items, hideFlag); | |
max = _.reduce(items, function(memo, item) { | |
return memo += item.get('weight'); | |
}, 0); | |
roll = randomInteger(max); | |
for (var i = 0, weightedProgress = 0; i < items.length; i++) { | |
weightedProgress += parseInt(items[i].get('weight'), 10); | |
if (weightedProgress >= roll) { | |
return { val: roll, item: items[i].get('name'), hide: !!hideFlag }; | |
} | |
} | |
} | |
function findSubTables(results, query) { | |
var subTables = [], commands, commandMatches, commandDescription, | |
name, times, matches; | |
results.forEach(function(result) { | |
commandDescription = ''; | |
commands = result.item.split(','); | |
commandMatches = _.reduce(commands, function(memo, command) { | |
command = command.split('*'); | |
name = command[0]; | |
times = parseInt(command[1], 10) || 1; | |
matches = findTables(name.split(delimiter)); | |
if (matches.length) { | |
_.times(times, function () { memo.push(matches[0]); }); | |
commandDescription += times + ' X ' + getShortName(name, query) + '\n'; | |
} | |
return memo; | |
}, []); | |
subTables = subTables.concat(commandMatches); | |
// Rewrite subtable roll syntax to be user friendly | |
result.item = commandDescription || result.item; | |
}); | |
return subTables; | |
} | |
// Return table name parts not included in the query. If all are included | |
// return the last. | |
function getShortName(table, query) { | |
var path, diff, output; | |
table = typeof table === 'string' ? table : table.get('name'); | |
path = table.split(delimiter); | |
diff = _.difference(path, query); | |
output = diff.length ? diff.join(' ') : _.last(path); | |
// Special case "name" since its used as title of default roll template. | |
if (output === 'name') output = '"name"'; | |
return output; | |
} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment