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

Need help with turn marker crazy idea.

I have this idea that I am trying to automate. I want to use the rounds counter from the turn marker to trigger a rollable table to switch images in order. I know that the round number is linked to bar2 in turn marker and was wondering if anyone knew how to link the bar number changing to trigger a macro. Or atleast a macro that tracked the turn tracker and could activate when new round begins. We are going to run the campaign in turn order even out of battle to track time loosely.
1614347271
The Aaron
Roll20 Production Team
API Scripter
Could you go into a little more detail about what you'd like as an end result?
1614353275

Edited 1614359425
My idea is to trigger a roll able table to change to next image to count time by rounds. In TurnMarker api it flags Bar2 with the round you are on and changes each time a new round starts. I want to have the roll able table move to next image to move a marker showing what time it is. By using the 6 sec per round math you can figure that 10 rounds is a min and from that make a timer. I can then have this on the table for players to see to add pressure. In order to keep track of rounds from the moment they enter the area I tell them to roll imitative. movement and actions all become turn based and they have to make sure they are not wasting time. The module I am going to test this on is Hidden Shrine of Tamochan because it starts with a timer and fits the concept. I know that I can manually do this I am just looking for an automated way to move the roll able table token image. Thank you for taking the time.
1614363778

Edited 1614379891
something like this but round change triggers the change. Macro in roll20 named time. !token-mod {{   --set currentside#+1 represents#1t[clock] width#[[210]] height#[[210]] }} added an id line so the token did not need to be selected. !token-mod {{ --set currentside#+1 represents#1t[Gealbhan] width#[[210]] height#[[210]] --ids @{swaylon|character_id} }} The only hang up is getting it to run the Macro and the change of the round number.
1614365634

Edited 1614367428
This is my rough attempt to trigger the Macro from the API not sure what the coding is to send to chat and execute the macro.     handleMarkerTurn = function(){         var marker = getMarker(),             turnOrder = TurnOrder.Get(),             round;         if(turnOrder[0].id === marker.id) {             round=parseInt(marker.get('bar2_value'))+1;             marker.set({                 name: state.TurnMarker.tokenName+' '+round,                 bar2_value: round             });             announceRound(round);             TurnOrder.Next();             sendChat('#time');         }     },
Any help on this? My other thought is to use Custom roll listener to trigger a macro or a token mod script. But I can not get that to work. I can get CRL to trigger at round change but not run a macro or token mod script.
I have been trying to get CRL to trigger a !token-mod script. I have tested it without the token mod script and it works with just text. the Token Mod works when put directly into chat. But when I combine the two nothing happens. no errors or anything. Please help. I have been at this for hours and can not find help on the forums. CRL Command line \\!token-mod --set currentside#+1 represents#1t[Gealbhan] --ids @{swaylon|character_id} I also tried to get CRL to run a macro but could not find out how to do that anywhere online. Normally I would run the Token-mod script as a macro but I am trying to automate it so that it triggers when the round changes. I am making a clock for Shrine of Tamochan to give pressure from the poison gas and this would help take 1 thing off my plate while running the module.
1614881790

Edited 1614881892
David M.
Pro
API Scripter
Never used CRL, but you could call your token-mod code directly in your sendChat from your script (assuming the rest of it works properly, I didn't really look closely), rather than triggering a macro. sendChat('', `!token-mod --set currentside#+1 represents#1t[Gealbhan] width#[[210]] height#[[210]] --ids @{swaylon|character_id}`) You could include queries as usual to make this more generalized rather than hardcoded.
1614882175
timmaugh
Pro
API Scripter
also, Aaron has an "On My Turn" script out there that you might be able to crib off of. It is already listening to the changes of the turn tracker to let you fire off events. You could probably use that directly or scaffold up something that held an individualized command for each of your players...?
I tried adding it into the code. I am using Turn Marker api to mark when to run the code. It shows up in chat but does not run it. Is there something I need to add to have it run in chat and not just post? announceRound = function(round){ if(state.TurnMarker.announceRounds) { sendChat( "", "/direct "+ "<div style='"+ 'background-color: #4B0082;'+ 'border: 3px solid #808080;'+ 'font-size: 20px;'+ 'text-align:center;'+ 'vertical-align: top;'+ 'color: white;'+ 'font-weight:bold;'+ 'padding: 5px 5px;'+ "'>"+ "<img src='"+state.TurnMarker.tokenURL+"' style='width:20px; height:20px; padding: 0px 5px;' />"+ "Round " + round + "<img src='"+state.TurnMarker.tokenURL+"' style='width:20px; height:20px; padding: 0px 5px;' />"+ `!token-mod --set currentside#+1 represents#1t[Gealbhan] --ids @{swaylon|character_id}` + "</div>"+ '<a style="position:relative;z-index:10000; top:-1em; float: right;font-size: .6em; color: white; border: 1px solid #cccccc; border-radius: 1em; margin: 0 .1em; font-weight: bold; padding: .1em .4em;" href="!tm reset ?{Round number|0}">Reset &'+'#x21ba;</a>' ); Thank you David M. said: Never used CRL, but you could call your token-mod code directly in your sendChat from your script (assuming the rest of it works properly, I didn't really look closely), rather than triggering a macro. sendChat('', `!token-mod --set currentside#+1 represents#1t[Gealbhan] width#[[210]] height#[[210]] --ids @{swaylon|character_id}`) You could include queries as usual to make this more generalized rather than hardcoded.
1614885627
timmaugh
Pro
API Scripter
It has to be its own call to sendChat, to do what David is suggesting. The R20 parser only detects API-intended messages based on the first character being the '!' character. So you would need 2 sendChat() calls... announceRound = function(round){         if(state.TurnMarker.announceRounds) {             sendChat('', `!token-mod --set currentside#+1 represents#1t[Gealbhan] width#[[210]] height#[[210]] --ids @{swaylon|character_id}`);             sendChat(                 "",                 "/direct "+                 "<div style='"+                     'background-color: #4B0082;'+                     'border: 3px solid #808080;'+                     // ... Getting back to your first post, a similar constriction is at work... namely that while the #macroName formation will expand the macro language into the chat if you enter that from the chat, that does not work from within the javascript/API environment. If you wanted to save the code to a macro and then retrieve it to send it to chat, you would need to locate the macro, then get the 'action' property... let tacoTheMacro = (findObjs({ type: 'macro', name: 'Tacro'})[0] || { get: () => { return ''; } }).get('action'); That will put the action text of the macro in tacoTheMacro, which you can then send out to chat.
Still no dice. It is not showing up in chat now. But the token is not changing I copied the code into the chat to make sure and the image changed. !token-mod --set currentside#+1 represents#1t[Gealbhan] --ids @{swaylon|character_id} I do appreciate the help. If I just type into the sendChat it shows up when the turn order changes so I know it is sending. Roll20 is not running the code when it is sent for some reason. I will keep digging and try to find a way to make this run. I really want to get this clock to function automatically but if I have to remember to hit the macro button at the start of each round I will.
1614888793
David M.
Pro
API Scripter
timmaugh, could it be a timing issue? Is the api unable to handle the two chat messages firing so quickly? Just to clarify: This works when typed directly into chat? !token-mod --set currentside#+1 represents#1t[Gealbhan] --ids @{swaylon|character_id} ...but this doesn't work from your script? sendChat('', `!token-mod --set currentside#+1 represents#1t[Gealbhan] --ids @{swaylon|character_id}`) Assuming that's the case, what if the token-mod sendChat was the only one sent? Does that part work then? Comment out your html sendChat lines to test. Actually, you could test both cases with just a single sendChat (commenting out the other) to see if either one works with the script as-is. Just to rule out any other inadvertent code changes that might be causing problems.
1614889619
timmaugh
Pro
API Scripter
It could be, but I wouldn't expect it to happen every time. This bug report documents the issue Aaron and I found the other day, where calls were being dropped: <a href="https://app.roll20.net/forum/permalink/9837419/" rel="nofollow">https://app.roll20.net/forum/permalink/9837419/</a> But to have it consistently happen with only 2 calls.... That would be an even worse problem. I have a more robust test script I wrote to track this dropped-message behavior (more robust than the simple repro script in that thread). It will sit between scripts like this and roll20, tracking what goes up to roll20 and what comes back. If you see something get sent that never comes back down... you have an issue. When I get back to my PC, I'll post the script with some instructions.
You are correct with it works in chat but not from the API. I tried running just the token mod from the sendChat without the second sendChat and it still did not run. I am starting to think that maybe the trigger processes to fast to send the Token-mod command. I also tryed the macro addition but I have never understood how to put that into the API and make it trigger.
1614890360

Edited 1614890405
David M.
Pro
API Scripter
And running only the original sendChat (with the html/css) works with the current state of the script?&nbsp;
Yes the original send chat works. It sends the round and round number like it should.
The only repeatable fact is the the !token-mod code line does not run when sent from the token marker API. If I add " " to it it prints the code into the chat perfectly.
This is the section of token marker I feel I could get the code to work. But there is a second part that would match the timing. announceRound = function(round){ if(state.TurnMarker.announceRounds) { sendChat('', `!token-mod --set currentside#+1 represents#1t[Gealbhan] --ids @{swaylon|character_id}`); sendChat( "", "/direct "+ "&lt;div style='"+ 'background-color: #4B0082;'+ 'border: 3px solid #808080;'+ 'font-size: 20px;'+ 'text-align:center;'+ 'vertical-align: top;'+ 'color: white;'+ 'font-weight:bold;'+ 'padding: 5px 5px;'+ "'&gt;"+ "&lt;img src='"+state.TurnMarker.tokenURL+"' style='width:20px; height:20px; padding: 0px 5px;' /&gt;"+ "Round " + round + "&lt;img src='"+state.TurnMarker.tokenURL+"' style='width:20px; height:20px; padding: 0px 5px;' /&gt;"+ "&lt;/div&gt;"+ '&lt;a style="position:relative;z-index:10000; top:-1em; float: right;font-size: .6em; color: white; border: 1px solid #cccccc; border-radius: 1em; margin: 0 .1em; font-weight: bold; padding: .1em .4em;" href="!tm reset ?{Round number|0}"&gt;Reset &amp;'+'#x21ba;&lt;/a&gt;' ); } }, The Second area is where the round number is changed. handleMarkerTurn = function(){ var marker = getMarker(), turnOrder = TurnOrder.Get(), round; if(turnOrder[0].id === marker.id) { round=parseInt(marker.get('bar2_value'))+1; marker.set({ name: state.TurnMarker.tokenName+' '+round, bar2_value: round }); announceRound(round); TurnOrder.Next(); } }, I am not skilled enough yet to write my own API to listen for the round change and just run the !token-mod. But if there is any help in that path I could dig in and learn it to write an API just to fire a macro or code line at round change.
1614892194
timmaugh
Pro
API Scripter
Ok, here is the Interlocutor script to test the receipt of messages: /* ========================================================= Name : Interlocutor GitHub : Roll20 Contact &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;: timmaugh Version : 0.0.1 Last Update : 2/21/2021 ========================================================= */ var API_Meta = API_Meta || {}; API_Meta.Interlocutor = { offset: Number.MAX_SAFE_INTEGER, lineCount: -1 }; { &nbsp; &nbsp; try { throw new Error(''); } catch (e) { API_Meta.Interlocutor.offset = (parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/, '$1'), 10) - (13)); } } const Interlocutor = (() =&gt; { &nbsp; &nbsp; const apiproject = 'Interlocutor'; &nbsp; &nbsp; const version = '0.0.1'; &nbsp; &nbsp; const schemaVersion = 0.1; &nbsp; &nbsp; API_Meta[apiproject].version = version; &nbsp; &nbsp; const vd = new Date(1613942668454); &nbsp; &nbsp; const versionInfo = () =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; log(`\u0166\u0166 ${apiproject} v${API_Meta[apiproject].version}, ${vd.getFullYear()}/${vd.getMonth() + 1}/${vd.getDate()} \u0166\u0166 -- offset ${API_Meta[apiproject].offset}`); &nbsp; &nbsp; &nbsp; &nbsp; if (!state.hasOwnProperty(apiproject) || state[apiproject].version !== schemaVersion) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; log(`&nbsp; &gt; Updating ${apiproject} Schema to v${schemaVersion} &lt;`); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; switch (state[apiproject] &amp;&amp; state[apiproject].version) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case 0.1: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; state[apiproject].active = true; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; /* break; // intentional dropthrough */ /* falls through */ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case 'UpdateSchemaVersion': &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; state[apiproject].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[apiproject] = { &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; active: true &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; const logsig = () =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; // initialize shared namespace for all signed projects, if needed &nbsp; &nbsp; &nbsp; &nbsp; state.torii = state.torii || {}; &nbsp; &nbsp; &nbsp; &nbsp; // initialize siglogged check, if needed &nbsp; &nbsp; &nbsp; &nbsp; state.torii.siglogged = state.torii.siglogged || false; &nbsp; &nbsp; &nbsp; &nbsp; state.torii.sigtime = state.torii.sigtime || Date.now() - 3001; &nbsp; &nbsp; &nbsp; &nbsp; if (!state.torii.siglogged || Date.now() - state.torii.sigtime &gt; 3000) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; const logsig = '\n' + &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; '&nbsp; &nbsp;‗‗‗‗‗‗‗‗‗‗‗‗‗‗‗‗‗‗‗‗‗‗‗‗‗‗‗‗‗‗‗‗‗‗‗‗‗‗‗‗‗‗‗&nbsp; &nbsp; ' + '\n' + &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; '&nbsp; &nbsp; ∖_______________________________________∕&nbsp; &nbsp; &nbsp;' + '\n' + &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; '&nbsp; &nbsp; &nbsp; ∖___________________________________∕&nbsp; &nbsp; &nbsp; &nbsp;' + '\n' + &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; '&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;___┃ ┃_______________┃ ┃___&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ' + '\n' + &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; '&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ┃___&nbsp; &nbsp;_______________&nbsp; &nbsp;___┃&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;' + '\n' + &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;' + '\n' + &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;' + '\n' + &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;' + '\n' + &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;' + '\n' + &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;' + '\n' + &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; '______________┃ ┃_______________┃ ┃_______________' + '\n' + &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; '&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;⎞⎞⎛⎛&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ⎞⎞⎛⎛&nbsp; &nbsp; &nbsp; ' + '\n'; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; log(`${logsig}`); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; state.torii.siglogged = true; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; state.torii.sigtime = Date.now(); &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; }; &nbsp; &nbsp; // ================================================== &nbsp; &nbsp; // MESSAGING / CHAT REPORTING &nbsp; &nbsp; // ================================================== &nbsp; &nbsp; const HE = (() =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; const esRE = (s) =&gt; s.replace(/(\\|\/|\[|\]|\(|\)|\{|\}|\?|\+|\*|\||\.|\^|\$)/g, '\\$1'); &nbsp; &nbsp; &nbsp; &nbsp; const e = (s) =&gt; `&amp;${s};`; &nbsp; &nbsp; &nbsp; &nbsp; const entities = { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; '&lt;': e('lt'), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; '&gt;': e('gt'), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "'": e('#39'), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; '@': e('#64'), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; '{': e('#123'), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; '|': e('#124'), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; '}': e('#125'), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; '[': e('#91'), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ']': e('#93'), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; '"': e('quot'), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; '*': e('#42') &nbsp; &nbsp; &nbsp; &nbsp; }; &nbsp; &nbsp; &nbsp; &nbsp; const re = new RegExp(`(${Object.keys(entities).map(esRE).join('|')})`, 'g'); &nbsp; &nbsp; &nbsp; &nbsp; return (s) =&gt; s.replace(re, (c) =&gt; (entities[c] || c)); &nbsp; &nbsp; })(); &nbsp; &nbsp; const rowbg = ["#ffffff", "#dedede"]; &nbsp; &nbsp; const headerbg = { &nbsp; &nbsp; &nbsp; &nbsp; normal: rowbg[1], &nbsp; &nbsp; &nbsp; &nbsp; critical: "##F46065" &nbsp; &nbsp; }; &nbsp; &nbsp; const msgtable = '&lt;div style="width:100%;"&gt;&lt;div style="border-radius:10px;border:2px solid #000000;background-color:__bg__; margin-right:16px; overflow:hidden;"&gt;&lt;table style="width:100%; margin: 0 auto; border-collapse:collapse;font-size:12px;"&gt;__TABLE-ROWS__&lt;/table&gt;&lt;/div&gt;&lt;/div&gt;'; &nbsp; &nbsp; const msg1header = '&lt;tr style="border-bottom:1px solid #000000;font-weight:bold;text-align:center; background-color:__bg__; line-height: 22px;"&gt;&lt;td colspan = "__colspan__"&gt;__cell1__&lt;/td&gt;&lt;/tr&gt;'; &nbsp; &nbsp; const msg2header = '&lt;tr style="border-bottom:1px solid #000000;font-weight:bold;text-align:center; background-color:__bg__; line-height: 22px;"&gt;&lt;td&gt;__cell1__&lt;/td&gt;&lt;td style="border-left:1px solid #000000;"&gt;__cell2__&lt;/td&gt;&lt;/tr&gt;'; &nbsp; &nbsp; const msg3header = '&lt;tr style="border-bottom:1px solid #000000;font-weight:bold;text-align:center; background-color:__bg__; line-height: 22px;"&gt;&lt;td&gt;__cell1__&lt;/td&gt;&lt;td style="border-left:1px solid #000000;"&gt;__cell2__&lt;/td&gt;&lt;td style="border-left:1px solid #000000;"&gt;__cell3__&lt;/td&gt;&lt;/tr&gt;'; &nbsp; &nbsp; const msg1row = '&lt;tr style="background-color:__bg__;"&gt;&lt;td style="padding:4px;"&gt;&lt;div style="__row-css__"&gt;__cell1__&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;'; &nbsp; &nbsp; const msg2row = '&lt;tr style="background-color:__bg__;font-weight:bold;"&gt;&lt;td style="padding:1px 4px;"&gt;__cell1__&lt;/td&gt;&lt;td style="border-left:1px solid #000000;text-align:center;padding:1px 4px;font-weight:normal;"&gt;__cell2__&lt;/td&gt;&lt;/tr&gt;'; &nbsp; &nbsp; const msg3row = '&lt;tr style="background-color:__bg__;font-weight:bold;"&gt;&lt;td style="padding:1px 4px;"&gt;__cell1__&lt;/td&gt;&lt;td style="border-left:1px solid #000000;text-align:center;padding:1px 4px;font-weight:normal;"&gt;__cell2__&lt;/td&gt;&lt;td style="border-left:1px solid #000000;text-align:center;padding:1px 4px;font-weight:normal;"&gt;__cell3__&lt;/td&gt;&lt;/tr&gt;'; &nbsp; &nbsp; const msgbox = ({ c: c = "chat message", t: t = "title", btn: b = "buttons", send: send = false, sendas: sas = "API", wto: wto = "", type: type = "normal" }) =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; let tbl = msgtable.replace("__bg__", rowbg[0]); &nbsp; &nbsp; &nbsp; &nbsp; let hdr = msg1header.replace("__bg__", headerbg[type]).replace("__cell1__", t); &nbsp; &nbsp; &nbsp; &nbsp; let row = msg1row.replace("__bg__", rowbg[0]).replace("__cell1__", c); &nbsp; &nbsp; &nbsp; &nbsp; let btn = b !== "buttons" ? msg1row.replace("__bg__", rowbg[0]).replace("__cell1__", b).replace("__row-css__", "text-align:right;margin:4px 4px 8px;") : ""; &nbsp; &nbsp; &nbsp; &nbsp; let msg = tbl.replace("__TABLE-ROWS__", hdr + row + btn); &nbsp; &nbsp; &nbsp; &nbsp; if (wto) msg = `/w "${wto}" ${msg}`; &nbsp; &nbsp; &nbsp; &nbsp; if (["t", "true", "y", "yes", true].includes(send)) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChatOrig(sas, msg); &nbsp; &nbsp; &nbsp; &nbsp; } else { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return msg; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }; &nbsp; &nbsp; const replacer = (key, value) =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; // Filtering out properties &nbsp; &nbsp; &nbsp; &nbsp; if (key === 'signature') { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return undefined; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; return value; &nbsp; &nbsp; }; &nbsp; &nbsp; const syntaxHighlight = (str, replacer = undefined) =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; const css = { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; stringstyle: 'mediumblue;', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; numberstyle: 'magenta;', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; booleanstyle: 'darkorange;', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; nullstyle: 'darkred;', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; keystyle: 'darkgreen;' &nbsp; &nbsp; &nbsp; &nbsp; }; &nbsp; &nbsp; &nbsp; &nbsp; if (typeof str !== 'string') { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; str = JSON.stringify(str, replacer, '&nbsp; &nbsp;'); &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; str = str.replace(/&amp;/g, '&amp;amp;').replace(/&lt;/g, '&amp;lt;').replace(/&gt;/g, '&amp;gt;'); &nbsp; &nbsp; &nbsp; &nbsp; return str.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?)/g, function (match) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let cls = 'numberstyle'; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (/^"/.test(match)) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (/:$/.test(match)) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; cls = 'keystyle'; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; cls = 'stringstyle'; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else if (/true|false/.test(match)) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; cls = 'booleanstyle'; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else if (/null/.test(match)) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; cls = 'nullstyle'; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return '&lt;span style=" color: ' + css[cls] + '"&gt;' + HE(match.replace(/^"(.*)"(:?)$/g, ((m, g1, g2) =&gt; `${g1}${g2}`)).replace(/\\(.)/g, `$1`)) + '&lt;/span&gt;'; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; }; &nbsp; &nbsp; const showObjInfo = (o, t = 'PARSED OBJECT', replacer = undefined) =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; msgbox({ t: t, c: `&lt;div&gt;&lt;pre style="background: transparent; border: none;white-space: pre-wrap;font-family: Inconsolata, Consolas, monospace;"&gt;${syntaxHighlight(o || '', replacer).replace(/\n/g, '&lt;br&gt;')}&lt;/pre&gt;&lt;/div&gt;`, send: true }); &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; }; &nbsp; &nbsp; let preservedMsgObj = {}; &nbsp; &nbsp; const generateUUID = (() =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; let a = 0; &nbsp; &nbsp; &nbsp; &nbsp; let b = []; &nbsp; &nbsp; &nbsp; &nbsp; return () =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let c = (new Date()).getTime() + 0; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let f = 7; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let e = new Array(8); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let d = c === a; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; a = c; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; for (; 0 &lt;= f; f--) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; e[f] = "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".charAt(c % 64); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; c = Math.floor(c / 64); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; c = e.join(""); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (d) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; for (f = 11; 0 &lt;= f &amp;&amp; 63 === b[f]; f--) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; b[f] = 0; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; b[f]++; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; for (f = 0; 12 &gt; f; f++) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; b[f] = Math.floor(64 * Math.random()); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; for (f = 0; 12 &gt; f; f++) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; c += "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".charAt(b[f]); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return c; &nbsp; &nbsp; &nbsp; &nbsp; }; &nbsp; &nbsp; })(); &nbsp; &nbsp; const conditionalPluck = (array, key, cobj = {}) =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; // test array of objects to return a given property of each object if all conditions are met &nbsp; &nbsp; &nbsp; &nbsp; // cobj properties are functions testing that property (k) in the evaluated object (o) &nbsp; &nbsp; &nbsp; &nbsp; // to test if testedproperty equals a given value: { testedProperty: (k,o) =&gt; { return o[k] === 'given value'; } } &nbsp; &nbsp; &nbsp; &nbsp; // to test if testedproperty exists:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;{ testedProperty: (k,o) =&gt; { return o.hasOwnProperty(k); } } &nbsp; &nbsp; &nbsp; &nbsp; return array.map(o =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let b = true; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (cobj) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Object.keys(cobj).forEach(k =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (b &amp;&amp; !cobj[k](k, o)) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; b = false; &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; if (b) return o[key]; &nbsp; &nbsp; &nbsp; &nbsp; }).filter(e =&gt; e); &nbsp; &nbsp; }; &nbsp; &nbsp; const controlBoard = msg =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; // turn on/off state[apiproject].active &nbsp; &nbsp; &nbsp; &nbsp; // also, to report out the msg array &nbsp; &nbsp; &nbsp; &nbsp; // also, to clear the msg array &nbsp; &nbsp; &nbsp; &nbsp; let c = msg.content.split(/\s+/).slice(1).join(' '); &nbsp; &nbsp; &nbsp; &nbsp; switch (c.toLowerCase()) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case 'on': &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; state[apiproject].active = true; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; assignChat(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case 'off': &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; state[apiproject].active = false; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; assignChat(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case 'clear': &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case 'reset': &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; preservedMsgObj = {}; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; msgbox({ c: `All tracked messages deleted.`, t: `INTERLOCUTOR`, send: true }); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; default: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; if (/^(report|show)/.test(c)) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let show = /\s+/.test(c) ? c.split(/\s+/).slice(1).join(' ') : 'base'; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let repObj; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; switch (show.toLowerCase()) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case 'content': &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; repObj = Object.keys(preservedMsgObj).map(m =&gt; { return { MESSAGE: `${m}`, DISPATCH: preservedMsgObj[m].dispatch.content, RECEIVED: !preservedMsgObj[m].dispatch.content.startsWith('!') ? 'None required' : preservedMsgObj[m].received.content || '' }; }); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case 'base': &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; default: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; repObj = Object.keys(preservedMsgObj).map(m =&gt; `${m}: ${!preservedMsgObj[m].dispatch.content.startsWith('!') ? 'None required' : preservedMsgObj[m].received.hasOwnProperty('content') ? 'RECEIVED' : ''}`); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; showObjInfo(repObj, 'INTERLOCUTOR REPORT'); &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }; &nbsp; &nbsp; const sendChatOrig = sendChat; &nbsp; &nbsp; const sendChatI = (from, cmd, ...args) =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; let apitrigger = `${apiproject}${generateUUID()}`; // apitrigger used to track this message &nbsp; &nbsp; &nbsp; &nbsp; preservedMsgObj[apitrigger] = { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; dispatch: { from: from, content: cmd }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; received: {} &nbsp; &nbsp; &nbsp; &nbsp; }; &nbsp; &nbsp; &nbsp; &nbsp; sendChatOrig(from, /^!/.test(cmd) ? `!${apitrigger}${cmd}` : cmd, ...args); &nbsp; &nbsp; }; &nbsp; &nbsp; const assignChat = () =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; sendChat = state.Interlocutor.active ? Interlocutor.sendChatI : Interlocutor.sendChatOrig; &nbsp; &nbsp; &nbsp; &nbsp; showObjInfo(state[apiproject], `INTERLOCUTOR STATUS`); &nbsp; &nbsp; }; &nbsp; &nbsp; on('chat:message', (msg) =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; if (msg.type !== 'api') return; &nbsp; &nbsp; &nbsp; &nbsp; if (/^!interlocutor\s+.+/.test(msg.content)) { // interlocutor config statement &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; controlBoard(msg); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; if (state[apiproject].active !== true) return; &nbsp; &nbsp; &nbsp; &nbsp; const trigrx = new RegExp(`^!(${Object.keys(preservedMsgObj).join('|')})`); &nbsp; &nbsp; &nbsp; &nbsp; let apitrigger; // the apitrigger used by the message &nbsp; &nbsp; &nbsp; &nbsp; if (Object.keys(preservedMsgObj).length &amp;&amp; trigrx.test(msg.content)) { // message has apitrigger &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; apitrigger = trigrx.exec(msg.content)[1]; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; msg.content = `${msg.content.replace(trigrx, '').replace(/^\s*/, '')}`; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; preservedMsgObj[apitrigger].received = _.clone(msg); &nbsp; &nbsp; &nbsp; &nbsp; } else { // interlocutor is ON, this is not an interlocutor config message, and message has no apitrigger... this is probably a user-generated message &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; apitrigger = `UserGenerated${generateUUID()}`; // apitrigger used to track this message &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; preservedMsgObj[apitrigger] = { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; dispatch: { from: msg.who, content: msg.content }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; received: _.clone(msg) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }); &nbsp; &nbsp; on('ready', () =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; versionInfo(); &nbsp; &nbsp; &nbsp; &nbsp; logsig(); &nbsp; &nbsp; &nbsp; &nbsp; assignChat(); &nbsp; &nbsp; }) &nbsp; &nbsp; return { &nbsp; &nbsp; &nbsp; &nbsp; sendChatI: sendChatI, &nbsp; &nbsp; &nbsp; &nbsp; sendChatOrig: sendChatOrig &nbsp; &nbsp; }; })(); //sendChat = state.Interlocutor.active || false ? Interlocutor.sendChatI : Interlocutor.sendChatOrig; { try { throw new Error(''); } catch (e) { API_Meta.Interlocutor.lineCount = (parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/, '$1'), 10) - API_Meta.Interlocutor.offset); } } When you enable that and reboot your sandbox, you'll get a report as to whether the Interlocutor is active (on). It should install as active. To turn it on/off, use the handle: !interlocutor on !interlocutor off When it is on, it will be collecting upstream and downstream messages, and it will keep collecting them until you turn it off or until you clear it. To clear it, use either 'clear' or 'reset' with the handle: !interlocutor clear Then, to see a report of what messages went up and down, use either 'report' or 'show'. !interlocutor show There are 2 reports available. The base/default report can be accessed as above (with nothing) or by explicitly adding 'base': !interlocutor show base Otherwise, there is a report that shows the message content of each message. Trigger that with 'content': !interlocutor show content Understanding the Report The base report will show you messages sent that were/were not received back from Roll20. For the above, you can see that the original message shows as user-generated (anything from the chat, macro, or ability), and that it was received. The next 3 lines were api-generated sendChats. The first two were received. The last was not intended to be an API call (it just output text to the chat interface), so we wouldn't expect an API message to be received again... that's why that one says "None Required." Immediately after that, I ran the&nbsp; !interlocutor show content &nbsp;to see the content report for the same batch of messages. This is that output: That is helpful if you have a long series of messages and you wonder which were properly sent vs those that were dropped. Remember to clear the message between tracking runs, and to turn it off when&nbsp; you're done with it (or even disable the script). If it is left "on" and you don't realize you're collecting messages, you've basically opted in for a memory leak. :-o
Interlocutor &nbsp;showed this... That is after I got it to change to new round. So it is sending the code. RECEIVED: None required }, { MESSAGE: Interlocutor-MUz6Vb7EE_9nSqZxWPR , DISPATCH: !token-mod --set currentside#+1 represents#1t[Gealbhan] --ids @{swaylon|character_id} , RECEIVED: !token-mod --set currentside#+1 represents#1t[Gealbhan] --ids -MEp2-dC-1sCJYFPcTkK }, { MESSAGE: Interlocutor-MUz6VbEePxVl20SAYSY ,
1614897956
timmaugh
Pro
API Scripter
ok, I've got one more suggestion before I'm out of ideas... The difference between user-generated and api-generated messages is that api-generated doesn't have a selected property, and the who and playerid properties are altered to point to the api. I know token-mod gets around there being no selected property by letting you specify the ids to affect in the --id argument... but maybe there is something else going on that it doesn't like... something that is different between sending the command via chat vs the api. To test that, you can do two things. Install this little scriptlet: on('chat:message',(msg) =&gt; { &nbsp; &nbsp; if (!(msg.type === 'api' &amp;&amp; /^!ttm\s[^\s]/.test(msg.content))) return; &nbsp; &nbsp; sendChat('', `!${msg.content.slice(msg.content.indexOf(' ') + 1)}`); }); I think that should do it. Then send the following to chat: !ttm&nbsp;token-mod --set currentside#+1 represents#1t[Gealbhan] --ids @{swaylon|character_id} That will just resend the token-mod command as an api-generated command. If that does NOT work, the next step is to install SelectManager and run&nbsp; !forselected&nbsp;token-mod --set currentside#+1 represents#1t[Gealbhan] --ids @{swaylon|character_id} To see if that will work. forselected will force-hand-off the original selected, player, and who from the user-generated message. If this works, then we know where the problem lies.
1614899066

Edited 1614901583
Oh my god... I thank everyone of you!!! So I was digging through a ton of forums and found an old one with The Arron and he mentioned turning (players can Ids) to true. because token mod is limited to the GM and API is not a GM.&nbsp; so the steps are: !token-mod --help set (Players Can Ids) to on. on line 341 of TurnMarker1 API. " announceRound = function(round){ " before sendChat line add the code below. sendChat('', `!token-mod --api-as --ignore-selected --ids @{ clocktokenname |character_id} --set currentside#+1 represents#1t[ rollabletablename ] `); make a rollable table that you want to change at each full round. (A clock in my plan) example is just a multi sided token for my ISO game. and add it to your map as a token. I made a character sheet so I could drag the rollable token onto the map quicker. Every time the round changes the token will advance one image in order. If you set a clock or progress bar it will show time change. in D&amp;D 1 round is 6 seconds. 10 Rounds is 1 Min, 600 rounds is one hour.&nbsp; I can not express how happy I am that this part is working. You all are Rock Stars. Thank you,
1614905918
David M.
Pro
API Scripter
Awesome, glad you go it working! I always forget to ask about the token-mod use-ids flag :)&nbsp;&nbsp;
1614906334
timmaugh
Pro
API Scripter
Adam S. said: You all are Rock Stars. David is a rock star. I'm more of a roadie. No, really. Do you know how I know David is a rock star? Because every time I look at his avatar picture, the man is trying to hand me a Guinness. Legend.
I will try to post a short video of it working when I finish my idea.
1614911911
David M.
Pro
API Scripter
timmaugh said: No, really. Do you know how I know David is a rock star? Because every time I look at his avatar picture, the man is trying to hand me a Guinness. Legend. LOL! That pic was taken at a microbrewery within walking distance of where my son rehearsed with a youth jazz band a few years ago. Very conveniently located ;) Adam S. said: I will try to post a short video of it working when I finish my idea. Cool, will be interested to see it working