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

Bug with The Aarons Turn Tracker for spell effects and Group Initative

I tried to use the turn tracker script  found here using for example  !act -1 5 --Effect Which works as intended without Group Initative But together with Group Initative the turn of Effect is automatically skipt and it doesnt reduce the duration. Any help would be great 
Try this updated code here . I use a combination of AddCustomTurn with AddInvisibleCustomTurn and a custom 'AddSpell' (just a modified version of ACT that has a specific image for the Turn Tracker item) with this macro: !?{Spell, Visible, Hidden?|Spell,actspell|Visible,act|Hidden,aict} ?{Countdown Number|-1} ?{Number of Turns|10} --?{Caster|@{selected|token_name}}: "?{Effect Name|BlessSleepEtc}" ?{Concentration?||Yes,&amp;#171;🇨&amp;#187; |No, |} /w GM [?{Caster}&amp;#58; &amp;#34;?{Effect Name}&amp;#34; ?{Concentration?} added to turn tracker for ?{Number of Turns} turns](#" style="background: none; background-color: transparent; border: none; padding: 0px 7px; text-decoration: none; display: inline-block; outline: 2px dashed purple;) And this code (it's a little hacky and could be consolidated a lot): /* AddTurnTrackerEffects Start */ /* // Replaces SpellTurnTrackerEffects // Combines AddCustomTurn and AddInvisibleCustomTurn // AddCustomTurn: <a href="https://app.roll20.net/forum/permalink/10212772/" rel="nofollow">https://app.roll20.net/forum/permalink/10212772/</a> // AddInvisibleCustomTurn: <a href="https://app.roll20.net/forum/permalink/6606389/" rel="nofollow">https://app.roll20.net/forum/permalink/6606389/</a> // Modified version of AddCustomTurn from deukalion <a href="https://app.roll20.net/forum/post/10847052/addcustomturn-for-shield-spell-get-tracker-position-slash-index/?pageforid=10848698#post-10848698" rel="nofollow">https://app.roll20.net/forum/post/10847052/addcustomturn-for-shield-spell-get-tracker-position-slash-index/?pageforid=10848698#post-10848698</a> // <a href="https://app.roll20.net/forum/permalink/10212772/" rel="nofollow">https://app.roll20.net/forum/permalink/10212772/</a> */ // Github: <a href="https://github.com/shdwjk/Roll20API/blob/master/AddCustomTurn/AddCustomTurn.js" rel="nofollow">https://github.com/shdwjk/Roll20API/blob/master/AddCustomTurn/AddCustomTurn.js</a> // By: The Aaron, Arcane Scriptomancer // Contact: <a href="https://app.roll20.net/users/104025/the-aaron" rel="nofollow">https://app.roll20.net/users/104025/the-aaron</a> var API_Meta = API_Meta||{}; //eslint-disable-line no-var API_Meta.AddCustomTurn={offset:Number.MAX_SAFE_INTEGER,lineCount:-1}; {try{throw new Error('');}catch(e){API_Meta.AddCustomTurn.offset=(parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/,'$1'),10)-6);}} const AddCustomTurn = (() =&gt; { // eslint-disable-line no-unused-vars const scriptName = "AddCustomTurn"; const version = '0.1.1'; API_Meta.AddCustomTurn.version = version; const lastUpdate = 1625442265; // eslint-disable-line no-unused-vars const schemaVersion = 0.1; const DEFAULT_NAME = '[Custom Turn]'; const checkInstall = () =&gt; { //CHANGE log(`-=&gt; ${scriptName} v${version} &lt;=- [${new Date(lastUpdate*1000)}]`); if( ! state.hasOwnProperty(scriptName) || state[scriptName].version !== schemaVersion) { log(` &gt; Updating Schema to v${schemaVersion} &lt;`); switch(state[scriptName] &amp;&amp; state[scriptName].version) { case 0.0: /* break; // intentional dropthrough */ /* falls through */ case 'UpdateSchemaVersion': state[scriptName].version = schemaVersion; break; default: state[scriptName] = { version: schemaVersion }; break; } } }; /* eslint-disable no-unused-vars */ const getTurnArray = () =&gt; ( '' === Campaign().get('turnorder') ? [] : JSON.parse(Campaign().get('turnorder'))); const getTurnArrayFromPrev = (prev) =&gt; ( '' === prev.turnorder ? [] : JSON.parse(prev.turnorder)); const setTurnArray = (ta) =&gt; Campaign().set({turnorder: JSON.stringify(ta)}); const addTokenTurn = (id, pr) =&gt; setTurnArray([...getTurnArray(), {id,pr}]); const addCustomTurn = (custom, pr) =&gt; setTurnArray([...getTurnArray(), {id:"-1",custom,pr}]); const removeTokenTurn = (tid) =&gt; setTurnArray(getTurnArray().filter( (to) =&gt; to.id !== tid)); const removeCustomTurn = (custom) =&gt; setTurnArray(getTurnArray().filter( (to) =&gt; to.custom !== custom)); const clearTurnOrder = () =&gt; Campaign().set({turnorder:'[]'}); const sorter_asc = (a, b) =&gt; a.pr - b.pr; const sorter_desc = (a, b) =&gt; b.pr - a.pr; const sortTurnOrder = (sortBy = sorter_desc) =&gt; Campaign().set({turnorder: JSON.stringify(getTurnArray().sort(sortBy))}); /* eslint-enable no-unused-vars */ const checkFormulaOnTurn = (prevTo) =&gt; { let to=getTurnArray(); if(to.length &amp;&amp; to[0].id==='-1' &amp;&amp; prevTo[0].custom !== to.custom){ sendChat('',`[[${to[0].pr}+(${to[0].formula||0})]]`,(r)=&gt;{ to[0].pr=r[0].inlinerolls[0].results.total; setTurnArray(to); handleTurnorderChange(to,prevTo); }); } }; const opToText = (op) =&gt; { switch(op){ case 'LT': return 'less than'; case 'LE': return 'less than or equal to'; case 'EQ': return 'equal to'; case 'GE': return 'greater than or equal to'; case 'GT': return 'greater than'; default: return ''; } }; const describeAutoDelete = (entry) =&gt;{ if(entry.autoDelete){ let dc =(entry.deleteCondition||{}); return `Delete when ${opToText(dc.op)} ${dc.val||0}.`; } return ''; }; const outputEvent = (event, entry, who) =&gt;{ switch(event){ case 'expire': { if(!playerIsGM(entry.player)){ sendChat('ACT',`/w gm &lt;div style="padding:1px 3px;border: 1px solid #8B4513;background: #eeffee; color: #8B4513; font-size: 80%;"&gt;&lt;div style="background-color: #ffeeee;"&gt;&lt;b&gt;${entry.custom}&lt;/b&gt; expired and was removed.&lt;/div&gt;&lt;/div&gt;`); } sendChat('ACT',`/w "${who||entry.who}" &lt;div style="padding:1px 3px;border: 1px solid #8B4513;background: #eeffee; color: #8B4513; font-size: 80%;"&gt;&lt;div style="background-color: #ffeeee;"&gt;&lt;b&gt;${entry.custom}&lt;/b&gt; expired and was removed.&lt;/div&gt;&lt;/div&gt;`); } break; case 'remove': { if(!playerIsGM(entry.player)){ sendChat('ACT',`/w gm &lt;div style="padding:1px 3px;border: 1px solid #8B4513;background: #eeffee; color: #8B4513; font-size: 80%;"&gt;&lt;div style="background-color: #ffeeee;"&gt;&lt;b&gt;${entry.custom}&lt;/b&gt; was removed.&lt;/div&gt;&lt;/div&gt;`); } sendChat('ACT',`/w "${who||entry.who}" &lt;div style="padding:1px 3px;border: 1px solid #8B4513;background: #eeffee; color: #8B4513; font-size: 80%;"&gt;&lt;div style="background-color: #ffeeee;"&gt;&lt;b&gt;${entry.custom}&lt;/b&gt; was removed.&lt;/div&gt;&lt;/div&gt;`); } break; case 'add': { sendChat('ACT',`/w gm &lt;div style="padding:1px 3px;border: 1px solid #8B4513;background: #eeffee; color: #8B4513; font-size: 80%;"&gt;&lt;div style="background-color: #ffeeee;"&gt;&lt;b&gt;${who||entry.who}&lt;/b&gt; added custom turn: &lt;b&gt;${entry.custom}&lt;/b&gt; at &lt;b&gt;${entry.pr}&lt;/b&gt; (&lt;b&gt;${entry.formula}&lt;/b&gt;). ${describeAutoDelete(entry)}&lt;/div&gt;&lt;/div&gt;`); } break; } }; const isDeleteCondition = (entry) =&gt; { if(entry.autoDelete){ let dc =(entry.deleteCondition||{}); switch(dc.op){ case 'LT': return (parseInt(entry.pr)&lt;dc.val); case 'LE': return (parseInt(entry.pr)&lt;=dc.val); case 'EQ': return (parseInt(entry.pr)==dc.val); case 'GE': return (parseInt(entry.pr)&gt;=dc.val); case 'GT': return (parseInt(entry.pr)&gt;dc.val); } } }; const handleTurnorderChange = (to,p)=&gt;{ if(!Array.isArray(p)) { to=getTurnArray(); p = getTurnArrayFromPrev(p); } if(to.length &amp;&amp; to[0].id==='-1' &amp;&amp; to[0].custom !== p[0].custom &amp;&amp; isDeleteCondition(to[0])){ setTurnArray(to.slice(1)); outputEvent('delete',to[0]); } }; const processInlinerolls = (msg) =&gt; { if(msg.hasOwnProperty('inlinerolls')){ return msg.inlinerolls .reduce((m,v,k) =&gt; { let ti=v.results.rolls.reduce((m2,v2) =&gt; { if(v2.hasOwnProperty('table')){ m2.push(v2.results.reduce((m3,v3) =&gt; [...m3,v3.tableItem.name],[]).join(", ")); } return m2; },[]).join(', '); return [...m,{k:`$[[${k}]]`, v:(ti.length &amp;&amp; ti) || v.results.total || 0}]; },[]) .reduce((m,o) =&gt; m.replace(o.k,o.v), msg.content); } else { return msg.content; } }; const ch = (c) =&gt; { const entities = { '&lt;' : 'lt', '&gt;' : 'gt', '&amp;' : 'amp', "'" : '#39', '@' : '#64', '{' : '#123', '|' : '#124', '}' : '#125', '[' : '#91', ']' : '#93', '"' : 'quot', '*' : 'ast', '/' : 'sol', ' ' : 'nbsp' }; if( entities.hasOwnProperty(c) ){ return `&amp;${entities[c]};`; } return ''; }; const _h = { outer: (...o) =&gt; `&lt;div style="border: 1px solid black; background-color: white; padding: 3px 3px;"&gt;${o.join(' ')}&lt;/div&gt;`, title: (t,v) =&gt; `&lt;div style="font-weight: bold; border-bottom: 1px solid black;font-size: 130%;"&gt;${t} v${v}&lt;/div&gt;`, subhead: (...o) =&gt; `&lt;b&gt;${o.join(' ')}&lt;/b&gt;`, minorhead: (...o) =&gt; `&lt;u&gt;${o.join(' ')}&lt;/u&gt;`, optional: (...o) =&gt; `${ch('[')}${o.join(` ${ch('|')} `)}${ch(']')}`, required: (...o) =&gt; `${ch('&lt;')}${o.join(` ${ch('|')} `)}${ch('&gt;')}`, header: (...o) =&gt; `&lt;div style="padding-left:10px;margin-bottom:3px;"&gt;${o.join(' ')}&lt;/div&gt;`, section: (s,...o) =&gt; `${_h.subhead(s)}${_h.inset(...o)}`, paragraph: (...o) =&gt; `&lt;p&gt;${o.join(' ')}&lt;/p&gt;`, items: (o) =&gt; `&lt;li&gt;${o.join('&lt;/li&gt;&lt;li&gt;')}&lt;/li&gt;`, ol: (...o) =&gt; `&lt;ol&gt;${_h.items(o)}&lt;/ol&gt;`, ul: (...o) =&gt; `&lt;ul&gt;${_h.items(o)}&lt;/ul&gt;`, grid: (...o) =&gt; `&lt;div style="padding: 12px 0;"&gt;${o.join('')}&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;/div&gt;`, cell: (o) =&gt; `&lt;div style="width: 130px; padding: 0 3px; float: left;"&gt;${o}&lt;/div&gt;`, inset: (...o) =&gt; `&lt;div style="padding-left: 10px;padding-right:20px"&gt;${o.join(' ')}&lt;/div&gt;`, join: (...o) =&gt; o.join(' '), pre: (...o) =&gt;`&lt;div style="border:1px solid #e1e1e8;border-radius:4px;padding:8.5px;margin-bottom:9px;font-size:12px;white-space:normal;word-break:normal;word-wrap:normal;background-color:#f7f7f9;font-family:monospace;overflow:auto;"&gt;${o.join(' ')}&lt;/div&gt;`, preformatted: (...o) =&gt;_h.pre(o.join('&lt;br&gt;').replace(/\s/g,ch(' '))), code: (...o) =&gt; `&lt;code&gt;${o.join(' ')}&lt;/code&gt;`, attr: { bare: (o)=&gt;`${ch('@')}${ch('{')}${o}${ch('}')}`, selected: (o)=&gt;`${ch('@')}${ch('{')}selected${ch('|')}${o}${ch('}')}`, target: (o)=&gt;`${ch('@')}${ch('{')}target${ch('|')}${o}${ch('}')}`, char: (o,c)=&gt;`${ch('@')}${ch('{')}${c||'CHARACTER NAME'}${ch('|')}${o}${ch('}')}` }, bold: (...o) =&gt; `&lt;b&gt;${o.join(' ')}&lt;/b&gt;`, italic: (...o) =&gt; `&lt;i&gt;${o.join(' ')}&lt;/i&gt;`, font: { command: (...o)=&gt;`&lt;b&gt;&lt;span style="font-family:serif;"&gt;${o.join(' ')}&lt;/span&gt;&lt;/b&gt;` } }; const showHelp = (who) =&gt; { sendChat('',`/w "${who}" ${ _h.outer( _h.title(scriptName,version), _h.header( _h.paragraph(`${scriptName} provides an easy way to add (and remove) custom turns which increment or decrement, and have auto delete features.`) ), _h.subhead('Commands'), _h.inset( _h.font.command( `!act`, _h.optional( _h.required('formula'), _h.optional( _h.required('initial') ) ), _h.required(`--${_h.required('custom turn name')}`), _h.optional( `--delete-on-zero`, `--delete-lt ${_h.required('number')}`, `--delete-le ${_h.required('number')}`, `--delete-eq ${_h.required('number')}`, `--delete-ge ${_h.required('number')}`, `--delete-gt ${_h.required('number')}`, `--after`, `--index ${_h.required('number')}`, `--help` ) ), _h.paragraph('Add a custom turn to the Turn Order, with options for adjusting it each turn, and removing it when it has a specified value.'), _h.minorhead('Positional Arguments'), _h.ul( `${_h.bold(_h.required('formula'))} -- The formula for changing the turn. ${_h.code('+')} or ${_h.code('-')} prepended to the number specify the direction. (${_h.bold('Default')}: ${_h.code('+1')} )`, `${_h.bold(_h.required('initial'))} -- The initial value for the custom turn. (${_h.bold('Default')}: ${_h.code('0')} )` ), _h.minorhead('Dash Arguments'), _h.ul( `${_h.bold(`--${_h.required('custom turn name')}`)} -- The name of the custom turn to add.`, `${_h.bold('--delete-on-zero')} -- deletes the custom turn when its value is less than or equal to 0. Shorthand for ${_h.code('--delete-le 0')}.`, `${_h.bold(`--delete-lt ${_h.required('number')}`)} -- deletes the custom turn when its value is less than ${_h.code(_h.required('number'))}.`, `${_h.bold(`--delete-le ${_h.required('number')}`)} -- deletes the custom turn when its value is less than or equal to ${_h.code(_h.required('number'))}.`, `${_h.bold(`--delete-eq ${_h.required('number')}`)} -- deletes the custom turn when its value is equal to ${_h.code(_h.required('number'))}.`, `${_h.bold(`--delete-ge ${_h.required('number')}`)} -- deletes the custom turn when its value is greater than or equal to ${_h.code(_h.required('number'))}.`, `${_h.bold(`--delete-gt ${_h.required('number')}`)} -- deletes the custom turn when its value is greater than ${_h.code(_h.required('number'))}.`, `${_h.bold(`--after`)} -- adds the custom turn after the current turn. Shorthand for ${_h.code('--index 1')}.`, `${_h.bold(`--index ${_h.required('number')}`)} -- adds the custom turn after the entry at index ${_h.code(_h.required('number'))}.`, `${_h.bold('--help')} -- Shows the Help screen.` ), _h.paragraph(''), _h.font.command( `!dct`, _h.optional( `${_h.required('custom turn name')}`, `--help` ) ), _h.paragraph('Remove a custom turn from the turn order by name. When used by the GM, removes the first custom turn with the given name. When used by a player, removes the first custom turn they created with the given name.'), _h.minorhead('Positional Arguments'), _h.ul( `${_h.bold(_h.required('custom turn name'))} -- The name of the custom turn to remove.` ), _h.minorhead('Dash Arguments'), _h.ul( `${_h.bold('--help')} -- Shows the Help screen.` ), _h.paragraph('') ), _h.subhead('Examples'), _h.inset( _h.paragraph(`Add a turn that just counts up. Custom turn names can contain spaces:`), _h.inset( _h.pre('!act --Counter for Rounds') ), _h.paragraph(`Add a turn that counts down from 10:`), _h.inset( _h.pre('!act -1 10 --Bless') ), _h.paragraph(`Add a turn that counts down from 10 and is after the first item in the turn order (All examples are the same):`), _h.inset( _h.pre('!act -1 10 --Bless --after'), _h.pre('!act -1 10 --Bless --index 1'), _h.pre('!act -1 10 --after --Bless'), _h.pre('!act -1 10 --index 1 --Bless') ), _h.paragraph(`Add a turn that counts down from 10 and removes itself when it reaches 0 (All examples are the same):`), _h.inset( _h.pre('!act -1 10 --Bless [Bob the Slayer] --delete-on-zero'), _h.pre('!act -1 10 --Bless [Bob the Slayer] --delete-le 0'), _h.pre('!act -1 10 --delete-on-zero --Bless [Bob the Slayer]'), _h.pre('!act -1 10 --delete-le 0 --Bless [Bob the Slayer]') ), _h.paragraph(`Supports multi-line syntax by wrapping with ${_h.code('{{')} and ${_h.code('}}')}:`), _h.inset( _h.preformatted( '!act -1 10 {{', ' --Bless [Bob the Slayer]', ' --delete-on-zero', ' --after', '}}' ) ), _h.paragraph(`Removing a turn named ${_h.code("Bless")}:`), _h.inset( _h.pre('!dct Bless') ), _h.paragraph(`Removing a turn named ${_h.code("Bless [Bob the Slayer]")}:`), _h.inset( _h.pre('!dct Bless [Bob the Slayer]') ) ) ) }`); }; const handleInput = (msg) =&gt; { if('api' === msg.type) { if(msg.content.match(/^!act(\b\s|$)/) ){ let who=(getObj('player',msg.playerid)||{get:()=&gt;'API'}).get('_displayname'); let args = processInlinerolls(msg) .replace(/&lt;br\/&gt;\n/g, ' ') .replace(/(\{\{(.*?)\}\})/g," $2 ") .split(/\s+--/); let cmds=args.shift().split(/\s+/); let change=parseFloat(cmds[1]); change = Number.isNaN(change) ? '+1' : change; change = `${/^[+-]\d/.test(change)?'':'+'}${change}`; let initial = parseFloat(cmds[2])||0; let entry = { id: "-1", pr: initial, formula: change, custom: DEFAULT_NAME, autoDelete: false, player: msg.playerid, who: who, source: 'AddCustomTurn' }; //CHANGE Default add new entries to last position let idx = 9999; args.forEach(a=&gt;{ let parts = a.split(/\s+/); switch(parts[0].toLowerCase()){ case 'help': return showHelp(who); //CHANGE <a href="https://app.roll20.net/forum/post/10847052/addcustomturn-for-shield-spell-get-tracker-position-slash-index/?pageforid=10848698#post-10848698" rel="nofollow">https://app.roll20.net/forum/post/10847052/addcustomturn-for-shield-spell-get-tracker-position-slash-index/?pageforid=10848698#post-10848698</a> case 'before': let position = getTurnArray().findIndex(o =&gt; o.id === parts[1]) idx = position &gt; 0 ? position : 9999 break; case 'after': idx = 1; break; case 'index': idx = parseInt(parts[1])||0; break; case 'delete-on-zero': entry.autoDelete = true; entry.deleteCondition = { op: 'LE', val: 0}; break; case 'delete-lt': entry.autoDelete = true; entry.deleteCondition = { op: 'LT', val: parseInt(parts[1])}; break; case 'delete-le': entry.autoDelete = true; entry.deleteCondition = { op: 'LE', val: parseInt(parts[1])}; break; case 'delete-eq': entry.autoDelete = true; entry.deleteCondition = { op: 'EQ', val: parseInt(parts[1])}; break; case 'delete-ge': entry.autoDelete = true; entry.deleteCondition = { op: 'GE', val: parseInt(parts[1])}; break; case 'delete-gt': entry.autoDelete = true; entry.deleteCondition = { op: 'GT', val: parseInt(parts[1])}; break; default: { let custom = parts.join(' '); if(DEFAULT_NAME === entry.custom){ entry.custom = custom; } } break; } }); if(DEFAULT_NAME !== entry.custom){ let to=getTurnArray(); setTurnArray([...to.slice(0,idx),entry,...to.slice(idx)]); if(!playerIsGM(msg.playerid)){ outputEvent('add',entry); } } else { showHelp(who); } } else if(msg.content.match(/^!eot/i)){ let to=getTurnArray(); setTimeout(()=&gt;checkFormulaOnTurn(to),100); } else if(msg.content.match(/^!dct(\b\s|$)/i)){ let who=(getObj('player',msg.playerid)||{get:()=&gt;'API'}).get('_displayname'); let args = processInlinerolls(msg) .replace(/&lt;br\/&gt;\n/g, ' ') .replace(/(\{\{(.*?)\}\})/g," $2 ") .split(/\s+--/); let cmds=args.shift().split(/\s+/); let ctname=cmds.slice(1).join(' '); if(ctname.length){ let to = getTurnArray(); let pigm = playerIsGM(msg.playerid); let idx = to.findIndex(e=&gt;e.custom === ctname &amp;&amp; (pigm || e.player === msg.playerid)); if(-1 !== idx) { let e = to[idx]; to = [...to.slice(0,idx),...to.slice(idx+1)]; setTurnArray(to); outputEvent('remove',e); } } else { showHelp(who); } } } }; const registerEventHandlers = () =&gt; { on('chat:message', handleInput); on('change:campaign:turnorder',handleTurnorderChange); }; on('ready', () =&gt; { checkInstall(); registerEventHandlers(); }); return { // Public interface here }; })(); {try{throw new Error('');}catch(e){API_Meta.AddCustomTurn.lineCount=(parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/,'$1'),10)-API_Meta.AddCustomTurn.offset);}} // <a href="https://app.roll20.net/forum/permalink/6606389/" rel="nofollow">https://app.roll20.net/forum/permalink/6606389/</a> on('ready',function(){ "use strict"; //CHANGE const aictImgSrc = "<a href="https://s3.amazonaws.com/files.d20.io/images/58732795/pfn3AoNw630KlzHP0dGMWw/thumb.png?1532188342" rel="nofollow">https://s3.amazonaws.com/files.d20.io/images/58732795/pfn3AoNw630KlzHP0dGMWw/thumb.png?1532188342</a>"; const aictImgSrc = "<a href="https://s3.amazonaws.com/files.d20.io/images/283869561/KJ0kTqL_ICOpVrSlkcLqsw/thumb.png?1651853449" rel="nofollow">https://s3.amazonaws.com/files.d20.io/images/283869561/KJ0kTqL_ICOpVrSlkcLqsw/thumb.png?1651853449</a>"; const activeAICTs = findObjs({type:'graphic',layer:'object'}) //CHANGE .filter((t)=&gt;/images\/58732795\/pfn3AoNw630KlzHP0dGMWw\//.test(t.get('imgsrc'))) .filter((t)=&gt;/images\/283869561\/KJ0kTqL_ICOpVrSlkcLqsw\//.test(t.get('imgsrc'))) .filter((()=&gt;{ const toIds = JSON.parse(Campaign().get('turnorder')||'[]').map(to=&gt;to.id); return t =&gt; { if( ! toIds.includes(t.id)){ t.remove(); return false; } return true; }; })()) .map((t)=&gt;t.id) ; const getPageForPlayer = (playerid) =&gt; { let player = getObj('player',playerid); if(playerIsGM(playerid)){ return player.get('lastpage'); } let psp = Campaign().get('playerspecificpages'); if(psp[playerid]){ return psp[playerid]; } return Campaign().get('playerpageid'); }; let lastTurnCache = ''; const checkFormulaOnTurn = _.debounce(() =&gt; { let to=JSON.parse(Campaign().get('turnorder')||'[]'); if(to.length){ if(to[0].id!==lastTurnCache &amp;&amp; activeAICTs.includes(to[0].id)) { sendChat('',`[[${to[0].pr}+(${to[0].formula||0})]]`,(r)=&gt;{ to[0].pr=r[0].inlinerolls[0].results.total; Campaign().set('turnorder',JSON.stringify(to)); }); } lastTurnCache = to[0].id; } },10); on('chat:message',function(msg){ var args,cmds,who,initial,change,entry; if('api' === msg.type) { if(msg.content.match(/^!aict\b/) ){ who=(getObj('player',msg.playerid)||{get:()=&gt;'API'}).get('_displayname'); if(_.has(msg,'inlinerolls')){ msg.content = _.chain(msg.inlinerolls) .reduce(function(m,v,k){ var ti=_.reduce(v.results.rolls,function(m2,v2){ if(_.has(v2,'table')){ m2.push(_.reduce(v2.results,function(m3,v3){ m3.push(v3.tableItem.name); return m3; },[]).join(', ')); } return m2; },[]).join(', '); m['$[['+k+']]']= (ti.length &amp;&amp; ti) || v.results.total || 0; return m; },{}) .reduce(function(m,v,k){ return m.replace(k,v); },msg.content) .value(); } args = msg.content .replace(/&lt;br\/&gt;\n/g, ' ') .replace(/(\{\{(.*?)\}\})/g," $2 ") .split(/\s+--/); cmds = args.shift().split(/\s+/); change = parseFloat(cmds[1]); change = ( isNaN(change) ? '+1': change ); change = `${/^[+-]\d/.test(change)?'':'+'}${change}`; initial = parseFloat(cmds[2])||0; entry = args.join(' '); if(entry.length){ let to=JSON.parse(Campaign().get('turnorder')||'[]'); let pageid = getPageForPlayer(msg.playerid); let token = createObj('graphic', { subtype: 'token', imgsrc: aictImgSrc, pageid: pageid, layer: 'objects', //CHANGE layer: 'gmlayer', top: -100, left: -100, width: 70, height: 70, name: entry, showname: true }); activeAICTs.push(token.id); // to.unshift({ to.push({ id: token.id, pr: initial, _pageid: pageid, formula: change }); Campaign().set('turnorder',JSON.stringify(to)); if(!playerIsGM(msg.playerid)){ sendChat('ACT',`/w gm &lt;div style="padding:1px 3px;border: 1px solid #8B4513;background: #eeffee; color: #8B4513; font-size: 80%;"&gt;&lt;div style="background-color: #ffeeee;"&gt;&lt;b&gt;${who}&lt;/b&gt; added entry for &lt;b&gt;${entry}&lt;/b&gt; starting at &lt;b&gt;${initial}&lt;/b&gt; and changing by &lt;b&gt;${change}&lt;/b&gt;.&lt;/div&gt;&lt;/div&gt;`); } } else { sendChat('ACT',`/w "${who}" &lt;div style="padding:1px 3px;border: 1px solid #8B4513;background: #eeffee; color: #8B4513; font-size: 80%;"&gt;&lt;div style="background-color: #ffeeee;"&gt;Use &lt;b&gt;&lt;pre&gt;!aict [formula] [starting value] --[description]&lt;/pre&gt;&lt;/b&gt;&lt;/div&gt;&lt;/div&gt;`); } } else if(msg.content.match(/^!eot/i)){ _.defer(checkFormulaOnTurn); } } }); }); // <a href="https://app.roll20.net/forum/permalink/6606389/" rel="nofollow">https://app.roll20.net/forum/permalink/6606389/</a> on('ready',function(){ "use strict"; const actspellImgSrc = "<a href="https://s3.amazonaws.com/files.d20.io/images/283883646/i6JP4_CXy9yITrdnNWHJzg/thumb.png?1651859319" rel="nofollow">https://s3.amazonaws.com/files.d20.io/images/283883646/i6JP4_CXy9yITrdnNWHJzg/thumb.png?1651859319</a>"; const activeACTSpells = findObjs({type:'graphic',layer:'object'}) //CHANGE .filter((t)=&gt;/images\/58732795\/pfn3AoNw630KlzHP0dGMWw\//.test(t.get('imgsrc'))) .filter((t)=&gt;/images\/283883646\/i6JP4_CXy9yITrdnNWHJzg\//.test(t.get('imgsrc'))) .filter((()=&gt;{ const toIds = JSON.parse(Campaign().get('turnorder')||'[]').map(to=&gt;to.id); return t =&gt; { if( ! toIds.includes(t.id)){ t.remove(); return false; } return true; }; })()) .map((t)=&gt;t.id) ; const getPageForPlayer = (playerid) =&gt; { let player = getObj('player',playerid); if(playerIsGM(playerid)){ return player.get('lastpage'); } let psp = Campaign().get('playerspecificpages'); if(psp[playerid]){ return psp[playerid]; } return Campaign().get('playerpageid'); }; let lastTurnCache = ''; const checkFormulaOnTurn = _.debounce(() =&gt; { let to=JSON.parse(Campaign().get('turnorder')||'[]'); if(to.length){ if(to[0].id!==lastTurnCache &amp;&amp; activeACTSpells.includes(to[0].id)) { sendChat('',`[[${to[0].pr}+(${to[0].formula||0})]]`,(r)=&gt;{ to[0].pr=r[0].inlinerolls[0].results.total; Campaign().set('turnorder',JSON.stringify(to)); }); } lastTurnCache = to[0].id; } },10); on('chat:message',function(msg){ var args,cmds,who,initial,change,entry; if('api' === msg.type) { if(msg.content.match(/^!actspell\b/) ){ who=(getObj('player',msg.playerid)||{get:()=&gt;'API'}).get('_displayname'); if(_.has(msg,'inlinerolls')){ msg.content = _.chain(msg.inlinerolls) .reduce(function(m,v,k){ var ti=_.reduce(v.results.rolls,function(m2,v2){ if(_.has(v2,'table')){ m2.push(_.reduce(v2.results,function(m3,v3){ m3.push(v3.tableItem.name); return m3; },[]).join(', ')); } return m2; },[]).join(', '); m['$[['+k+']]']= (ti.length &amp;&amp; ti) || v.results.total || 0; return m; },{}) .reduce(function(m,v,k){ return m.replace(k,v); },msg.content) .value(); } args = msg.content .replace(/&lt;br\/&gt;\n/g, ' ') .replace(/(\{\{(.*?)\}\})/g," $2 ") .split(/\s+--/); cmds = args.shift().split(/\s+/); change = parseFloat(cmds[1]); change = ( isNaN(change) ? '+1': change ); change = `${/^[+-]\d/.test(change)?'':'+'}${change}`; initial = parseFloat(cmds[2])||0; entry = args.join(' '); if(entry.length){ let to=JSON.parse(Campaign().get('turnorder')||'[]'); let pageid = getPageForPlayer(msg.playerid); let token = createObj('graphic', { subtype: 'token', imgsrc: actspellImgSrc, pageid: pageid, layer: 'objects', //CHANGE layer: 'gmlayer', top: -100, left: -100, width: 70, height: 70, name: entry, showname: true, showplayers_name: true }); activeACTSpells.push(token.id); // to.unshift({ to.push({ id: token.id, pr: initial, _pageid: pageid, formula: change }); Campaign().set('turnorder',JSON.stringify(to)); if(!playerIsGM(msg.playerid)){ sendChat('ACT',`/w gm &lt;div style="padding:1px 3px;border: 1px solid #8B4513;background: #eeffee; color: #8B4513; font-size: 80%;"&gt;&lt;div style="background-color: #ffeeee;"&gt;&lt;b&gt;${who}&lt;/b&gt; added entry for &lt;b&gt;${entry}&lt;/b&gt; starting at &lt;b&gt;${initial}&lt;/b&gt; and changing by &lt;b&gt;${change}&lt;/b&gt;.&lt;/div&gt;&lt;/div&gt;`); } } else { sendChat('ACT',`/w "${who}" &lt;div style="padding:1px 3px;border: 1px solid #8B4513;background: #eeffee; color: #8B4513; font-size: 80%;"&gt;&lt;div style="background-color: #ffeeee;"&gt;Use &lt;b&gt;&lt;pre&gt;!actspell [formula] [starting value] --[description]&lt;/pre&gt;&lt;/b&gt;&lt;/div&gt;&lt;/div&gt;`); } } else if(msg.content.match(/^!eot/i)){ _.defer(checkFormulaOnTurn); } } }); }); /* AddTurnTrackerEffects End */