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 .
×
Create a free account

[Script Update] TokenMod -- An interface to adjusting properties of a token from a macro or the chat area.

Perfect, thank you so very much
Still no one-click :(
1492564731
The Aaron
Pro
API Scripter
Weird! &nbsp;I've pushed it up again, I must have failed to send a pull request somehow!? &nbsp;Here is the pull request:&nbsp; <a href="https://github.com/Roll20/roll20-api-scripts/pull/" rel="nofollow">https://github.com/Roll20/roll20-api-scripts/pull/</a>...
1492621150
The Aaron
Pro
API Scripter
Ok, Phil merged this in this morning (because he's AWESOME!) so this should be in the 1-click now.
Yay! 😊
The Aaron said: Ok, Phil merged this in this morning (because he's AWESOME!) so this should be in the 1-click now. Thanks for the update and all the hard work you rock!
1492623811
The Aaron
Pro
API Scripter
No problem! &nbsp;Let me know if you hit any issues. =D
I hit an issue. 😝 I am trying to use the following command: !token-mod --set statusmarkers|?fluffy-wing:?{Height in tens of feet, i.e. 30 = 3|0} It works great if I take out the ? before fluffy-wing, but as-is it just removes it regardless of if I put 3 or 0 in the dialog box. Any ideas? &nbsp;I can send you an invite to my test game if you'd like.&nbsp;
I think the ? only edits the status marker thats already there. So maybe you could add it manually or break this into 2 macros? Fly Toggle !token-mod --set statusmarkers|!fluffy-wing Elevation Change !token-mod --set statusmarkers|?fluffy-wing:?{Elevation:|Up,+|Down,-}?{Height in tens of feet, i.e. 30 = 3|0}
1492653234
The Aaron
Pro
API Scripter
Hmm. I'll have to check the code. I've only ever tested the ? With relative changes, with the intent of removing it at 0.&nbsp;
The Aaron said: Hmm. I'll have to check the code. I've only ever tested the ? With relative changes, with the intent of removing it at 0.&nbsp; To be fair, that was my original use for it also. But now I'd like to duplicate the idea behind the Fly script, which is no longer maintained. Also, having fewer scripts just reduces the chance of conflicts. So I figured as long as one digit is enough, TokenMod will work just fine for that concept. But I'm having so much fun exploring all the possibilities of scripting, the number of token macros I have is exploding, so I'd really like to not require a second macro to take the Wing status off when people land. &nbsp;Thus, my desire to both input a number rather than decrement, and take it off when 0 is input, using one macro. :-)
1492708516
The Aaron
Pro
API Scripter
Makes sense. =D &nbsp;You can use the array syntax if you want more digits... !token-mod --set statusmarkers|fluffy-wing[2]:3|fluffy-wing[1]:5 I'll look into how I can make that easier to use. =D
The Aaron said: Makes sense. =D &nbsp;You can use the array syntax if you want more digits... !token-mod --set statusmarkers|fluffy-wing[2]:3|fluffy-wing[1]:5 I'll look into how I can make that easier to use. =D I'm not too fussed about the second digit yet. 10s of feet is probably enough granularity for my game at the moment. Thanks for the example, though! You should collect these every time someone asks a question and have a big pile of examples available somewhere (can the help have a button to open a new webpage?). The more examples of different uses, the faster people will pick up all the possible syntax. Thanks again for creating this and sharing it!
1492709300
The Aaron
Pro
API Scripter
I have lots of plans like that... just need more time to do it in... =D
The Aaron said: I have lots of plans like that... just need more time to do it in... =D I know the feeling! &nbsp;And bug fixing existing functionality always makes sense to be the priority.&nbsp;
1492710099
The Aaron
Pro
API Scripter
Well, it's largely that I have 50+ scripts and if I'm going to do it once, I'd rather do it for all of them. =D &nbsp; "Better is the enemy of Good Enough" , as my bro likes to say...
1492710204
The Aaron
Pro
API Scripter
Just released scripts...&nbsp;
WOW! :o So I shouldn't ask you to debug / take over the Token Path script that seems to be buggy as is? ;-) LOL
1492710645
The Aaron
Pro
API Scripter
=D &nbsp;well, you can ASK anything you like, but I might have to decline. =D &nbsp; What's this Token Path thing?
<a href="https://wiki.roll20.net/Script:Token_Path" rel="nofollow">https://wiki.roll20.net/Script:Token_Path</a> Seems to work great, and it would be fantastically useful for my players (we are all still relatively new to Roll20), who frequently are like "Hmm, well maybe if I go this way instead, I'll step over here to see what I can see, how many squares do I have left again?" LOL But when I go to close combat, the dot tokens it creates to show your steps stick around, and I've gotten crashes in the API a few times just trying it out. &nbsp;I can PM you an invite to my test game if you'd like, and don't feel like installing it yourself. Alternatively, I was considering searching the forum and starting a new thread about it when I had time. I was assuming that since it appears to have issues and hasn't been updated on Roll20 for a couple years it has been abandoned, but I could be wrong. Maybe it still works fine if you aren't also using TurnMarker. ;-)&nbsp;
1492712548
The Aaron
Pro
API Scripter
I wouldn't think it would interfere, but you never know... =D
Agreed, and I didn't mean to imply that was my primary suspicion, and I haven't tried isolating it by disabling other scripts yet. I was just going to ask you to take a glance at the code and see if there is an obviously outdated API call or clear oops in the logic or something. ;-) &nbsp;I'm just being lazy, really. :-P
1492715929
The Aaron
Pro
API Scripter
no worries. =D
Just curious, is there a !token-mod command to remove all status markers? &nbsp;Unless I missed it, seems that I need to specify the marker to remove as part of the command.
1494170191
The Aaron
Pro
API Scripter
There isn't an explicit command but you can do: !token-mod --set statusmarkers|=blue|-blue
I call this macro clear status. !token-mod --set statusmarkers|-bleeding-eye|-broken-heart|-edge-crack|-screaming|-grab|-lightning-helix|-aura|-chemical-bolt|-back-pain|-fishing-net|-fist|-sleepy|-ninja-mask|-angel-outfit|-red|-blue|-green|-brown|-purple|-pink|-yellow|-dead|-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 name|@{selected|character_name}
Craven said: I call this macro clear status. !token-mod --set statusmarkers|-bleeding-eye|-broken-heart|-edge-crack|-screaming|-grab|-lightning-helix|-aura|-chemical-bolt|-back-pain|-fishing-net|-fist|-sleepy|-ninja-mask|-angel-outfit|-red|-blue|-green|-brown|-purple|-pink|-yellow|-dead|-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 name|@{selected|character_name} You sir have saved my life, I was calling each status with a separate !token-mod command... I knew if I browsed this forum long enough I would find someone smarter than I to fix my verbose macro.
1494839799
Ziechael
Forum Champion
Sheet Author
API Scripter
From the Power Cards thread: Sylverlokk said: What am I doing wrong with this: !token-mod --set statusmarkers|!bleeding-eye:?{Turns|1} it's just supposed to ask for how many turns and set that status on with that number regardless of whether it was on before or not. Is that not possible? Or am I just doing it wrong? Ziechael said: You'd be best off posting in the tokenMod thread for help with commands relating to that script but on the face of it I'd say you need to use: !token-mod --set statusmarkers|!bleeding-eye:[[?{Turns|1}]] To get it to place the status regardless of other ones with a single query (but may affect other statuses if you have a low turn count) you could do: !token-mod --set statusmarkers|!bleeding-eye[?{Turns|1}]:[[?{Turns|1}]] Or if you are happy with multiple queries: !token-mod --set statusmarkers|!bleeding-eye[?{Index|1}]:[[?{Turns|1}]]
1494844918
The Aaron
Pro
API Scripter
The issue is the ! before bleeding-eye. &nbsp;! toggle a status on and off, so this would set bleeding-eye with a number on the first use, then clear it, then set it again, etc. This is probably what you want: !token-mod --set statusmarkers|bleeding-eye:?{Turns|1}
A strange request - would it be possible to add a --help sub-option to show JUST the list of status names?&nbsp; 9/10 when I'm checking the help, I'm checking to get the name of one of the status icons.&nbsp; A display that showed the status icon next to the name would be great too.&nbsp; &gt;_&gt;
1499560380
vÍnce
Pro
Sheet Author
I never needed to resize the chat window until the first time I tried to read Aaron's dissertation on Tokenmod's features. &nbsp;;P
1499560643
The Aaron
Pro
API Scripter
Hahaha!! &nbsp;That's actually something I've been considering. I'll see what I can do.&nbsp;
1501516007
The Aaron
Pro
API Scripter
Update v0.8.35 -- Changed finding of attributes to be case-insensitive. It turns out that the client interface only finds the first matching attribute and ignores case, so it's reasonable for the API to do the same. (thanks Alexander )
Hi, I'm sorry if these have been answered already, but I didn't have time to sift through all the posts here. My question is, is it possible to reference a token's status markers? Like, instead of --set , I want to --get them.
1504187319
The Aaron
Pro
API Scripter
Not with this script, but it wouldn't be hard to write a script that would translate the status markers into attributes. &nbsp;I might have already done that in the past. &nbsp; If you just want a listing of status markers in the chat, that's even easier: on('ready',() =&gt; { &nbsp; &nbsp; on('chat:message', (msg) =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; const who=(getObj('player',msg.playerid)||{get:()=&gt;'API'}).get('_displayname'); &nbsp; &nbsp; &nbsp; &nbsp; let cmd, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ids, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; whisper=false; &nbsp; &nbsp; &nbsp; &nbsp; if('api' !== msg.type){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; ids = msg.content.split(/\s+/); &nbsp; &nbsp; &nbsp; &nbsp; cmd = ids.shift(); &nbsp; &nbsp; &nbsp; &nbsp; ids = _.union( (playerIsGM(msg.playerid) ? ids : []), ((msg.selected && _.pluck(msg.selected,'_id')) || []) ); &nbsp; &nbsp; &nbsp; &nbsp; switch(cmd){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case '!wstatus': &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; whisper=true; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; /* eslint-disable no-fallthrough */ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case '!status': &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; /* eslint-enable no-fallthrough */ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(ids.length){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _.chain(ids) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .uniq() &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .map( (id) =&gt; getObj('graphic',id) ) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .reject(_.isUndefined) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .reduce( (m,t)=&gt;{ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;m.push(`&lt;div&gt;&lt;b&gt;${t.get('name')}&lt;/b&gt;: ${t.get('statusmarkers').split(/,/).map((s)=&gt;s.replace(/@(\d+)/,(a,b)=&gt;`(x${b})`)).join(', ')}&lt;/div&gt;`); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;return m; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, []) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .tap((m)=&gt;{ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChat('',`${whisper ? `/w "${who}" `: ''} ${m.join('')}`); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChat('',`/w "${who}" Use &lt;code&gt;!status&lt;/code&gt; with one or more tokens selected, or supply token IDs with &lt;code&gt;!status ID1 ID2&lt;/code&gt;. You can use &lt;code&gt;!wstatus&lt;/code&gt; to have the results whispered to you.`); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }); }); With the above script, select one or more tokens and run: !status to get a listing of their statuses. &nbsp;If you'd rather they were whispered, use: !wstatus You can also specify one or more ids as arguments to either command: !wstatus @{target|token_id}
The Aaron said: The issue is the ! before bleeding-eye. &nbsp;! toggle a status on and off, so this would set bleeding-eye with a number on the first use, then clear it, then set it again, etc. This is probably what you want: !token-mod --set statusmarkers|bleeding-eye:?{Turns|1} What does ?{Turns|1} do ?
1505959994
The Aaron
Pro
API Scripter
It's a roll query. It prompts the user for a value, defaulting to 1. In the example above, it sets the number on the status marker.&nbsp;
1505961617

Edited 1505962315
does it count down as the turn rolls over in the tracker?
1505962424

Edited 1505962466
No it won't do that, you would need another one to count down and track in manually. I have something like that for Rage.
1507937367

Edited 1507937400
GM Matt
Sheet Author
Love this script. I think this question has already been answered, but I just wanted to make sure I'm clear about it: (1) There is no way to read the existence or value of a status marker in a macro without API script (i.e., to apply modifiers to a roll) (2) There is no existing API script that is explicitly designed to do this, either. ??
1507938706
The Aaron
Pro
API Scripter
You mean you want to do something like: [[1d20+@{selected|status_blue}]] and add 3 if the blue status marker has a 3 on it?
1507939064

Edited 1507939094
GM Matt
Sheet Author
Yes, exactly. Can that be done? Is that how its done? (Please say "yes")
1507939420
The Aaron
Pro
API Scripter
It's not built in, but it can be done with the caveat you'd need a character to attach the attributes to, or you'd need to pass the formula to an API script to be expanded.&nbsp; The character part is easy, but you wouldn't get much utility out of it with mooks as they'd all have the same attribute numbers.&nbsp; With an API script, you could access them directly on the token but that might not play well with other things you want to do. What's your use case?
I'm working with Torg Eternity, which uses an old-school system that takes into account a huge array of modifiers to genrerate a difficulty number for an attack. Target concealed? -2 Aimed last turn? +4 Burst fire on automatic weapon? +2 Target vulnerable? +2 I want to set status markers on the token to account for these different conditions (thus, allowing manipulation of mooks when needed, like you suggested) - which I'm doing successfully with TokenMod (thanks, again!). So now I need to create a macro which outputs the final difficulty number to a template by taking into account all of the modifiers. Trying to get things where we can put all that math into chat for everyone to see. I'm not sure I can pull off something that keeps writing and reading from the character sheets. Not proficient enough in jscript.&nbsp;
1507950686

Edited 1507950790
The Aaron
Pro
API Scripter
That sounds like something you'd want to write a custom API script for.&nbsp; You can't put negative numbers in the status markers, so you'd need to declare some of them as negative and some as positive.&nbsp; They only have a range of 1-9, so you couldn't account for a bonus any higher/lower than that.&nbsp;&nbsp; Try this out.&nbsp; Grab any token and set the statuses on it you want to use as negative then run this: !torg-set Then set a bunch of statuses with numbers on some tokens, select them and type this: !torg 1d20 --Using some fancy things. With this Script: on('ready',()=&gt;{ &nbsp; &nbsp; const version = '0.1.0', &nbsp; &nbsp; &nbsp; &nbsp; lastUpdate = 1507950226, &nbsp; &nbsp; &nbsp; &nbsp; schemaVersion = 0.1; &nbsp; &nbsp; const apiCmd = /^!torg\b($|\s+)/i; &nbsp; &nbsp; const apiCmdSet =/^!torg-set\b/i; &nbsp; &nbsp; const statuses = [ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'red', 'blue', 'green', 'brown', 'purple', 'pink', 'yellow', // 0-6 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'skull', 'sleepy', 'half-heart', 'half-haze', 'interdiction', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'snail', 'lightning-helix', 'spanner', 'chained-heart', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'chemical-bolt', 'death-zone', 'drink-me', 'edge-crack', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'ninja-mask', 'stopwatch', 'fishing-net', 'overdrive', 'strong', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'fist', 'padlock', 'three-leaves', 'fluffy-wing', 'pummeled', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'tread', 'arrowed', 'aura', 'back-pain', 'black-flag', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'bleeding-eye', 'bolt-shield', 'broken-heart', 'cobweb', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'broken-shield', 'flying-flag', 'radioactive', 'trophy', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'broken-skull', 'frozen-orb', 'rolling-bomb', 'white-tower', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'grab', 'screaming', 'grenade', 'sentry-gun', 'all-for-one', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'angel-outfit', 'archery-target' &nbsp; &nbsp; &nbsp; &nbsp; ]; &nbsp; &nbsp; const statusColormap = ['#C91010', '#1076c9', '#2fc910', '#c97310', '#9510c9', '#eb75e1', '#e5eb75']; &nbsp; &nbsp; const getStatusIconByIndex = (idx) =&gt; (idx&lt;7) &nbsp; &nbsp; &nbsp; &nbsp; ? `&lt;div style="width: 1em; height: 1em; border-radius:20px; display:inline-block; margin: 0; border:0; cursor: pointer;background-color: ${statusColormap[idx]}"&gt;&lt;/div&gt;` &nbsp; &nbsp; &nbsp; &nbsp; :`&lt;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%;"&gt;&lt;/div&gt;` &nbsp; &nbsp; &nbsp; &nbsp; ; &nbsp; &nbsp; const getStatusIcon = (status) =&gt; getStatusIconByIndex(_.indexOf(statuses,status)); &nbsp; &nbsp; const parseStatuses = (statuses) =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; let s = statuses.split(/,/).map((sb)=&gt;({status:sb.split(/@/)[0],num:sb.split(/@/)[1]||0})); &nbsp; &nbsp; &nbsp; &nbsp; return { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; roll:_.map(s,(st)=&gt;`(${state.TorgStatus.negativeStatuses.includes(st.status) ? '-' :''}${st.num}[${st.status}])`).join('+'), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; mesg:_.map(s,(st)=&gt;`${state.TorgStatus.negativeStatuses.includes(st.status) ? '-' :'+'}${st.num}${getStatusIcon(st.status)}`).join('') &nbsp; &nbsp; &nbsp; &nbsp; }; &nbsp; &nbsp; }; &nbsp; &nbsp; const checkInstall = () =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; log('-=&gt; TorgStatus v'+version+' &lt;=-&nbsp; ['+(new Date(lastUpdate*1000))+']'); &nbsp; &nbsp; &nbsp; &nbsp; if( ! _.has(state,'TorgStatus') || state.TorgStatus.version !== schemaVersion) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; log('&nbsp; &gt; Updating Schema to v'+schemaVersion+' &lt;'); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; switch(state.TorgStatus && state.TorgStatus.version) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case 0.0: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case 'UpdateSchemaVersion': &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; state.TorgStatus.version = schemaVersion; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; default: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; state.TorgStatus = { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; version: schemaVersion, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; negativeStatuses: [] &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; on('chat:message',(orig_msg)=&gt;{ &nbsp; &nbsp; &nbsp; &nbsp; if('api' === orig_msg.type) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(apiCmd.test(orig_msg.content) ){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let msg=_.clone(orig_msg); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(_.has(msg,'inlinerolls')){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; msg.content = _.chain(msg.inlinerolls) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .reduce(function(m,v,k){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var ti=_.reduce(v.results.rolls,function(m2,v2){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(_.has(v2,'table')){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; m2.push(_.reduce(v2.results,function(m3,v3){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; m3.push(v3.tableItem.name); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return m3; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; },[]).join(', ')); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return m2; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; },[]).join(', '); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; m['$[['+k+']]']= (ti.length && ti) || v.results.total || 0; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return m; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; },{}) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .reduce(function(m,v,k){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return m.replace(k,v); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; },msg.content) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .value(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let args = msg.content.split(/\s+--/); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let roll = args.shift().replace(apiCmd,'')||'0d0'; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let mesg = args.join(' '); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _.chain(msg.selected) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .map((o)=&gt;getObj('graphic',o._id)) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .reject(_.isUndefined) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .map((o)=&gt;({ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; t: o, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; b: parseStatuses(o.get('statusmarkers')) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; })) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .map((o)=&gt;`&lt;div&gt;&lt;img style="max-height:1.1em;max-width:2em;" src="${o.t.get('imgsrc')}"&gt;[[${roll}+(${o.b.roll})]] ${mesg} ${o.b.mesg}&lt;/div&gt;`) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .tap(m=&gt;sendChat('',m.join(''))) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else if(apiCmdSet.test(orig_msg.content) && playerIsGM(orig_msg.playerid) ){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let who=(getObj('player',orig_msg.playerid)||{get:()=&gt;'API'}).get('_displayname'); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _.chain(orig_msg.selected) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .map((o)=&gt;getObj('graphic',o._id)) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .reject(_.isUndefined) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .reduce((m,o)=&gt;_.union(m,o.get('statusmarkers').split(/@\d,|@\d|,/)),[]) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .filter(s=&gt;s.length&&s!=='dead') &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .tap(s=&gt;state.TorgStatus.negativeStatuses=s) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .tap(s=&gt;sendChat('',`/w "${who}" Negative Statuses: ${_.map(s,getStatusIcon).join('')}`)) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }); &nbsp; &nbsp; checkInstall(); }); Can play with the formatting and such, but that's the general idea...
Wonderful! Thanks for taking the time, Aaron! - Matt
1507955485
The Aaron
Pro
API Scripter
Cheers!&nbsp; Let me know if you need assistance reformatting the messages and such.&nbsp; This could be expanded in several ways if needed.&nbsp; Adding whispering wouldn't be hard, as well as adding the names of the tokens instead of just the images (and maybe making them bigger).
1508003183

Edited 1508003207
GM Matt
Sheet Author
Thanks again, Aaron. Is it possible to set this up so that I can include stats off of selected and target character sheets as a part of the die roll? (In truth, I don't need the die roll, because I'm just displaying up the difficulty of the check - which will follow - in chat) I tried&nbsp; !torg [[0d0 + @{target|DodgeAdds}]] --Using some fancy things. to no avail
1508005213
The Aaron
Pro
API Scripter
It will probably work without the [[ and ]]. So, would you say that ideally you'd like: For each selected token Output the sum of: The status marker values (positive and negative a list of attributes to be drawn from the associated character That sound about right?
1508011615
The Aaron
Pro
API Scripter
Give this one a try.&nbsp; 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).&nbsp; 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',()=&gt;{ &nbsp; &nbsp; const version = '0.1.0', &nbsp; &nbsp; &nbsp; &nbsp; lastUpdate = 1508011212, &nbsp; &nbsp; &nbsp; &nbsp; schemaVersion = 0.1; &nbsp; &nbsp; const apiCmd = /^!torg\b($|\s+)/i; &nbsp; &nbsp; const apiCmdSet =/^!torg-set\b/i; &nbsp; &nbsp; const statuses = [ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'red', 'blue', 'green', 'brown', 'purple', 'pink', 'yellow', // 0-6 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'skull', 'sleepy', 'half-heart', 'half-haze', 'interdiction', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'snail', 'lightning-helix', 'spanner', 'chained-heart', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'chemical-bolt', 'death-zone', 'drink-me', 'edge-crack', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'ninja-mask', 'stopwatch', 'fishing-net', 'overdrive', 'strong', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'fist', 'padlock', 'three-leaves', 'fluffy-wing', 'pummeled', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'tread', 'arrowed', 'aura', 'back-pain', 'black-flag', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'bleeding-eye', 'bolt-shield', 'broken-heart', 'cobweb', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'broken-shield', 'flying-flag', 'radioactive', 'trophy', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'broken-skull', 'frozen-orb', 'rolling-bomb', 'white-tower', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'grab', 'screaming', 'grenade', 'sentry-gun', 'all-for-one', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'angel-outfit', 'archery-target' &nbsp; &nbsp; &nbsp; &nbsp; ]; &nbsp; &nbsp; const statusColormap = ['#C91010', '#1076c9', '#2fc910', '#c97310', '#9510c9', '#eb75e1', '#e5eb75']; &nbsp; &nbsp; const getStatusIconByIndex = (idx) =&gt; (idx&lt;7) &nbsp; &nbsp; &nbsp; &nbsp; ? `&lt;div style="width: 1em; height: 1em; border-radius:20px; display:inline-block; margin: 0; border:0; cursor: pointer;background-color: ${statusColormap[idx]}"&gt;&lt;/div&gt;` &nbsp; &nbsp; &nbsp; &nbsp; :`&lt;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%;"&gt;&lt;/div&gt;` &nbsp; &nbsp; &nbsp; &nbsp; ; &nbsp; &nbsp; const getStatusIcon = (status) =&gt; getStatusIconByIndex(_.indexOf(statuses,status)); &nbsp; &nbsp; const parseStatuses = (statuses) =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; let s = statuses.split(/,/).map((sb)=&gt;({status:sb.split(/@/)[0],num:sb.split(/@/)[1]||0})); &nbsp; &nbsp; &nbsp; &nbsp; return { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; roll:s.map(st=&gt;`( ${state.TorgStatus.negativeStatuses.includes(st.status) ? '-' :''}${st.num} [${st.status}])`).join('+'), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; mesg:s.map(st=&gt;` &lt;span style="white-space: nowrap;display:inline-block;border: 1px solid #999;background-color:#eff;border-radius:.4em;padding: .1em .4em;"&gt;${state.TorgStatus.negativeStatuses.includes(st.status) ? '-' :'+'}${st.num} ${getStatusIcon(st.status)}&lt;/span&gt; `).join(''), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; total:s.reduce((m,st)=&gt;m+(state.TorgStatus.negativeStatuses.includes(st.status) ? -1 : 1) * st.num, 0) &nbsp; &nbsp; &nbsp; &nbsp; }; &nbsp; &nbsp; }; &nbsp; &nbsp; const checkInstall = () =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; log('-=&gt; TorgStatus v'+version+' &lt;=-&nbsp; ['+(new Date(lastUpdate*1000))+']'); &nbsp; &nbsp; &nbsp; &nbsp; if( ! _.has(state,'TorgStatus') || state.TorgStatus.version !== schemaVersion) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; log('&nbsp; &gt; Updating Schema to v'+schemaVersion+' &lt;'); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; switch(state.TorgStatus && state.TorgStatus.version) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case 0.0: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case 'UpdateSchemaVersion': &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; state.TorgStatus.version = schemaVersion; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; default: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; state.TorgStatus = { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; version: schemaVersion, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; negativeStatuses: [] &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; const outer = (contents) =&gt; `&lt;div style="margin-bottom: .3em;border:1px solid #999;background-color: #ffe;padding: .2em; border-radius:.2em;"&gt;${contents}&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;/div&gt;`; &nbsp; &nbsp; const inner = (contents) =&gt; `&lt;div&gt;${contents}&lt;/div&gt;`; &nbsp; &nbsp; const result = (contents) =&gt; `&lt;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;"&gt;${contents}&lt;/div&gt;`; &nbsp; &nbsp; const icon = (img)=&gt;`&lt;img style="max-height:2.6em;max-width:4em; float:left;" src="${img}"&gt;`; &nbsp; &nbsp; const rollFmt = (contents) =&gt;`[[${contents}]]`.replace(/\[\[\s+/,'[[').replace(/\[\[\s+\[\[/,'[[[['); &nbsp; &nbsp; const attrRoll = (attrs) =&gt; attrs.map(a=&gt;`${parseInt(a.value)||0} [${a.attr}]`).join('+'); &nbsp; &nbsp; const attrMesg = (attrs) =&gt; attrs.map(a=&gt;` &lt;span style="white-space: nowrap;display:inline-block;border: 1px solid #999;background-color:#eff;border-radius:.4em;padding: .1em .4em;"&gt;${a.value} ${a.attr}&lt;/span&gt; `).join(''); &nbsp; &nbsp; const attrTotal = (attrs) =&gt;attrs.reduce((m,a)=&gt;m+parseFloat(a.value)||0,0); &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; on('chat:message',(orig_msg)=&gt;{ &nbsp; &nbsp; &nbsp; &nbsp; if('api' === orig_msg.type) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(apiCmd.test(orig_msg.content) ){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let msg=_.clone(orig_msg); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(_.has(msg,'inlinerolls')){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; msg.content = _.chain(msg.inlinerolls) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .reduce(function(m,v,k){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var ti=_.reduce(v.results.rolls,function(m2,v2){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(_.has(v2,'table')){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; m2.push(_.reduce(v2.results,function(m3,v3){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; m3.push(v3.tableItem.name); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return m3; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; },[]).join(', ')); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return m2; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; },[]).join(', '); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; m['$[['+k+']]']= (ti.length && ti) || v.results.total || 0; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return m; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; },{}) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .reduce(function(m,v,k){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return m.replace(k,v); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; },msg.content) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .value(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let mesg = msg.content.split(/\s+--/); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let attrs = mesg.shift().split(/\s+==/); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let roll = attrs.shift().replace(apiCmd,'').trim()||''; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; attrs=attrs.reduce((m,a)=&gt;_.union(m,a.split(/\s+/)),[]).map(a=&gt;a.toLowerCase()).reduce((m,a)=&gt;{ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let p=a.split(/\|/); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let n=p[0]; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let f=(p[1]==='max'?'max':'current'); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return Object.assign(m,{[n]:(m[n]||[]).concat([f])}); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; },{}); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; mesg = mesg.join(' '); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _.chain(msg.selected) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .map((o)=&gt;getObj('graphic',o._id)) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .reject(_.isUndefined) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .map((o)=&gt;({ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; t: o, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; a: findObjs({type:'attribute',characterid:o.get('represents')}) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .filter((a)=&gt;Object.keys(attrs).includes(a.get('name').toLowerCase())) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .reduce((m,a)=&gt;m.concat(attrs[a.get('name').toLowerCase()].map(f=&gt;({attr:`${a.get('name')}${f==='max'?'|max':''}`,value:a.get(f)}))),[]), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; b: parseStatuses(o.get('statusmarkers')) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; })) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .map((o)=&gt;outer( &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; icon(o.t.get('imgsrc'))+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; inner( &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; result(roll.length &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ? rollFmt(`${roll}+[attributes:](${attrRoll(o.a)})+[status:](${o.b.roll})`)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; : (attrTotal(o.a)+o.b.total))+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (mesg.length ? mesg : '') &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ) + &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; `&lt;div&gt;${attrMesg(o.a)}${o.b.mesg}&lt;/div&gt;` &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; )) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .tap(m=&gt;{try{sendChat('',m.join(''));}catch(e){log(m);$d({roll});}}) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else if(apiCmdSet.test(orig_msg.content) && playerIsGM(orig_msg.playerid) ){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let who=(getObj('player',orig_msg.playerid)||{get:()=&gt;'API'}).get('_displayname'); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _.chain(orig_msg.selected) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .map((o)=&gt;getObj('graphic',o._id)) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .reject(_.isUndefined) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .reduce((m,o)=&gt;_.union(m,o.get('statusmarkers').split(/@\d,|@\d|,/)),[]) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .filter(s=&gt;s.length&&s!=='dead') &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .tap(s=&gt;state.TorgStatus.negativeStatuses=s) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .tap(s=&gt;sendChat('',`/w "${who}" Negative Statuses: ${_.map(s,getStatusIcon).join('')}`)) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }); &nbsp; &nbsp; checkInstall(); });