Mod:Cookbook
From Roll20 Wiki
The following are not full scripts. They are meant to be stitched together along with business logic to assist in the creation of full scripts, not create scripts on their own.
Contents |
Revealing Module Pattern
The Module Pattern emulates the concept of classes from other languages by encapsulating private and public members within an object. The Revealing Module Pattern improves upon the Module Pattern by making the syntax more consistent.
var myRevealingModule = myRevealingModule || (function() { var privateVar = 'This variable is private', publicVar = 'This variable is public'; function privateFunction() { log(privateVar); } function publicSet(text) { privateVar = text; } function publicGet() { privateFunction(); } return { setFunc: publicSet, myVar: publicVar, getFunc: publicGet }; })(); log(myRevealingModule.getFunc()); // "This variable is private" myRevealingModule.setFunc('But I can change its value'); log(myRevealingModule.getFunc()); // "But I can change its value" log(myRevealingModule.myVar); // "This variable is public" myRevealingModule.myVar = 'So I can change it all I want'; log(myRevealingModule.myVar); // "So I can change it all I want"
Memoization
Memoization is an optimization technique which stores the result for a given input, allowing the same output to be produced without computing it twice. This is especially useful in expensive computations. Of course, if it is rare that your function will receive the same input, memoization will be of limited utility while the storage requirements for it continue to grow.
var factorialCache = {}; function factorial(n) { var x; n = parseInt(n || 0); if (n < 0) { throw 'Factorials of negative numbers are not well-defined'; } if (n === 0) { return 1; } else if (factorialCache[n]) { return factorialCache[n]; } x = factorial(n - 1) * n; factorialCache[n] = x; return x; }
In a Roll20 API script, the cached values could potentially be stored in state
, which will persist between game sessions. If you have a large number of potential inputs, however, be aware that Roll20 may throttle your use of state
.
Utility Functions
Utility functions complete common tasks that you may want to use throughout many scripts. If you place a function at the outermost scope of a script tab, that function should be available to all of your scripts, reducing your overhead. Below is a selection of such functions.
getSenderForName
Dependencies: None
Given a string name, this function will return a string appropriate for the first parameter of sendChat. If there is a character that shares a name with a player, the player will be used. You may also pass an options
object, which is structured identically to the options
parameter of findObjs.
function getSenderForName(name, options) { var character = findObjs({ type: 'character', name: name }, options)[0], player = findObjs({ type: 'player', displayname: name.lastIndexOf(' (GM)') === name.length - 5 ? name.substring(0, name.length - 5) : name }, options)[0]; if (player) { return 'player|' + player.id; } if (character) { return 'character|' + character.id; } return name; }
levenshteinDistance
Source: en.wikibooks.org
Levenshtein Distance is a metric for measuring the difference between strings. The return value for this function will be the number of substitutions, insertions, and deletions required to transform string a into string b.
function levenshteinDistance(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
Dependencies: levenshteinDistance
Given a set of options, this function tries to construct the "/w name " portion of a whisper for a call to sendChat. The options
parameter should contain either player: true
or character: true
and a value for either id
or name
. Players are preferred over characters if both are true, and ids are preferred over names if both have a valid value. If a name is supplied, the player or character with the name closest to the supplied string will be sent the whisper.
options
is technically optional, but if you omit it (or don't supply a combination of player/character + id/name), the function will return an empty string.
function getWhisperTarget(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) { // Sort all players or characters (as appropriate) whose name *contains* the supplied name, // then sort them by how close they are to the supplied 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 ''; }
splitArgs
Source: Elgs Qian Chen on GitHub.com
It is true that msg.content.split(' ')
is frequently good enough for handling API commands and their parameters, but sometimes greater control over the tokenization is desired. splitArgs
lets you quote parameters to your API command (including nested quotes).
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; }
processInlinerolls
This function will scan through msg.content
and replace inline rolls with their total result. This is particularly useful for API commands to which the user may want to pass inline rolls as parameters.
function processInlinerolls(msg) { if (_.has(msg, 'inlinerolls')) { return _.chain(msg.inlinerolls) .reduce(function(previous, current, index) { previous['$[[' + index + ']]'] = current.results.total || 0; return previous; },{}) .reduce(function(previous, current, index) { return previous.replace(index, current); }, msg.content) .value(); } }
statusmarkersToObject
The inverse of objectToStatusmarkers; transforms a string suitable for use as the value of the statusmarkers
property of a Roll20 token object into a plain old JavaScript object.
Note that a statusmarker string can contain duplicate statusmarkers, while an object cannot contain duplicate properties.
function statusmarkersToObject(stats) { return _.reduce(stats.split(/,/), function(memo, value) { var parts = value.split(/@/), num = parseInt(parts[1] || '0', 10); if (parts[0].length) { memo[parts[0]] = Math.max(num, memo[parts[0]] || 0); } return memo; }, {}); }
objectToStatusmarkers
The inverse of statusmarkersToObject; transforms a plain old JavaScript object into a comma-delimited string suitable for use as the value of the statusmarkers
property of a Roll20 token object.
Note that a statusmarker string can contain duplicate statusmarkers, while an object cannot contain duplicate properties.
function objectToStatusmarkers(obj) { return _.map(obj, function(value, key) { return key === 'dead' || value < 1 || value > 9 ? key : key + '@' + parseInt(value); }) .join(','); }