Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×

Is There Any Way to Transfer a Mod Chat Button Name to a Macro?

1783419204

Edited 1783419698
Wondering if the subject is doable? IOW, a button with "Skeleton" as the caption be used in the title, or elsewhere, of the macro it launches? ATM, I've got the following working fine. The first chat "Turn Scary Undead" window is a Mod and the buttons launch macros. In this case the Zombie (10) button launches a macro called  Undead10 . I would do it all in the Mod, but we prefer to have the dice rattling & rolling on the screen*. So mod chat first, then the button displays a macro chat that does "a roll to turn" has an "effects" button to roll effects (the "back" button relaunches the "Turn Scary Undead" or "!turnundead" mod chat): The second chat window is recognizable as "our" template:general.  :-) The button code that makes the zombie button on the first chat window is from this snippet in the Mods screen: "</div>"; } else if (char_level === 2) { undeadbuttons = "<div style='text-align:center;'>" + "<p>[Skeleton (7)](!
#Undead07)  " + "[Zombie (10)](!
#Undead10)</p>" + "<p>[Ghoul (13)](!
#Undead13)   " + "[Shadow (16)](!
#Undead16)</p>" + "[Wight (19)](!
#Undead19)  " + "[Ghast (20)](!
#Undead20)" + "</div>"; } else if (char_level === 3) { NOTE: Not visible in the snippet above, but I use ROTTO's Panel constant make the chat. And the macro code for the Zombie (10) button is called Undead10: &{template:general} {{name=Turn Undead, @{selected|character_name}}} {{subtag= [Back](!turnundead)}} {{color=@{selected|color_option}}} {{Roll 10 or higher:=[[ ?{Modifier|0} + 1d20cs>[[10-?{Modifier}]]cf<[[9-?{Modifier}]] ]]}} {{[Effects](!
#UndeadE)}} I would REALLY like to have "Zombies", or whatever undead button is clicked on the top Mod chat, to also appear on the macro name or subtag. Is that possible? I'm OK if it is not, but it would be nice cosmetically if I could. Thanks.  ________ * BTW, APIs in 2020 used to be able to roll dice on the screen, if you included this variable in sendChat: {use3d: true}.  Unfortunately, that stopped in the Summer of 2020 or 2021, I don't know why.
This is a standalone Roll20 API for AD&D 1e Turn Undead. Select the cleric or paladin token and run the macro. The API opens a Turn Undead menu, checks the character’s effective turning level, lets you choose the undead type, rolls the turn attempt, and applies standard Roll20 markers to affected undead within range. It will read the token name for the specific monster ie skeleton. Turned undead get the screaming marker with the number of rounds shown. Destroyed or damned undead get the dead marker. If it works correctly, it will either tag selected tokens for turning or check every token within 60ft.  USE THIS COMMAND:     !tu1e-menu --cleric @{selected|token_id} /*  * TurnUndead1E_Standalone.js  * Standalone AD&D 1E Turn Undead helper for Roll20.  * Does not modify or depend on GM1E / GM Sheet.  * Uses standard Roll20 token markers only:  *   turned = screaming marker, with number showing 3d4 rounds  *   destroyed/damned = dead marker  */ on('ready', function () {     'use strict';     var SCRIPT = 'TurnUndead1E';     var VERSION = '1.0.0-standalone-standard-markers';     var TURNED_MARKER = 'screaming';     var DESTROYED_MARKER = 'dead';     var TURN_UNDEAD_ROWS = [         { key: 'skeleton', label: 'Skeleton', values: ['10','7','4','T','T','D','D','D*','D*','D*'] },         { key: 'zombie', label: 'Zombie', values: ['13','10','7','T','T','D','D','D','D*','D*'] },         { key: 'ghoul', label: 'Ghoul', values: ['16','13','10','4','T','T','D','D','D','D*'] },         { key: 'shadow', label: 'Shadow', values: ['19','16','13','7','4','T','T','D','D','D*'] },         { key: 'wight', label: 'Wight', values: ['20','19','16','10','7','4','T','T','D','D'] },         { key: 'ghast', label: 'Ghast', values: ['--','20','19','13','10','7','4','T','T','D'] },         { key: 'wraith', label: 'Wraith', values: ['--','--','20','16','13','10','7','4','T','D'] },         { key: 'mummy', label: 'Mummy', values: ['--','--','--','19','16','13','10','7','4','T'] },         { key: 'spectre', label: 'Spectre', values: ['--','--','--','20','19','16','13','10','7','T'] },         { key: 'vampire', label: 'Vampire', values: ['--','--','--','--','20','19','16','13','10','4'] },         { key: 'ghost', label: 'Ghost', values: ['--','--','--','--','--','20','19','16','13','7'] },         { key: 'lich', label: 'Lich', values: ['--','--','--','--','--','--','20','19','16','10'] },         { key: 'special', label: 'Special**', values: ['--','--','--','--','--','--','--','20','19','13'] }     ];     function esc(s) {         if (s === undefined || s === null) { return ''; }         s = String(s);         return s.split('&').join('&')             .split('<').join('<')             .split('>').join('>')             .split('"').join('"');     }     function box(c) {         return '<div style="background:#000;color:#fff;border:1px solid #aa2222;padding:6px;font-size:12px;line-height:1.25;display:block;">' + c + '</div>';     }     function title(t) {         return '<div style="background:#000;color:#fff;font-weight:bold;font-size:14px;border-bottom:1px solid #aa2222;margin-bottom:4px;padding-bottom:3px;">' + esc(t) + '</div>';     }     function row(c) {         return '<div style="background:#000;color:#fff;margin:0;">' + c + '</div>';     }     function hr() {         return '<div style="background:#000;border-top:1px solid #aa2222;margin:4px 0;height:0;"></div>';     }     function pill(label) {         return '<span style="background:#7a1f1f;color:#fff;border:1px solid #aa2222;border-radius:3px;padding:1px 5px;margin:0 1px;display:inline-block;font-size:11px;font-weight:bold;">' + esc(label) + '</span>';     }     function diePill(label) {         return '<span style="background:#d8a11d;color:#000;border:1px solid #f4d36a;border-radius:3px;padding:1px 6px;margin:0 1px;display:inline-block;font-size:12px;font-weight:bold;">' + esc(label) + '</span>';     }     function good(s) {         return '<span style="color:#51d66b;font-weight:bold;">' + esc(s) + '</span>';     }     function bad(s) {         return '<span style="color:#ff5555;font-weight:bold;">' + esc(s) + '</span>';     }     function neutral(s) {         return '<span style="color:#ddd;">' + esc(s) + '</span>';     }     function btn(label, command, tip) {         return '<a href="' + esc(command) + '" title="' + esc(tip || '') + '" style="background:#111;color:#fff;border:1px solid #aa2222;border-radius:3px;padding:1px 4px;margin:0 1px;display:inline-block;text-decoration:none;font-size:11px;font-weight:bold;">' + esc(label) + '</a>';     }     function toInt(v, fallback) {         var m;         if (v === undefined || v === null || v === '') { return fallback; }         m = String(v).match(/-?\d+/);         if (!m) { return fallback; }         return parseInt(m[0], 10);     }     function normalizeText(value) {         return String(value || '')             .toLowerCase()             .replace(/\*\*/g, '')             .replace(/[^a-z0-9]+/g, ' ')             .replace(/^\s+|\s+$/g, '');     }     function tokenById(id) {         return getObj('graphic', id);     }     function charByToken(token) {         var cid;         if (!token) { return null; }         cid = token.get('represents');         if (!cid) { return null; }         return getObj('character', cid);     }     function tokenName(token) {         if (!token) { return 'Unknown'; }         return token.get('name') || 'Token';     }     function getAttr(cid, name) {         var a;         if (!cid || !name) { return ''; }         a = findObjs({ _type: 'attribute', _characterid: cid, name: name }, { caseInsensitive: true })[0];         return a ? (a.get('current') || '') : '';     }     function getAttrNumber(cid, names, fallback) {         var i, n;         for (i = 0; i < names.length; i++) {             n = toInt(getAttr(cid, names[i]), NaN);             if (!isNaN(n)) { return n; }         }         return fallback;     }     function classSlotText(cid, slotIndex) {         var names;         names = [             'class' + String(slotIndex + 1),             'class_' + String(slotIndex + 1),             'class' + String(slotIndex + 1) + '_name',             'class_' + String(slotIndex + 1) + '_name'         ];         if (slotIndex === 0) {             names = names.concat(['class', 'class_name', 'character_class', 'npc_class']);         }         return normalizeText(firstAttr(cid, names));     }     function classSlotLevel(cid, slotIndex) {         var names;         names = [             'level' + String(slotIndex + 1),             'level_' + String(slotIndex + 1),             'class' + String(slotIndex + 1) + '_level',             'class_' + String(slotIndex + 1) + '_level'         ];         if (slotIndex === 0) {             names = names.concat(['level', 'character_level', 'npc_level']);         }         return getAttrNumber(cid, names, 0);     }     function firstAttr(cid, names) {         var i, v;         for (i = 0; i < names.length; i++) {             v = getAttr(cid, names[i]);             if (String(v || '').replace(/^\s+|\s+$/g, '') !== '') { return v; }         }         return '';     }     function turnClassInfoFromToken(token) {         var ch = charByToken(token);         var cid = ch ? ch.id : '';         var i, cls, lvl, bestPaladin;         if (!cid) {             return { canTurn: false, sourceClass: '', classLevel: 0, effectiveLevel: 0, label: 'No linked character sheet' };         }         for (i = 0; i < 3; i++) {             cls = classSlotText(cid, i);             lvl = classSlotLevel(cid, i);             if (cls.indexOf('cleric') !== -1 && lvl > 0) {                 return { canTurn: true, sourceClass: 'Cleric', classLevel: lvl, effectiveLevel: Math.max(1, lvl), label: 'Cleric level ' + Math.max(1, lvl) };             }             if (cls.indexOf('paladin') !== -1 && lvl >= 3) {                 if (!bestPaladin || lvl > bestPaladin.classLevel) {                     bestPaladin = { canTurn: true, sourceClass: 'Paladin', classLevel: lvl, effectiveLevel: Math.max(1, lvl - 2), label: 'Paladin level ' + lvl + '; turns as Cleric level ' + Math.max(1, lvl - 2) };                 }             }         }         if (bestPaladin) { return bestPaladin; }         return { canTurn: false, sourceClass: '', classLevel: 0, effectiveLevel: 0, label: 'No Cleric or eligible Paladin level found' };     }     function turnUndeadColumnForLevel(level) {         level = toInt(level, 1);         if (level <= 1) { return 0; }         if (level === 2) { return 1; }         if (level === 3) { return 2; }         if (level === 4) { return 3; }         if (level === 5) { return 4; }         if (level === 6) { return 5; }         if (level === 7) { return 6; }         if (level === 8) { return 7; }         if (level <= 13) { return 8; }         return 9;     }     function turnUndeadLevelBand(level) {         return ['1','2','3','4','5','6','7','8','9-13','14+'][turnUndeadColumnForLevel(level)];     }     function turnUndeadRow(key) {         var i;         key = String(key || 'skeleton').toLowerCase();         for (i = 0; i < TURN_UNDEAD_ROWS.length; i++) {             if (TURN_UNDEAD_ROWS[i].key === key) { return TURN_UNDEAD_ROWS[i]; }         }         return TURN_UNDEAD_ROWS[0];     }     function rollDice(count, sides) {         var i, total = 0, parts = [];         for (i = 0; i < count; i++) {             parts[i] = randomInteger(sides);             total += parts[i];         }         return { total: total, detail: parts.join('+') };     }     function rollAffectedCount(rowKey, resultCode) {         var d;         if (rowKey === 'special') {             return { rollLabel: '1d2', count: randomInteger(2), note: '1-2 for Special**' };         }         if (String(resultCode || '').indexOf('*') !== -1) {             return { rollLabel: '1d6+6', count: randomInteger(6) + 6, note: '7-12 because of *' };         }         d = randomInteger(12);         return { rollLabel: '1d12', count: d, note: '1-12' };     }     function tokenMarkerList(token) {         var raw;         if (!token) { return []; }         raw = String(token.get('statusmarkers') || '');         return raw ? raw.split(',').filter(function (m) { return !!m; }) : [];     }     function baseMarkerName(marker) {         return String(marker || '').split('@')[0].split('::')[0];     }     function setStandardMarker(token, markerName, number) {         var markers, i, base, value;         if (!token || !markerName) { return; }         markers = tokenMarkerList(token);         markers = markers.filter(function (m) {             base = baseMarkerName(m);             return base !== markerName;         });         value = markerName;         if (number !== undefined && number !== null && String(number) !== '') {             value += '@' + String(number);         }         markers.push(value);         token.set('statusmarkers', markers.join(','));     }     function clearTurnMarkers(token) {         var markers;         if (!token) { return; }         markers = tokenMarkerList(token).filter(function (m) {             var base = baseMarkerName(m);             return base !== TURNED_MARKER && base !== DESTROYED_MARKER;         });         token.set('statusmarkers', markers.join(','));     }     function shuffleTokens(tokens) {         var arr = (tokens || []).slice(0);         var i, j, tmp;         for (i = arr.length - 1; i > 0; i--) {             j = randomInteger(i + 1) - 1;             tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp;         }         return arr;     }     function tokenNameList(tokens) {         var out = [], i;         for (i = 0; i < (tokens || []).length; i++) { out.push(tokenName(tokens[i])); }         return out.length ? out.join(', ') : 'None';     }     function pageScaleFeet(page) {         var scale;         if (!page) { return 5; }         scale = parseFloat(page.get('scale_number'));         if (isNaN(scale) || scale <= 0) { scale = 5; }         return scale;     }     function pageGridPixels(page) {         var snapping;         if (!page) { return 70; }         snapping = parseFloat(page.get('snapping_increment'));         if (isNaN(snapping) || snapping <= 0) { snapping = 1; }         return 70 * snapping;     }     function pixelsToFeet(page, pixels) {         return (pixels / pageGridPixels(page)) * pageScaleFeet(page);     }     function distanceFeet(tokenA, tokenB) {         var page, dx, dy;         if (!tokenA || !tokenB || tokenA.get('pageid') !== tokenB.get('pageid')) { return 999999; }         page = getObj('page', tokenA.get('pageid'));         dx = (parseFloat(tokenA.get('left')) || 0) - (parseFloat(tokenB.get('left')) || 0);         dy = (parseFloat(tokenA.get('top')) || 0) - (parseFloat(tokenB.get('top')) || 0);         return pixelsToFeet(page, Math.sqrt(dx * dx + dy * dy));     }     function tokenTypeText(token) {         var text = '';         var ch = charByToken(token);         var cid = ch ? ch.id : '';         if (token) { text += ' ' + normalizeText(token.get('name')); }         if (ch) { text += ' ' + normalizeText(ch.get('name')); }         if (cid) {             text += ' ' + normalizeText(firstAttr(cid, ['undead_type', 'monster_type', 'creature_type', 'npc_type', 'type', 'race', 'name']));         }         return ' ' + text + ' ';     }     function tokenMatchesUndeadType(token, rowDef) {         var text, expected;         if (!token || !rowDef) { return false; }         if (rowDef.key === 'special') { return true; }         text = tokenTypeText(token);         expected = normalizeText(rowDef.label);         return text.indexOf(' ' + expected + ' ') !== -1 || text.indexOf(' ' + expected + 's ') !== -1;     }     function tokensInRadius(clericToken, radiusFeet, rowDef) {         var all, i, t, out = [];         if (!clericToken) { return out; }         all = findObjs({ _type: 'graphic', _pageid: clericToken.get('pageid'), layer: 'objects' }) || [];         for (i = 0; i < all.length; i++) {             t = all[i];             if (!t || t.id === clericToken.id) { continue; }             if (distanceFeet(clericToken, t) > radiusFeet) { continue; }             if (!tokenMatchesUndeadType(t, rowDef)) { continue; }             out.push(t);         }         return out;     }     function tokensFromIds(ids, clericTokenId, rowDef) {         var out = [], seen = {}, i, id, t;         for (i = 0; i < (ids || []).length; i++) {             id = String(ids[i] || '').replace(/^\s+|\s+$/g, '');             if (!id || id === clericTokenId || seen[id]) { continue; }             t = tokenById(id);             if (!t) { continue; }             if (!tokenMatchesUndeadType(t, rowDef)) { continue; }             seen[id] = true;             out.push(t);         }         return out;     }     function selectedTargetTokens(msg, clericTokenId, rowDef) {         var out = [], seen = {}, i, sel, t;         if (!msg || !msg.selected) { return out; }         for (i = 0; i < msg.selected.length; i++) {             sel = msg.selected[i];             if (!sel || sel._type !== 'graphic' || !sel._id || sel._id === clericTokenId || seen[sel._id]) { continue; }             t = tokenById(sel._id);             if (!t) { continue; }             if (!tokenMatchesUndeadType(t, rowDef)) { continue; }             seen[sel._id] = true;             out.push(t);         }         return out;     }     function playDiceSound() {         sendChat(SCRIPT, '!splay diceri');     }     function resolveTurn(msg, opts) {         var clericToken = tokenById(opts.cleric);         var rowDef = turnUndeadRow(opts.type);         var levelInfo;         var level;         var col;         var resultCode;         var targets = [];         var eligibleCount;         var roll;         var targetNumber;         var success = false;         var affectedRoll;         var affectedCount;         var affectedTokens = [];         var rounds;         var i;         var out = '';         if (!clericToken) {             sendChat(SCRIPT, '/w gm ' + box(title('Turn Undead Error') + row(bad('Cleric token not found.'))));             return;         }         levelInfo = turnClassInfoFromToken(clericToken);         level = toInt(opts.level, 0);         if (level <= 0) {             level = levelInfo.effectiveLevel || 1;         }         col = turnUndeadColumnForLevel(level);         resultCode = rowDef.values[col];         if (opts.targets && opts.targets.length) {             targets = tokensFromIds(opts.targets, clericToken.id, rowDef);         } else {             targets = selectedTargetTokens(msg, clericToken.id, rowDef);         }         if (!targets.length && opts.radius > 0) {             targets = tokensInRadius(clericToken, opts.radius, rowDef);         }         eligibleCount = targets.length;         playDiceSound();         out += title(tokenName(clericToken) + ' - Turn Undead');         out += row('<b>Undead Type:</b> ' + pill(rowDef.label));         out += row('<b>Effective Cleric Level:</b> ' + pill(String(level)) + ' <span style="color:#ddd;">matrix band ' + esc(turnUndeadLevelBand(level)) + '</span>');         out += row('<b>Matrix Result:</b> ' + pill(resultCode));         if (opts.radius > 0 && (!opts.targets || !opts.targets.length)) {             out += row('<b>Radius:</b> ' + pill(String(opts.radius) + ' ft'));         }         out += row('<b>Eligible Targets:</b> ' + pill(String(eligibleCount)) + ' <span style="color:#ddd;">' + esc(tokenNameList(targets)) + '</span>');         if (!eligibleCount) {             out += row(bad('No eligible matching undead tokens found. Token or character name/attrs must include the chosen undead type, or use Special**.'));             sendChat(SCRIPT, box(out));             return;         }         if (resultCode === '--') {             out += row(bad('No effect is possible against this undead type at this level.'));             sendChat(SCRIPT, box(out));             return;         }         if (resultCode === 'T') {             success = true;             out += row(good('Automatic turning.'));         } else if (resultCode.indexOf('D') === 0) {             success = true;             out += row(good('Automatic destroyed / damned result.'));         } else {             targetNumber = toInt(resultCode, 20);             roll = randomInteger(20);             success = roll >= targetNumber;             out += row('<b>Turn Roll:</b> d20 ' + diePill(String(roll)) + ' vs target ' + pill(String(targetNumber)) + ' or greater');             out += row(success ? good('SUCCESS') : bad('FAILURE'));         }         if (success) {             affectedRoll = rollAffectedCount(rowDef.key, resultCode);             affectedCount = Math.min(affectedRoll.count, eligibleCount);             affectedTokens = shuffleTokens(targets).slice(0, affectedCount);             out += row('<b>Number Affected Roll:</b> ' + diePill(affectedRoll.rollLabel + ' = ' + affectedRoll.count) + ' <span style="color:#ddd;">' + esc(affectedRoll.note) + '</span>');             if (affectedRoll.count > eligibleCount) {                 out += row(neutral('Only ' + eligibleCount + ' target(s) were available, so only ' + affectedCount + ' are affected.'));             }             if (resultCode.indexOf('D') === 0) {                 for (i = 0; i < affectedTokens.length; i++) {                     clearTurnMarkers(affectedTokens[i]);                     setStandardMarker(affectedTokens[i], DESTROYED_MARKER, '');                 }                 out += row('<b>Destroyed / Damned:</b> ' + good(tokenNameList(affectedTokens)));                 out += row(neutral('Applied standard Roll20 dead marker.'));             } else {                 rounds = rollDice(3, 4);                 for (i = 0; i < affectedTokens.length; i++) {                     clearTurnMarkers(affectedTokens[i]);                     setStandardMarker(affectedTokens[i], TURNED_MARKER, rounds.total);                 }                 out += row('<b>Turned:</b> ' + good(tokenNameList(affectedTokens)));                 out += row('<b>Rounds Turned:</b> ' + diePill('3d4 = ' + rounds.detail) + ' = ' + pill(String(rounds.total)));                 out += row(neutral('Applied standard Roll20 screaming marker with round number.'));             }         }         sendChat(SCRIPT, box(out));     }     function parseArgs(content) {         var opts = { cleric: '', type: 'skeleton', level: 0, radius: 60, targets: [] };         var parts = content.split(/\s+--/);         var i, p, k, v;         for (i = 1; i < parts.length; i++) {             p = parts[i];             k = p.split(/\s+/)[0];             v = p.substring(k.length).replace(/^\s+|\s+$/g, '');             if (k === 'cleric') { opts.cleric = v; }             if (k === 'type') { opts.type = v; }             if (k === 'level') { opts.level = toInt(v, 0); }             if (k === 'radius') { opts.radius = toInt(v, 60); }             if (k === 'targets') {                 opts.targets = v.split(/[|,;\s]+/).filter(function (x) { return !!x; });             }         }         return opts;     }     function showMenu(msg, clericTokenId) {         var token = tokenById(clericTokenId);         var info = turnClassInfoFromToken(token);         var level = info.effectiveLevel || 1;         var out = '';         var i, rd, cmd;         if (!token) {             sendChat(SCRIPT, '/w gm ' + box(title('Turn Undead Menu') + row(bad('Select a cleric token before using the menu macro.'))));             return;         }         out += title(tokenName(token) + ' - Turn Undead Menu');         out += row('<b>Auto Level:</b> ' + pill(String(level)) + ' <span style="color:#ddd;">' + esc(info.label) + '</span>');         out += row(neutral('Buttons scan a 60 ft radius for matching undead type tokens and use standard Roll20 markers only.'));         out += hr();         for (i = 0; i < TURN_UNDEAD_ROWS.length; i++) {             rd = TURN_UNDEAD_ROWS[i];             cmd = '!tu1e --cleric ' + clericTokenId + ' --level ' + level + ' --type ' + rd.key + ' --radius 60';             out += btn(rd.label + ' ' + rd.values[turnUndeadColumnForLevel(level)], cmd, 'Turn ' + rd.label + ' within 60 ft.');             if (i === 5 || i === 10) { out += '<br>'; }         }         sendChat(SCRIPT, '/w gm ' + box(out));     }     function showHelp() {         var out = '';         out += title('TurnUndead1E Help');         out += row('<b>Menu macro:</b>');         out += row('<code>!tu1e-menu --cleric @{selected|token_id}</code>');         out += hr();         out += row('<b>Direct radius macro:</b>');         out += row('<code>!tu1e --cleric @{selected|token_id} --level ?{Effective Cleric Level|1} --type ?{Undead Type|Skeleton,skeleton|Zombie,zombie|Ghoul,ghoul|Shadow,shadow|Wight,wight|Ghast,ghast|Wraith,wraith|Mummy,mummy|Spectre,spectre|Vampire,vampire|Ghost,ghost|Lich,lich|Special**,special} --radius ?{Radius feet|60}</code>');         out += hr();         out += row('<b>Markers:</b> Turned = standard screaming marker with round number. Destroyed = standard dead marker.');         sendChat(SCRIPT, '/w gm ' + box(out));     }     on('chat:message', function (msg) {         var opts;         if (!msg || msg.type !== 'api') { return; }         if (msg.content === '!tu1e-help' || msg.content === '!turnundead1e-help') {             showHelp();             return;         }         if (msg.content.indexOf('!tu1e-menu') === 0 || msg.content.indexOf('!turnundead1e-menu') === 0) {             opts = parseArgs(msg.content);             showMenu(msg, opts.cleric);             return;         }         if (msg.content.indexOf('!tu1e ') === 0 || msg.content.indexOf('!turnundead1e ') === 0) {             opts = parseArgs(msg.content);             resolveTurn(msg, opts);             return;         }     });     log(SCRIPT + ' v' + VERSION + ' ready. Commands: !tu1e-help, !tu1e-menu, !tu1e'); });
Whoa, this is pretty cool, thank you Rotto. I'd love to dump my macros for this. I will have a boo at it later on as I've run into a new issue, but the big question is do the dice roll on the screen?
It rolls the dice internally and shows the results. It's a tradeoff with the API
ROTTO said: It rolls the dice internally and shows the results. It's a tradeoff with the API Rats, the rolling on screen is the most important aspect, even more so than the name of the undead transferring from the API to the macro chat output. I  really  appreciate your offering the code - I've had so much success with TradingPost.js. It's very likely far more efficient than mine, but I can live with mine for now. Hope I'm not being a knob.