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.