Regarding the something you need (1) -- Can you give a thorough example of what you mean? You could already do this: !torg 0d0+(@{target|Foo} [Foo]) ==SomeAttr --Including some extra something from a target creature other than the selected ones. This would use the same target for all selected tokens' rolls. Regarding things that would help: 1) It already does this. You only need to run it again if you need to reset the statuses that are negative. 2) That's definitely doable, probably would need to pass the status and the name, something like: !torg-name --blue|Frost Damage --red|Fire Damage Ok. summary: !torg-set -- you can no use this with nothing selected to tell you what is configured for negatives !torg-set !torg-name -- use this to name statuses !torg-name --blue|Frost --green|Poison --edge-crack|Break all the Things Use it with no arguments to see the list. Don't supply a name to something to remove it: !torg-name --blue !torg -- This is unchanged, except now it will include names for things if there are names set: Give this one a try: on('ready',()=>{
const version = '0.1.1',
lastUpdate = 1508088657,
schemaVersion = 0.2;
const apiCmd = /^!torg\b($|\s+)/i;
const apiCmdSet =/^!torg-set\b/i;
const apiCmdName =/^!torg-name\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 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);
const statusBubble = (contents) =>`<span style="white-space: nowrap;display:inline-block;border: 1px solid #999;background-color:#eff;border-radius:.4em;padding: .1em .4em;">${contents}</span>`;
const statusName = (status) => state.TorgStatus.statusNames[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=>` ${state.TorgStatus.negativeStatuses.includes(st.status) ? '-' :'+'}${st.num} ${getStatusIcon(st.status)}${statusName(st.status)}`).map(statusBubble).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 0.1:
state.TorgStatus.statusNames = {};
/* falls through */
case 'UpdateSchemaVersion':
state.TorgStatus.version = schemaVersion;
break;
default:
state.TorgStatus = {
version: schemaVersion,
negativeStatuses: [],
statusNames: {}
};
break;
}
}
};
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);}})
;
} else if(apiCmdSet.test(orig_msg.content) && playerIsGM(orig_msg.playerid) ){
let who=(getObj('player',orig_msg.playerid)||{get:()=>'API'}).get('_displayname');
if(orig_msg.selected){
_.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)
;
}
sendChat('',`/w "${who}" Negative Statuses: ${_.map(state.TorgStatus.negativeStatuses,getStatusIcon).join('')}`);
} else if(apiCmdName.test(orig_msg.content) && playerIsGM(orig_msg.playerid) ){
let who=(getObj('player',orig_msg.playerid)||{get:()=>'API'}).get('_displayname');
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();
}
msg.content
.split(/\s+--/)
.slice(1)
.map(s=>({status:s.split(/\|/)[0],name:s.split(/\|/)[1]}))
.filter(s=>statuses.includes(s.status))
.forEach(s=>s.name ? state.TorgStatus.statusNames[s.status]=s.name : delete state.TorgStatus.statusNames[s.status] )
;
sendChat('',`/w "${who}" Status Names: ${Object.keys(state.TorgStatus.statusNames).map((k)=>`${getStatusIcon(k)}:${state.TorgStatus.statusNames[k]}`).map(statusBubble).join('')}`);
}
}
});
checkInstall();
});