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

Error adding Initiative to the tracker from an API

Hi, I'm trying to make an API to let me add initiatives to the turn tracker without having to select the token. I've based my code on some old snippets from other users. I call the function using the character_id found with @{selected|character_id} and everything works great. All the values are being fetched from the character sheet correctly and the result is shown in chat correctly with all the correct calculations. But as soon as I add "&{tracker}" I get an error message : " ReferenceError: sendResultToTracker is not defined " Here's the problematic line sendChat('','@{' + charName + '|whispertype} &{template:pc} {{name=^{initiative}}} {{type=initiative}} {{showchar=@{' + charName + '|rollshowchar}}} {{charname=@{' + charName + '|character_name}}} {{roll=[[1d20 +@{' + charName + '|initiative}[MOD] +(@{' + charName + '|initiative_condition})[CONDITION] +@{' + charName + '|rollmod_initiative}[QUERY]  &{tracker} ]]}} {{shownotes=@{' + charName + '|rollnotes_initiative}}} {{notes=@{' + charName + '|initiative_notes}}} {{conditionsflag=[[@{' + charName + '|initiative_condition}]]}} {{conditions=@{' + charName + '|conditions_display}}}'); I'm most probably just missing a detail. Any help would be appreciated. John
1660013152

Edited 1660013183
The Aaron
Roll20 Production Team
API Scripter
&{tracker} only works for players with a selected token. However, Mod scripts are not players and have no selection (or user interface, which is why they can't "select" things).  You can't add tokens to the turn order that way.  For Mod Scripts, you need to get the current turn order JSON string from the Campaign object, parse it, inject a new entry at the right point, then serialize it and set it back on the Campaign object.  I can post some sample code in a bit. (Also, is this your test account posting?)
Lol, yes I guess it is, dunno why I ended up using that one, wrong navigator. That would explain a lot. Tried multiples ways but it always ended up breaking at the &{tracker}. I don't need anything fancy, being able to add the new initiative in the turn order would be more than enough. BTW, thanks for all your contributions, I used a lot of them, learned from them and they definitely eased my learning curve. Still not a fan of asynchronous functions, probably cause I'm still a bit at lost with them but, I'll learn... Thanks a lot for your help
1660053238

Edited 1660053760
The Aaron
Roll20 Production Team
API Scripter
Here's a selection of helper functions I've written for managing things in the TurnOrder: const getTurnArray = () => ( '' === Campaign().get('turnorder') ? [] : JSON.parse(Campaign().get('turnorder'))); const getTurnArrayFromPrev = (prev) => ( '' === prev.turnorder ? [] : JSON.parse(prev.turnorder)); const setTurnArray = (ta) => Campaign().set({turnorder: JSON.stringify(ta)}); const addTokenTurn = (id, pr, pageid) => setTurnArray([...getTurnArray(), {id,pr,_pageid:(pageid||(getObj('graphic',id)||{get:''}).get('pageid'))}]); const addCustomTurn = (custom, pr) => setTurnArray([...getTurnArray(), {id:"-1",custom,pr}]); const removeTokenTurn = (tid) => setTurnArray(getTurnArray().filter( (to) => to.id !== tid)); const removeCustomTurn = (custom) => setTurnArray(getTurnArray().filter( (to) => to.custom !== custom)); const clearTurnOrder = () => Campaign().set({turnorder:'[]'}); const packTo = (to) => [{id:'HEADER',pr:Number.MAX_SAFE_INTEGER},...to].reduce((m,t)=>{ if('-1'===t.id){ m[m.length-1].packed=[...(m[m.length-1].packed || []), t]; return m; } return [...m,t]; },[]); const unpackTo = (pTo) => pTo.reduce((m,t)=>{ let packed = t.packed||[]; delete t.packed; if('HEADER' === t.id){ return [...packed,...m]; } return [...m,t,...packed]; },[]); const sorter_asc = (a, b) => ('-1' === a.id || '-1' === b.id) ? 0 : a.pr - b.pr; const sorter_desc = (a, b) => ('-1' === a.id || '-1' === b.id) ? 0 : b.pr - a.pr; const sortTurnOrder = (sortBy = sorter_desc) => Campaign().set({turnorder: JSON.stringify(unpackTo(packTo(getTurnArray()).sort(sortBy)))}); So, using those, you could do: addTokenTurn(someID,someNum); sortTurnOrder(); You'd still need to grab the number to set somehow, possibly by reading chat messages looking for your roll template output and extracting it from the roll results. Something in the sequence of: Send Roll Template to chat Catch chat messages Extract roll from correct message Set Turn Order entry. Hope that helps!  Let me know if there's more I can do. =D
1660077132

Edited 1660077407
I feel like I'm getting somewhere but still a few hurdles in the way. First, I made a simple function to test out your helper functions and I seem to be doing something wrong because I get an error : " TypeError: (getObj(...) || {(intermediate value)}).get is not a function " I made it as barebone as possible just to test it before adding any complexity/possible source of errors. on('chat:message',function(msg){   // I'm gonna add the necessary structure to check if it's the right msg/template here later     let rollResult = msg.inlinerolls[1].results.total; // The result of the Initiative roll     log ("rollResult: " + rollResult);     const getTurnArray = () => ( '' === Campaign().get('turnorder') ? [] : JSON.parse(Campaign().get('turnorder')));     const getTurnArrayFromPrev = (prev) => ( '' === prev.turnorder ? [] : JSON.parse(prev.turnorder));     const setTurnArray = (ta) => Campaign().set({turnorder: JSON.stringify(ta)});     const addTokenTurn = (id, pr, pageid) => setTurnArray([...getTurnArray(), {id,pr,_pageid:(pageid||(getObj('graphic',id)||{get:''}).get('pageid'))}]);     const addCustomTurn = (custom, pr) => setTurnArray([...getTurnArray(), {id:"-1",custom,pr}]);     const removeTokenTurn = (tid) => setTurnArray(getTurnArray().filter( (to) => to.id !== tid));     const removeCustomTurn = (custom) => setTurnArray(getTurnArray().filter( (to) => to.custom !== custom));     const clearTurnOrder = () => Campaign().set({turnorder:'[]'});          const packTo = (to) => [{id:'HEADER',pr:Number.MAX_SAFE_INTEGER},...to].reduce((m,t)=>{         if('-1'===t.id){             m[m.length-1].packed=[...(m[m.length-1].packed || []), t];             return m;         }         return [...m,t];     },[]);          const unpackTo = (pTo) => pTo.reduce((m,t)=>{         let packed = t.packed||[];         delete t.packed;         if('HEADER' === t.id){             return [...packed,...m];         }         return [...m,t,...packed];     },[]);          const sorter_asc = (a, b) => ('-1' === a.id || '-1' === b.id) ? 0 : a.pr - b.pr;     const sorter_desc = (a, b) => ('-1' === a.id || '-1' === b.id) ? 0 : b.pr - a.pr;     const sortTurnOrder = (sortBy = sorter_desc) => Campaign().set({turnorder: JSON.stringify(unpackTo(packTo(getTurnArray()).sort(sortBy)))});         addTokenTurn('-N5mpETC8e9Buvn8ih0Q','2');  // tokenID, position - Hard-coded just to test it out     sortTurnOrder(); }); Second, when I use addTokenTurn do I need to use a specific position or if I add it at any position it will do so without overwriting any other entries in the turn order? And Lastly, to add the token to the turn order I need its tokenID but, from the chat message I only get the playerID. Should I simply add the tokenID in the Initiative chat message (possibly adding an attribute to the character sheet) and catch it from there or is there a better way? Thanks again, been working on that for way to long but, it will do wonders for my players.
1660081049

Edited 1660081188
The Aaron
Roll20 Production Team
API Scripter
Probably that token doesn't exist.  When it tries to find it so it can get the page id, it fails.  I'll have to see about duplicating that and add some better checks. The second argument is a priority, it ends up in the `pr` field.  It's a weight, the number you'd type into the box, or what you would roll.  Position is determined by sorting.  The position that addTokenTurn() adds to is the very end of the list, so it won't be replacing anything. Token IDs are unique to a specific instance of a graphic on a specific page.  You would usually get them from the msg.selected when dealing with the API.  If you want a token representing a character on a specific page, you'd need to use findObjs(), probably based on the page where a player is.  I've got some functions for that... const getPageForPlayer = (playerid) => { let player = getObj('player',playerid); if(playerIsGM(playerid)){ return player.get('lastpage') || Campaign().get('playerpageid'); } let psp = Campaign().get('playerspecificpages'); if(psp[playerid]){ return psp[playerid]; } return Campaign().get('playerpageid'); };
1660099692

Edited 1660100325
You where right, the token_id was erroneous. Thought the token_id would have been the same between pages if linked to a character sheet since they share all the same attributes/values and are updated as the character sheet is. Misconception on my part... So far so good, I'll test it out tomorrow and remove the &{tracker} from the Character Sheet roll Template call and see if it holds. I'll copy my code here if anyone is interested at some point. Seen many people with similar questions during my research on the forums. on("ready",function(){ on("chat:message",function(msg) { if(msg.type == "general" && msg.rolltemplate == "initiative") { // Unnecessary for me but could be adressed if needed // * Find a way to get characters controlled by many players in characterList * var characterList = findObjs({type:"character",controlledby:msg.playerid}); var playerName = getObj("player",msg.playerid).get("displayname"); // Player has control over 0 character if(characterList.length == 0) { sendChat("Init+", "/w " + playerName.split(" ")[0] + " You don't have control over any character", null, {noarchive:true}); // Player has control over 1 character } else if(characterList.length == 1) { // Retrieve the page a player is currently on (Thx to The Aaron) const getPageForPlayer = (playerid) => { let player = getObj('player',playerid); if(playerIsGM(playerid)){ return player.get('lastpage') || Campaign().get('playerpageid'); } let psp = Campaign().get('playerspecificpages'); if(psp[playerid]){ return psp[playerid]; } return Campaign().get('playerpageid'); }; var characterID = characterList[0].get("id"); var playerPageID = getPageForPlayer(msg.playerid); // Retreive the token associated with the characterID on the current pageID var playerToken = findObjs({ type:"graphic", subtype:"token", represents:characterID, pageid:playerPageID })[0]; if (playerToken) { // Retrieve the result of the Initiative roll // (this part depends on your Template roll structure) let rollResult = msg.inlinerolls[1].results.total; // Helper functions for the Turn Order (Thx to The Aaron) const getTurnArray = () => ( '' === Campaign().get('turnorder') ? [] : JSON.parse(Campaign().get('turnorder'))); const getTurnArrayFromPrev = (prev) => ( '' === prev.turnorder ? [] : JSON.parse(prev.turnorder)); const setTurnArray = (ta) => Campaign().set({turnorder: JSON.stringify(ta)}); const addTokenTurn = (id, pr, pageid) => setTurnArray([...getTurnArray(), {id,pr,_pageid:(pageid||(getObj('graphic',id)||{get:''}).get('pageid'))}]); const addCustomTurn = (custom, pr) => setTurnArray([...getTurnArray(), {id:"-1",custom,pr}]); const removeTokenTurn = (tid) => setTurnArray(getTurnArray().filter( (to) => to.id !== tid)); const removeCustomTurn = (custom) => setTurnArray(getTurnArray().filter( (to) => to.custom !== custom)); const clearTurnOrder = () => Campaign().set({turnorder:'[]'}); const packTo = (to) => [{id:'HEADER',pr:Number.MAX_SAFE_INTEGER},...to].reduce((m,t)=>{ if('-1'===t.id){ m[m.length-1].packed=[...(m[m.length-1].packed || []), t]; return m; } return [...m,t]; },[]); const unpackTo = (pTo) => pTo.reduce((m,t)=>{ let packed = t.packed||[]; delete t.packed; if('HEADER' === t.id){ return [...packed,...m]; } return [...m,t,...packed]; },[]); const sorter_asc = (a, b) => ('-1' === a.id || '-1' === b.id) ? 0 : a.pr - b.pr; const sorter_desc = (a, b) => ('-1' === a.id || '-1' === b.id) ? 0 : b.pr - a.pr; const sortTurnOrder = (sortBy = sorter_desc) => Campaign().set({turnorder: JSON.stringify(unpackTo(packTo(getTurnArray()).sort(sortBy)))}); // Add the token to the Turn Order and sort it addTokenTurn(playerToken.get("id"),rollResult); sortTurnOrder(); } else { sendChat("Init+", "/w " + playerName.split(" ")[0] + " You don't have a token on this page", null, {noarchive:true}); } // Player controls many characters } else { // Unnecessary for me but could be adressed if needed // log ("Multiple characters!!! "); } } }); }); Thanks a lot for your help The Aaron!! 
1660103498
The Aaron
Roll20 Production Team
API Scripter
No problem! Just playing around with your script, see what you think of this.  If I understand your rolltemplate correctly, this should solve your multiple characters for a player problem, as well as multiple controllers for a character.  Also adds a bit of utility around accessing the contents of the rolltemplate.  =D  (Completely untested, of course...)  I also moved the function definitions to more canonical locations. on("ready",()=>{ // Helper functions for the Turn Order (Thx to The Aaron) const getTurnArray = () => ( '' === Campaign().get('turnorder') ? [] : JSON.parse(Campaign().get('turnorder'))); const getTurnArrayFromPrev = (prev) => ( '' === prev.turnorder ? [] : JSON.parse(prev.turnorder)); const setTurnArray = (ta) => Campaign().set({turnorder: JSON.stringify(ta)}); const addTokenTurn = (id, pr, pageid) => setTurnArray([...getTurnArray(), {id,pr,_pageid:(pageid||(getObj('graphic',id)||{get:''}).get('pageid'))}]); const addCustomTurn = (custom, pr) => setTurnArray([...getTurnArray(), {id:"-1",custom,pr}]); const removeTokenTurn = (tid) => setTurnArray(getTurnArray().filter( (to) => to.id !== tid)); const removeCustomTurn = (custom) => setTurnArray(getTurnArray().filter( (to) => to.custom !== custom)); const clearTurnOrder = () => Campaign().set({turnorder:'[]'}); const packTo = (to) => [{id:'HEADER',pr:Number.MAX_SAFE_INTEGER},...to].reduce((m,t)=>{ if('-1'===t.id){ m[m.length-1].packed=[...(m[m.length-1].packed || []), t]; return m; } return [...m,t]; },[]); const unpackTo = (pTo) => pTo.reduce((m,t)=>{ let packed = t.packed||[]; delete t.packed; if('HEADER' === t.id){ return [...packed,...m]; } return [...m,t,...packed]; },[]); const sorter_asc = (a, b) => ('-1' === a.id || '-1' === b.id) ? 0 : a.pr - b.pr; const sorter_desc = (a, b) => ('-1' === a.id || '-1' === b.id) ? 0 : b.pr - a.pr; const sortTurnOrder = (sortBy = sorter_desc) => Campaign().set({turnorder: JSON.stringify(unpackTo(packTo(getTurnArray()).sort(sortBy)))}); // Retrieve the page a player is currently on (Thx to The Aaron) const getPageForPlayer = (playerid) => { let player = getObj('player',playerid); if(playerIsGM(playerid)) { return player.get('lastpage') || Campaign().get('playerpageid'); } let psp = Campaign().get('playerspecificpages'); if(psp[playerid]) { return psp[playerid]; } return Campaign().get('playerpageid'); }; const playerCanControl = (obj, playerid='any') => { const playerInControlledByList = (list, playerid) => list.includes('all') || list.includes(playerid) || ('any'===playerid && list.length); let players = obj.get('controlledby') .split(/,/) .filter(s=>s.length); if(playerInControlledByList(players,playerid)){ return true; } if('' !== obj.get('represents') ) { players = (getObj('character',obj.get('represents')) || {get: function(){return '';} } ) .get('controlledby').split(/,/) .filter(s=>s.length); return playerInControlledByList(players,playerid); } return false; }; const processInlinerolls = (msg) => { if(msg.hasOwnProperty('inlinerolls')){ return msg.inlinerolls .reduce((m,v,k) => { let ti=v.results.rolls.reduce((m2,v2) => { if(v2.hasOwnProperty('table')){ m2.push(v2.results.reduce((m3,v3) => [...m3,(v3.tableItem||{}).name],[]).join(", ")); } return m2; },[]).join(', '); return [...m,{k:`$[[${k}]]`, v:(ti.length && ti) || v.results.total || 0}]; },[]) .reduce((m,o) => m.replace(o.k,o.v), msg.content); } else { return msg.content; } }; const parseRollTemplate = (t) => [...t.matchAll(/{{([^=]*?)=(.*?)}}/g)].reduce((m,p)=>({...m,[p[1]]:p[2]}),{}); on("chat:message", (msg) => { if(msg.type == "general" && msg.rolltemplate == "initiative") { // Unnecessary for me but could be adressed if needed let rt = parseRollTemplate(processInlinerolls(msg)); let characterList = findObjs({type:"character" ,name:rt.charname }) .filter(c=>playerCanControl(c,msg.playerid)) ; let playerName = getObj("player",msg.playerid).get("displayname"); // Player has control over 0 character if(characterList.length == 0) { sendChat("Init+", "/w " + playerName.split(" ")[0] + " You don't have control over any character", null, {noarchive:true}); // Player has control over 1 character } else if(characterList.length == 1) { let characterID = characterList[0] .id; let playerPageID = getPageForPlayer(msg.playerid); // Retreive the token associated with the characterID on the current pageID let playerToken = findObjs({ type:"graphic", subtype:"token", represents: characterID, pageid: playerPageID })[0]; if (playerToken) { // Retrieve the result of the Initiative roll // (this part depends on your Template roll structure) let rollResult = parseFloat(rt.roll)||0; // Add the token to the Turn Order and sort it addTokenTurn(playerToken.get("id"),rollResult); sortTurnOrder(); } else { sendChat("Init+", "/w " + playerName.split(" ")[0] + " You don't have a token on this page", null, {noarchive:true}); } // Player controls many characters } else { // Unnecessary for me but could be adressed if needed // log ("Multiple characters!!! "); } } }); });
1660183272

Edited 1660183336
Your new procedures definitely do the trick. Might need to add a few things if people want it to work for players controlling multiple tokens (asking the user to choose a character/token), but for what I was looking for this will suffice. I've added a line to limit the number of Turn Order for a token. Otherwise it was simply redundant entries piling in the Turn Order. removeTokenTurn(playerToken.get("id")); addTokenTurn(playerToken.get("id"),rollResult); sortTurnOrder(); Also added a procedure to remove Turn Order entries whenever a token is deleted. Strangely it is removed automatically when you use &{tracker} but, it doesn't when you add them this way. F rom what I can tell, i t simply leaves an entry in the Turn Order associated with no token. on("destroy:graphic", (obj) => {     if(obj.get("subtype") == "token") { // Might want to use tighter constraints         removeTokenTurn(obj.get("id"));     } }); Once again thanks for all your help. Let's be honest some of your nested procedures or callback, dunno exactly how to call them, seem like real wizardry to me. The next test, see how it all works out with my players. Have a good one!
1660188056
The Aaron
Roll20 Production Team
API Scripter
No problem!  Interesting about the auto remove. You're saying when you delete a token, it doesn't clear it from the turn order if added via a Mod script?  I'll have to try that out.  If you want me to break anything in particular down, I'd be happy too! I actually really enjoy that. 
1660190585
GiGs
Pro
Sheet Author
API Scripter
TheRollinOne said: Let's be honest some of your nested procedures or callback, dunno exactly how to call them, seem like real wizardry to me. That's why he's the aaronomancer.
Latest updates.  I've changed quite a few things.  The Character Sheet (could be a macro) now calls an API (!unselectedIni --[character_name]) instead of sending a roll Template to chat The API verifies if the player controls the character and if a token associated with that character exists on the current page. If so, a roll Template is being sent to chat Then I catch the roll Template and add an entry in the Turn Order (minimal tests are required here since the roll Template is sent by the API directly) on("ready",() => {     // Helper functions for the Turn Order (Thx to The Aaron)     const getTurnArray = () => ( '' === Campaign().get('turnorder') ? [] : JSON.parse(Campaign().get('turnorder')));     const getTurnArrayFromPrev = (prev) => ( '' === prev.turnorder ? [] : JSON.parse(prev.turnorder));     const setTurnArray = (ta) => Campaign().set({turnorder: JSON.stringify(ta)});     const addTokenTurn = (id, pr, pageid) => setTurnArray([...getTurnArray(), {id,pr,_pageid:(pageid||(getObj('graphic',id)||{get:''}).get('pageid'))}]);     const addCustomTurn = (custom, pr) => setTurnArray([...getTurnArray(), {id:"-1",custom,pr}]);     const removeTokenTurn = (tid) => setTurnArray(getTurnArray().filter( (to) => to.id !== tid));     const removeCustomTurn = (custom) => setTurnArray(getTurnArray().filter( (to) => to.custom !== custom));     const clearTurnOrder = () => Campaign().set({turnorder:'[]'});          const packTo = (to) => [{id:'HEADER',pr:Number.MAX_SAFE_INTEGER},...to].reduce((m,t)=>{         if('-1'===t.id){             m[m.length-1].packed=[...(m[m.length-1].packed || []), t];             return m;         }         return [...m,t];     },[]);          const unpackTo = (pTo) => pTo.reduce((m,t)=>{         let packed = t.packed||[];         delete t.packed;         if('HEADER' === t.id){             return [...packed,...m];         }         return [...m,t,...packed];     },[]);          const sorter_asc = (a, b) => ('-1' === a.id || '-1' === b.id) ? 0 : a.pr - b.pr;     const sorter_desc = (a, b) => ('-1' === a.id || '-1' === b.id) ? 0 : b.pr - a.pr;     const sortTurnOrder = (sortBy = sorter_desc) => Campaign().set({turnorder: JSON.stringify(unpackTo(packTo(getTurnArray()).sort(sortBy)))});          // Retrieve the page a player is currently on (Thx to The Aaron)     const getPageForPlayer = (playerid) => {         let player = getObj('player',playerid);         if(playerIsGM(playerid)) {             return player.get('lastpage') || Campaign().get('playerpageid');         }         let psp = Campaign().get('playerspecificpages');         if(psp[playerid]) {             return psp[playerid];         }         return Campaign().get('playerpageid');     };     const processInlinerolls = (msg) => {         if(msg.hasOwnProperty('inlinerolls')){             return msg.inlinerolls                 .reduce((m,v,k) => {                     let ti=v.results.rolls.reduce((m2,v2) => {                         if(v2.hasOwnProperty('table')){                             m2.push(v2.results.reduce((m3,v3) => [...m3,(v3.tableItem||{}).name],[]).join(", "));                         }                         return m2;                     },[]).join(', ');                     return [...m,{k:`$[[${k}]]`, v:(ti.length && ti) || v.results.total || 0}];                 },[])                 .reduce((m,o) => m.replace(o.k,o.v), msg.content);         } else {             return msg.content;         }     };          const parseRollTemplate = (t) => [...t.matchAll(/{{([^=]*?)=(.*?)}}/g)].reduce((m,p)=>({...m,[p[1]]:p[2]}),{});          on('chat:message', (msg) => {         if('api' === msg.type && msg.content.match(/^!unselectedIni/) ) {             let args = msg.content.split(" ");             let playerName = getObj("player",msg.playerid).get("displayname");                           if(args.length > 1 && args[1].indexOf('--') == 0) {                 let characterName = args[1].substr(2)                 for(let i = 2; i < args.length; i++) {                     characterName += ' ' + args[i];                 }                                  let characterList = findObjs({type:'character',name:characterName});                                  if(characterList.length == 0) {                     sendChat('Ini+', "/w " + playerName.split(" ")[0] + " ERROR: No character with that name was found", null, {noarchive:true});                 } else if(characterList.length == 1) {                     let characterID = characterList[0].id;                     let playerPageID = getPageForPlayer(msg.playerid);                     let playerToken = findObjs({                         type:"graphic",                         subtype:"token",                         represents: characterID,                         pageid: playerPageID                     })[0];                                          if(playerToken) {                         sendChat(characterName, '@{' + characterName + '|whispertype} &{template:ini} {{name=^{initiative}}} {{type=initiative}} {{showchar=@{' + characterName + '|rollshowchar}}} {{charname=@{' + characterName + '|character_name}}} {{roll=[[1d20+@{' + characterName + '|initiative}[MOD]+[[@{' + characterName + '|initiative}/100]]+(@{' + characterName + '|initiative_condition})[CONDITION]+@{' + characterName + '|rollmod_initiative}[QUERY] ]]}} {{shownotes=@{' + characterName + '|rollnotes_initiative}}} {{notes=@{' + characterName + '|initiative_notes}}} {{conditionsflag=[[@{' + characterName + '|initiative_condition}]]}} {{conditions=@{' + characterName + '|conditions_display}}}');                     } else {                         sendChat("Init+", "/w " + playerName.split(" ")[0] + " ERROR: You don't have a token on this page", null, {noarchive:true});                     }                 } else { // A player controls many characters with the same name                     sendChat("Init+", "/w " + playerName.split(" ")[0] + " ERROR: Multiple characters with the same name", null, {noarchive:true});                 }             } else {                 sendChat('Ini+', "/w " + playerName.split(" ")[0] + " ERROR: Invalid API call. You most likely don't control this character", null, {noarchive:true});             }         }     });          on("chat:message", (msg) => {         if(msg.type === "general" && msg.rolltemplate === "ini" && msg.playerid === "API") {             let rt = parseRollTemplate(processInlinerolls(msg));             let characterList = findObjs({type:"character",name:rt.charname});             let characterID = characterList[0].get("id");             let playerPageID = getPageForPlayer(characterList[0].get("controlledby"));             let playerToken = findObjs({                 type:"graphic",                 subtype:"token",                 represents:characterID,                 pageid:playerPageID             })[0];                          if(playerToken) {                 let rollResult = parseFloat(rt.roll) || 0;                 removeTokenTurn(playerToken.get("id"));                 addTokenTurn(playerToken.get("id"),rollResult);                 sortTurnOrder();                             }         }     }); }); I decided to use this method since with the earlier versions, the roll template was done (dice roll + template shown in chat) even if the values were invalid and no entry was added to the Turn Order. That was confusing for my players and not optimal. This code should be useable for other systems with minimal changes as long as you use Character Sheets and an Initiative roll Template. Thanks again to The Aaron for going the extra mile and help me getting this done.
1660448140
The Aaron
Roll20 Production Team
API Scripter
Sweet!  That looks pretty clean.  Happy to help whenever I can. =D