Give this one a try. Same commands as above, but you can also put == followed by a list of attributes: !torg 1d6 ==level test test|max --some bunch of words to describe things, if you want == must come before --, you can append |max to get the max value, they'll get added in if they exist: Whatever is before the == will get passed as a roll to perform (the 1d6 above). You can omit that roll part: !torg ==level test test|max --some bunch of words to describe things, if you want and you'll just get the total of the bonuses: If it doesn't have the attribute, it won't even list it, if it has the attribute and it doesn't have a number, it will show what it has but only add 0. Script: on('ready',()=>{
const version = '0.1.0',
lastUpdate = 1508011212,
schemaVersion = 0.1;
const apiCmd = /^!torg\b($|\s+)/i;
const apiCmdSet =/^!torg-set\b/i;
const statuses = [
'red', 'blue', 'green', 'brown', 'purple', 'pink', 'yellow', // 0-6
'skull', 'sleepy', 'half-heart', 'half-haze', 'interdiction',
'snail', 'lightning-helix', 'spanner', 'chained-heart',
'chemical-bolt', 'death-zone', 'drink-me', 'edge-crack',
'ninja-mask', 'stopwatch', 'fishing-net', 'overdrive', 'strong',
'fist', 'padlock', 'three-leaves', 'fluffy-wing', 'pummeled',
'tread', 'arrowed', 'aura', 'back-pain', 'black-flag',
'bleeding-eye', 'bolt-shield', 'broken-heart', 'cobweb',
'broken-shield', 'flying-flag', 'radioactive', 'trophy',
'broken-skull', 'frozen-orb', 'rolling-bomb', 'white-tower',
'grab', 'screaming', 'grenade', 'sentry-gun', 'all-for-one',
'angel-outfit', 'archery-target'
];
const statusColormap = ['#C91010', '#1076c9', '#2fc910', '#c97310', '#9510c9', '#eb75e1', '#e5eb75'];
const getStatusIconByIndex = (idx) => (idx<7)
? `<div style="width: 1em; height: 1em; border-radius:20px; display:inline-block; margin: 0; border:0; cursor: pointer;background-color: ${statusColormap[idx]}"></div>`
:`<div style="width: 1em; height: 1em; display:inline-block; margin: 0; border:0; cursor: pointer;padding:0;background-image: url('<a href="https://app.roll20.net/images/statussheet.png');background-repeat:no-repeat;background-position" rel="nofollow">https://app.roll20.net/images/statussheet.png');background-repeat:no-repeat;background-position</a>: ${((-(34/24))*(idx-7))}em 0;background-size:auto 100%;"></div>`
;
const getStatusIcon = (status) => getStatusIconByIndex(_.indexOf(statuses,status));
const parseStatuses = (statuses) => {
let s = statuses.split(/,/).map((sb)=>({status:sb.split(/@/)[0],num:sb.split(/@/)[1]||0}));
return {
roll:s.map(st=>`( ${state.TorgStatus.negativeStatuses.includes(st.status) ? '-' :''}${st.num} [${st.status}])`).join('+'),
mesg:s.map(st=>` <span style="white-space: nowrap;display:inline-block;border: 1px solid #999;background-color:#eff;border-radius:.4em;padding: .1em .4em;">${state.TorgStatus.negativeStatuses.includes(st.status) ? '-' :'+'}${st.num} ${getStatusIcon(st.status)}</span> `).join(''),
total:s.reduce((m,st)=>m+(state.TorgStatus.negativeStatuses.includes(st.status) ? -1 : 1) * st.num, 0)
};
};
const checkInstall = () => {
log('-=> TorgStatus v'+version+' <=- ['+(new Date(lastUpdate*1000))+']');
if( ! _.has(state,'TorgStatus') || state.TorgStatus.version !== schemaVersion) {
log(' > Updating Schema to v'+schemaVersion+' <');
switch(state.TorgStatus && state.TorgStatus.version) {
case 0.0:
case 'UpdateSchemaVersion':
state.TorgStatus.version = schemaVersion;
break;
default:
state.TorgStatus = {
version: schemaVersion,
negativeStatuses: []
};
break;
}
}
};
const outer = (contents) => `<div style="margin-bottom: .3em;border:1px solid #999;background-color: #ffe;padding: .2em; border-radius:.2em;">${contents}<div style="clear:both;"></div></div>`;
const inner = (contents) => `<div>${contents}</div>`;
const result = (contents) => `<div style="display:inline-block;font-size: 1.5em; border: 1px solid #999; background-color: #fef; font-weight: bold; border-radius: .2em; padding: .4em .2em;margin-right: .2em;">${contents}</div>`;
const icon = (img)=>`<img style="max-height:2.6em;max-width:4em; float:left;" src="${img}">`;
const rollFmt = (contents) =>`[[${contents}]]`.replace(/\[\[\s+/,'[[').replace(/\[\[\s+\[\[/,'[[[[');
const attrRoll = (attrs) => attrs.map(a=>`${parseInt(a.value)||0} [${a.attr}]`).join('+');
const attrMesg = (attrs) => attrs.map(a=>` <span style="white-space: nowrap;display:inline-block;border: 1px solid #999;background-color:#eff;border-radius:.4em;padding: .1em .4em;">${a.value} ${a.attr}</span> `).join('');
const attrTotal = (attrs) =>attrs.reduce((m,a)=>m+parseFloat(a.value)||0,0);
on('chat:message',(orig_msg)=>{
if('api' === orig_msg.type) {
if(apiCmd.test(orig_msg.content) ){
let msg=_.clone(orig_msg);
if(_.has(msg,'inlinerolls')){
msg.content = _.chain(msg.inlinerolls)
.reduce(function(m,v,k){
var ti=_.reduce(v.results.rolls,function(m2,v2){
if(_.has(v2,'table')){
m2.push(_.reduce(v2.results,function(m3,v3){
m3.push(v3.tableItem.name);
return m3;
},[]).join(', '));
}
return m2;
},[]).join(', ');
m['$[['+k+']]']= (ti.length && ti) || v.results.total || 0;
return m;
},{})
.reduce(function(m,v,k){
return m.replace(k,v);
},msg.content)
.value();
}
let mesg = msg.content.split(/\s+--/);
let attrs = mesg.shift().split(/\s+==/);
let roll = attrs.shift().replace(apiCmd,'').trim()||'';
attrs=attrs.reduce((m,a)=>_.union(m,a.split(/\s+/)),[]).map(a=>a.toLowerCase()).reduce((m,a)=>{
let p=a.split(/\|/);
let n=p[0];
let f=(p[1]==='max'?'max':'current');
return Object.assign(m,{[n]:(m[n]||[]).concat([f])});
},{});
mesg = mesg.join(' ');
_.chain(msg.selected)
.map((o)=>getObj('graphic',o._id))
.reject(_.isUndefined)
.map((o)=>({
t: o,
a: findObjs({type:'attribute',characterid:o.get('represents')})
.filter((a)=>Object.keys(attrs).includes(a.get('name').toLowerCase()))
.reduce((m,a)=>m.concat(attrs[a.get('name').toLowerCase()].map(f=>({attr:`${a.get('name')}${f==='max'?'|max':''}`,value:a.get(f)}))),[]),
b: parseStatuses(o.get('statusmarkers'))
}))
.map((o)=>outer(
icon(o.t.get('imgsrc'))+
inner(
result(roll.length
? rollFmt(`${roll}+[attributes:](${attrRoll(o.a)})+[status:](${o.b.roll})`)
: (attrTotal(o.a)+o.b.total))+
(mesg.length ? mesg : '')
) +
`<div>${attrMesg(o.a)}${o.b.mesg}</div>`
))
.tap(m=>{try{sendChat('',m.join(''));}catch(e){log(m);$d({roll});}})
;
} else if(apiCmdSet.test(orig_msg.content) && playerIsGM(orig_msg.playerid) ){
let who=(getObj('player',orig_msg.playerid)||{get:()=>'API'}).get('_displayname');
_.chain(orig_msg.selected)
.map((o)=>getObj('graphic',o._id))
.reject(_.isUndefined)
.reduce((m,o)=>_.union(m,o.get('statusmarkers').split(/@\d,|@\d|,/)),[])
.filter(s=>s.length&&s!=='dead')
.tap(s=>state.TorgStatus.negativeStatuses=s)
.tap(s=>sendChat('',`/w "${who}" Negative Statuses: ${_.map(s,getStatusIcon).join('')}`))
;
}
}
});
checkInstall();
});