Ok. Here's a script that handles this. The basic format is: !mdd <#><type> [...<#><type>] where # is the number of dice to roll and type is which ones. Type is one of: b for blue dice e or bk for enemy dice g for green dice o for orange dice r for red dice s or p for shadow dice y for yellow dice So, to roll 3 blue dice, 2 purple dice and a red die: !mmd 3b 2p 1r Order doesn't matter and you can specify the same die multiple times: !mmd 1b 3r 2b 2g 1y 2s 3e 2r 1bk I don't know if it matters, but you can use !wmmd to whisper the result. Here's what output looks like calling `!mdd 6y 7o 3r 4b 3g 4p 8e`: It will only show tallies for things that were rolled: Note: currently, the images are on my own web server, but I'll move them into Roll20 if that all seems good to you? Try it out and let me know what you think! Mod Script Code: on('ready',()=>{
const DicePools = {
'shadow': {
name: 'Shadow Die',
faces: [
{
face: '<a href="http://roll20api.net/mdd/Purple6.png" rel="nofollow">http://roll20api.net/mdd/Purple6.png</a>',
values: {
shadow: 1,
sword: 0,
mana: 0,
shield: 0,
unblockable: 0,
monster: 0
}
},
{
face: '<a href="http://roll20api.net/mdd/Purple3.png" rel="nofollow">http://roll20api.net/mdd/Purple3.png</a>',
values: {
shadow: 0,
sword: 1,
mana: 0,
shield: 0,
unblockable: 0,
monster: 0
}
},
{
face: '<a href="http://roll20api.net/mdd/Purple4.png" rel="nofollow">http://roll20api.net/mdd/Purple4.png</a>',
values: {
shadow: 0,
sword: 2,
mana: 0,
shield: 0,
unblockable: 0,
monster: 0
}
},
{
face: '<a href="http://roll20api.net/mdd/Purple5.png" rel="nofollow">http://roll20api.net/mdd/Purple5.png</a>',
values: {
shadow: 0,
sword: 3,
mana: 0,
shield: 0,
unblockable: 0,
monster: 0
}
},
{
face: '<a href="http://roll20api.net/mdd/Purple1.png" rel="nofollow">http://roll20api.net/mdd/Purple1.png</a>',
values: {
shadow: 0,
sword: 0,
mana: 1,
shield: 0,
unblockable: 0,
monster: 0
}
},
{
face: '<a href="http://roll20api.net/mdd/Purple2.png" rel="nofollow">http://roll20api.net/mdd/Purple2.png</a>',
values: {
shadow: 0,
sword: 0,
mana: 2,
shield: 0,
unblockable: 0,
monster: 0
}
},
]
},
'enemy': {
name: 'Enemy Die',
faces: [
{
face: '<a href="http://roll20api.net/mdd/Black1.png" rel="nofollow">http://roll20api.net/mdd/Black1.png</a>',
values: {
shadow: 0,
sword: 0,
mana: 0,
shield: 0,
unblockable: 0,
monster: 0
}
},
{
face: '<a href="http://roll20api.net/mdd/Black2.png" rel="nofollow">http://roll20api.net/mdd/Black2.png</a>',
values: {
shadow: 0,
sword: 0,
mana: 0,
shield: 0,
unblockable: 0,
monster: 0
}
},
{
face: '<a href="http://roll20api.net/mdd/Black4.png" rel="nofollow">http://roll20api.net/mdd/Black4.png</a>',
values: {
shadow: 0,
sword: 0,
mana: 0,
shield: 0,
unblockable: 0,
monster: 1
}
},
{
face: '<a href="http://roll20api.net/mdd/Black5.png" rel="nofollow">http://roll20api.net/mdd/Black5.png</a>',
values: {
shadow: 0,
sword: 0,
mana: 0,
shield: 0,
unblockable: 0,
monster: 1
}
},
{
face: '<a href="http://roll20api.net/mdd/Black3.png" rel="nofollow">http://roll20api.net/mdd/Black3.png</a>',
values: {
shadow: 0,
sword: 0,
mana: 0,
shield: 0,
unblockable: 1,
monster: 0
}
},
{
face: '<a href="http://roll20api.net/mdd/Black6.png" rel="nofollow">http://roll20api.net/mdd/Black6.png</a>',
values: {
shadow: 0,
sword: 0,
mana: 0,
shield: 0,
unblockable: 1,
monster: 1
}
},
]
},
'yellow': {
name: 'Yellow Die',
faces: [
{
face: '<a href="http://roll20api.net/mdd/Yellow1.png" rel="nofollow">http://roll20api.net/mdd/Yellow1.png</a>',
values: {
shadow: 0,
sword: 0,
mana: 0,
shield: 0,
unblockable: 0,
monster: 0
}
},
{
face: '<a href="http://roll20api.net/mdd/Yellow2.png" rel="nofollow">http://roll20api.net/mdd/Yellow2.png</a>',
values: {
shadow: 0,
sword: 1,
mana: 0,
shield: 0,
unblockable: 0,
monster: 0
}
},
{
face: '<a href="http://roll20api.net/mdd/Yellow3.png" rel="nofollow">http://roll20api.net/mdd/Yellow3.png</a>',
values: {
shadow: 0,
sword: 1,
mana: 0,
shield: 0,
unblockable: 0,
monster: 0
}
},
{
face: '<a href="http://roll20api.net/mdd/Yellow6.png" rel="nofollow">http://roll20api.net/mdd/Yellow6.png</a>',
values: {
shadow: 0,
sword: 2,
mana: 0,
shield: 0,
unblockable: 0,
monster: 0
}
},
{
face: '<a href="http://roll20api.net/mdd/Yellow4.png" rel="nofollow">http://roll20api.net/mdd/Yellow4.png</a>',
values: {
shadow: 0,
sword: 1,
mana: 1,
shield: 0,
unblockable: 0,
monster: 0
}
},
{
face: '<a href="http://roll20api.net/mdd/Yellow5.png" rel="nofollow">http://roll20api.net/mdd/Yellow5.png</a>',
values: {
shadow: 0,
sword: 1,
mana: 1,
shield: 0,
unblockable: 0,
monster: 0
}
},
]
},
'orange': {
name: 'Orange Die',
faces: [
{
face: '<a href="http://roll20api.net/mdd/Orange1.png" rel="nofollow">http://roll20api.net/mdd/Orange1.png</a>',
values: {
shadow: 0,
sword: 0,
mana: 0,
shield: 0,
unblockable: 0,
monster: 0
}
},
{
face: '<a href="http://roll20api.net/mdd/Orange2.png" rel="nofollow">http://roll20api.net/mdd/Orange2.png</a>',
values: {
shadow: 0,
sword: 1,
mana: 0,
shield: 0,
unblockable: 0,
monster: 0
}
},
{
face: '<a href="http://roll20api.net/mdd/Orange4.png" rel="nofollow">http://roll20api.net/mdd/Orange4.png</a>',
values: {
shadow: 0,
sword: 2,
mana: 0,
shield: 0,
unblockable: 0,
monster: 0
}
},
{
face: '<a href="http://roll20api.net/mdd/Orange5.png" rel="nofollow">http://roll20api.net/mdd/Orange5.png</a>',
values: {
shadow: 0,
sword: 2,
mana: 0,
shield: 0,
unblockable: 0,
monster: 0
}
},
{
face: '<a href="http://roll20api.net/mdd/Orange6.png" rel="nofollow">http://roll20api.net/mdd/Orange6.png</a>',
values: {
shadow: 0,
sword: 3,
mana: 0,
shield: 0,
unblockable: 0,
monster: 0
}
},
{
face: '<a href="http://roll20api.net/mdd/Orange3.png" rel="nofollow">http://roll20api.net/mdd/Orange3.png</a>',
values: {
shadow: 0,
sword: 1,
mana: 1,
shield: 0,
unblockable: 0,
monster: 0
}
},
]
},
'red': {
name: 'Red Die',
faces: [
{
face: '<a href="http://roll20api.net/mdd/Red1.png" rel="nofollow">http://roll20api.net/mdd/Red1.png</a>',
values: {
shadow: 0,
sword: 1,
mana: 0,
shield: 0,
unblockable: 0,
monster: 0
}
},
{
face: '<a href="http://roll20api.net/mdd/Red2.png" rel="nofollow">http://roll20api.net/mdd/Red2.png</a>',
values: {
shadow: 0,
sword: 1,
mana: 0,
shield: 0,
unblockable: 0,
monster: 0
}
},
{
face: '<a href="http://roll20api.net/mdd/Red5.png" rel="nofollow">http://roll20api.net/mdd/Red5.png</a>',
values: {
shadow: 0,
sword: 3,
mana: 0,
shield: 0,
unblockable: 0,
monster: 0
}
},
{
face: '<a href="http://roll20api.net/mdd/Red6.png" rel="nofollow">http://roll20api.net/mdd/Red6.png</a>',
values: {
shadow: 0,
sword: 4,
mana: 0,
shield: 0,
unblockable: 0,
monster: 0
}
},
{
face: '<a href="http://roll20api.net/mdd/Red3.png" rel="nofollow">http://roll20api.net/mdd/Red3.png</a>',
values: {
shadow: 0,
sword: 1,
mana: 1,
shield: 0,
unblockable: 0,
monster: 0
}
},
{
face: '<a href="http://roll20api.net/mdd/Red4.png" rel="nofollow">http://roll20api.net/mdd/Red4.png</a>',
values: {
shadow: 0,
sword: 2,
mana: 1,
shield: 0,
unblockable: 0,
monster: 0
}
},
]
},
'green': {
name: 'Green Die',
faces: [
{
face: '<a href="http://roll20api.net/mdd/Green1.png" rel="nofollow">http://roll20api.net/mdd/Green1.png</a>',
values: {
shadow: 0,
sword: 0,
mana: 0,
shield: 0,
unblockable: 0,
monster: 0
}
},
{
face: '<a href="http://roll20api.net/mdd/Green2.png" rel="nofollow">http://roll20api.net/mdd/Green2.png</a>',
values: {
shadow: 0,
sword: 0,
mana: 0,
shield: 1,
unblockable: 0,
monster: 0
}
},
{
face: '<a href="http://roll20api.net/mdd/Green3.png" rel="nofollow">http://roll20api.net/mdd/Green3.png</a>',
values: {
shadow: 0,
sword: 0,
mana: 0,
shield: 1,
unblockable: 0,
monster: 0
}
},
{
face: '<a href="http://roll20api.net/mdd/Green4.png" rel="nofollow">http://roll20api.net/mdd/Green4.png</a>',
values: {
shadow: 0,
sword: 0,
mana: 0,
shield: 1,
unblockable: 0,
monster: 0
}
},
{
face: '<a href="http://roll20api.net/mdd/Green5.png" rel="nofollow">http://roll20api.net/mdd/Green5.png</a>',
values: {
shadow: 0,
sword: 0,
mana: 0,
shield: 2,
unblockable: 0,
monster: 0
}
},
{
face: '<a href="http://roll20api.net/mdd/Green6.png" rel="nofollow">http://roll20api.net/mdd/Green6.png</a>',
values: {
shadow: 0,
sword: 0,
mana: 0,
shield: 3,
unblockable: 0,
monster: 0
}
},
]
},
'blue': {
name: 'Blue Die',
faces: [
{
face: '<a href="http://roll20api.net/mdd/Blue1.png" rel="nofollow">http://roll20api.net/mdd/Blue1.png</a>',
values: {
shadow: 0,
sword: 0,
mana: 0,
shield: 0,
unblockable: 0,
monster: 0
}
},
{
face: '<a href="http://roll20api.net/mdd/Blue2.png" rel="nofollow">http://roll20api.net/mdd/Blue2.png</a>',
values: {
shadow: 0,
sword: 0,
mana: 0,
shield: 0,
unblockable: 0,
monster: 0
}
},
{
face: '<a href="http://roll20api.net/mdd/Blue3.png" rel="nofollow">http://roll20api.net/mdd/Blue3.png</a>',
values: {
shadow: 0,
sword: 0,
mana: 0,
shield: 1,
unblockable: 0,
monster: 0
}
},
{
face: '<a href="http://roll20api.net/mdd/Blue4.png" rel="nofollow">http://roll20api.net/mdd/Blue4.png</a>',
values: {
shadow: 0,
sword: 0,
mana: 0,
shield: 1,
unblockable: 0,
monster: 0
}
},
{
face: '<a href="http://roll20api.net/mdd/Blue5.png" rel="nofollow">http://roll20api.net/mdd/Blue5.png</a>',
values: {
shadow: 0,
sword: 0,
mana: 0,
shield: 1,
unblockable: 0,
monster: 0
}
},
{
face: '<a href="http://roll20api.net/mdd/Blue6.png" rel="nofollow">http://roll20api.net/mdd/Blue6.png</a>',
values: {
shadow: 0,
sword: 0,
mana: 0,
shield: 2,
unblockable: 0,
monster: 0
}
},
]
}
};
const PoolNameLookup = {
shadow: 'Shadow',
sword: 'Damage',
mana: 'Mana',
shield: 'Blocking',
unblockable: 'Unblockable',
monster: 'Monster'
};
const DiceNameLookup = {
'b': 'blue',
'bk': 'enemy',
'e': 'enemy',
'g': 'green',
'o': 'orange',
'r': 'red',
's': 'shadow',
'p': 'shadow',
'y': 'yellow'
};
const dieRegex = new RegExp(`^(\\d+)(${Object.keys(DiceNameLookup).join('|')})$`,'i');
const times = (n,f) => Array(n).fill(n).map(f);
const roll = (t) => DicePools[t].faces[randomInteger(DicePools[t].faces.length)-1];
const rollN = (t,n) => times(n,()=>roll(t));
const css = (o)=>Object.keys(o).reduce((m,k)=>`${m}${k}:${o[k]};`,'');
const s = {
box: css({
["font-weight"] : "bold",
["border-bottom"] : "2px solid #0F3DA0",
["border-top"] : "4px solid #0F3DA0",
["background-color"] : "#AEB6C6"
}),
heading: css({
["font-weight"] : "bold",
["font-size"] : "1.3em"
}),
row: css({
["margin"] : ".1em",
["border-bottom"] : "1px solid #0F3DA0"
}),
stat_l: css({
["min-width"] : "7em",
["font-weight"] : "bold",
["color"] : "purple",
["display"] : "inline-block",
}),
die: css({
["display"] : "inline-block",
["margin"] : ".1em",
["font-size"] : "1.3em",
["max-width"] : "3em",
["border"] : "2px solid black",
["border-radius"] : ".25em",
["background-color"] : "white",
["color"] : "black",
["float"] : "left"
}),
clear: css({
["clear"] : "both"
})
};
const f = {
content: (t,...c) => `<div style="${s.content}">${f.heading(t)}${c.join('')}</div>`,
heading: (t) => `<div style="${s.heading}">${t}</div>`,
box: (t) => `<div style="${s.box}">${t}</div>`,
clear: () => `<div style="${s.clear}"></div>`,
stat_l: (l) => `<span style="${s.stat_l}">${PoolNameLookup[l]}:</span>`,
stat_v: (v) => `<code>${v}</code>`,
stat: (l,v) => `<li style="${s.stat}">${f.stat_l(l)} ${f.stat_v(v)}</li>`,
stats: (ss) => `<ul>${Object.keys(ss).filter(k=>ss[k]).map(k=>f.stat(k,ss[k])).join('')}</ul>`,
die: (img) => `<img style="${s.die}" src="${img}" />`,
row: (...o) => `<div style="${s.row}">${o.join(' ')}${f.clear()}</div>`
};
const formatOutput = (r,t) => {
return f.content(
t,
f.row(...r.f.map(i=>f.die(i))),
f.stats(r.v)
);
};
const processInlinerolls = (msg) => {
if(msg.hasOwnProperty('inlinerolls')){
return msg.inlinerolls
.reduce((m,v,k) => {
let ti=v.results.rolls.reduce((m2,v2) => {
if(v2.hasOwnProperty('table')){
m2.push(v2.results.reduce((m3,v3) => [...m3,(v3.tableItem||{}).name],[]).join(", "));
}
return m2;
},[]).join(', ');
return [...m,{k:`$[[${k}]]`, v:(ti.length && ti) || v.results.total || 0}];
},[])
.reduce((m,o) => m.replace(o.k,o.v), msg.content);
} else {
return msg.content;
}
};
on('chat:message',msg=>{
if('api'===msg.type && /^![w]?mdd(\b\s|$)/i.test(msg.content) && playerIsGM(msg.playerid)){
let who = (getObj('player',msg.playerid)||{get:()=>'API'}).get('_displayname');
let w=/^!w/.test(msg.content);
let content = processInlinerolls(msg);
let roll = content.split(/\s+/)
.slice(1)
.filter(e=>dieRegex.test(e))
.map(e=>e.match(dieRegex).slice(1,3))
.map(d=>rollN(DiceNameLookup[d[1].toLowerCase()],parseInt(d[0])))
.reduce((m,r)=>[...m,...r],[])
.reduce((m,r)=>{
m.f.push(r.face);
Object.keys(r.values).forEach(k=>m.v[k]=(m.v[k]||0)+r.values[k]);
return m;
},{f:[],v:{}})
;
let out=formatOutput(roll,`Result <a href="${content}">${content.replace(/^[^\s]* /,'')}</a>`);
sendChat('MDDice',`${w?`/w "${who}" `:''}${out}`);
}
});
});