I just stumbled across this old suggestion, and for Pro users I did want to point out that there is an Mod script for this, though it's not in the library. I can't remember who made it but someone showed it to me when I was trying to solve the same problem, and it works great. var RealRollableTable = RealRollableTable || (function() { 'use strict'; var version = 1, tables = _.groupBy(findObjs({ type: 'rollabletable' }), function(table) { return table.get('name').toLowerCase(); }), commands = { 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, index, list) { var i, weight = parseInt(element.get('weight')); 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), 0); actualRoll = '1d' + dieSize + ' + ' + roll; displayTotal = (dieSize > 0 ? randomInteger(dieSize) : 0) + rollresult.total; tableItemIndex = Math.max(Math.min(displayTotal, items.length - 1), 0); } else { tableItemIndex = Math.max(Math.min(rollresult.total, 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>'); } }; function getPlayerCharacterFrom(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; } 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) { 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 ''; } 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 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]; } function getSystemId() { // Get character id of `system` var systemChar = findObjs({ type: 'character', name: 'system', controlledby: '' }, { caseInsensitive: true })[0]; return systemChar ? systemChar.id : ''; } function getSystemFrom() { // 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'; } 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() }; } } function registerEventHandlers() { on('chat:message', handleInput); } return { checkInstall: checkInstall, registerEventHandlers: registerEventHandlers }; }()); on('ready', function() { 'use strict'; RealRollableTable.checkInstall(); RealRollableTable.registerEventHandlers(); });