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

AICT (Add Invisible Custom Turn) issue

Hi everyone, I've recently started using AICT in my campaign to track the cooldowns of monster abilities more conveniently.  I have been using ACT (Add Custom Turn) for the last 5 years with no issue whatsoever. However, AICT seems to be causing an issue that ACT never caused. Whenever an invisible turn created by AICT is in the Turn Tracker, any macro or text input that references the Turn Tracker, such as: [[@{tracker|Whatever}+2]] Causes an issue that breaks the entire macro. The error text is then as follows: TypeError: Cannot read properties of undefined (reading 'toLowerCase') I have also noticed (I'm a script noob) that the items created by AICT have a different "appearance" in the Turn Tracker than other items created manually. Does anyone know why this issue comes up? Thanks in advance! And many thanks to Aaron for both scripts as well!
There was an Update recently regarding the Turn Tracker. I assume that your Problem is related to that...
1691645411
The Aaron
Roll20 Production Team
API Scripter
Hmm. I'll have to take a look. 
1691684345
The Aaron
Roll20 Production Team
API Scripter
Ok, I've duplicated this issue.  There seems to have been a change in Roll20's turn order that is causing this to happen.  I've brought it up to the devs, but it may be that this script is using emergent behavior that isn't actually supported (formulas on token turns, rather than custom turns), so I might need to reimplement some things.
Okay, that's good to hear. Thanks for the heads up!
The Aaron said: Ok, I've duplicated this issue.  There seems to have been a change in Roll20's turn order that is causing this to happen.  I've brought it up to the devs, but it may be that this script is using emergent behavior that isn't actually supported (formulas on token turns, rather than custom turns), so I might need to reimplement some things. Hi back, just checking to see if there is anything new on this subject matter! Or maybe we'll get notified when it does happen? Thanks in advance.
Boris said: The Aaron said: Ok, I've duplicated this issue.  There seems to have been a change in Roll20's turn order that is causing this to happen.  I've brought it up to the devs, but it may be that this script is using emergent behavior that isn't actually supported (formulas on token turns, rather than custom turns), so I might need to reimplement some things. Hi back, just checking to see if there is anything new on this subject matter! Or maybe we'll get notified when it does happen? Thanks in advance. Just making sure that this topic doesn't die because the script still does not work when an item from AICT is present in the turn order.
Any update on this?
David B. said: Any update on this? I have tested to see if the issue persists (16th of October 2023) and it persists. No news, I'm guessing there are other issues that are more important.
1697502844

Edited 1697503021
Andrew R.
Pro
Sheet Author
ScriptCards supports Turn Tracker now, so you might be able to get the functionality you want with that. I used it to replace ACT calls with built-in functions, and posted an example in the ScriptCards thread.  (For the Crimson Bat, a Glorantha mega-monster)
Andrew R. said: ScriptCards supports Turn Tracker now, so you might be able to get the functionality you want with that. I used it to replace ACT calls with built-in functions, and posted an example in the ScriptCards thread.  (For the Crimson Bat, a Glorantha mega-monster) Thanks, I have found the post and it seems that you can indeed create custom turn tracker items. I have read through the wiki of Scriptcards, and could not find a way to: Create a value that is hidden (or on the GM layer) Add a function to the value (+1 each turn or, in my case, -1 each turn) If you have any knowledge of these features, I'd be greatly interested.
1697589013
The Aaron
Roll20 Production Team
API Scripter
I pinged the dev thread on this again.  I'm not hopeful it will get fixed.  I think I'll have to re-implement this functionality.
The Aaron said: I pinged the dev thread on this again.  I'm not hopeful it will get fixed.  I think I'll have to re-implement this functionality. Hey, thanks for keeping up with this issue. At least you are trying, thanks so much for that!
I believe one solution would be to write a custom Mod script to track.  You would trigger the cooldown math every time a defined round counter was updated and whisper to the GM chat window the active cooldowns and how many more rounds are left.  
Will M. said: I believe one solution would be to write a custom Mod script to track.  You would trigger the cooldown math every time a defined round counter was updated and whisper to the GM chat window the active cooldowns and how many more rounds are left.   If I could, I would, but I'm not too savvy on writing scripts sadly. Thus, I try to work with what's out here!
1697724203
timmaugh
Pro
API Scripter
Boris said: I have read through the wiki of Scriptcards, and could not find a way to: Create a value that is hidden (or on the GM layer) Add a function to the value (+1 each turn or, in my case, -1 each turn) If you have any knowledge of these features, I'd be greatly interested. While I can construct a system that would do this utilizing extant scripts (SpawnDefaultToken can spawn your token, TokenMod can move it to the GM Layer if SpawnDefaultToken can't, OnMyTurn can fire every turn, prompting something like TokenMod or ChatSetAttr to decrement a value, etc.)... while I can do that, I think you can still utilize your existing setup if you just swap in Fetch constructions for the tracker references. Fetch tracker syntax: Attribute: @(tracker.Whatever) Token Prop: @(tracker.bar1) Token Status: @(tracker.status.Bloodied.is) Token Status Value: @(tracker.status.Bloodied) If there is a chance that the token will be on another page, you'll need to include a [gm] reference: @(tracker[gm].AC) And if you want to glance ahead/behind in the Turn Order, you can supply an offset: @(tracker+1.AC) ...or... @(tracker[gm]+1.AC) Since Fetch is a metascript, you'll need to slow down any inline a Fetch construction is a part of (keeping it from resolving until after Fetch has had time to return the value). So your first example of: [[@{tracker|Whatever}+2]] ...would become: [\][\]@(tracker.Whatever)+2\]\] Swap that into any macro that is headed to a mod script and it should work. On the other hand, if the command line you put that Fetch construction in is NOT headed to a mod script (maybe it's headed straight to chat, instead), you will actually need to wrap the whole line in: ! ...and... {&simple} For instance, you can check this by entering the above deferred inline roll like this: ! [\][\]@(tracker.Whatever)+2\]\] {&simple}
 while I can do that, I think you can still utilize your existing setup if you just swap in Fetch constructions for the tracker references. Thanks for such an in-depth answer. I understand the logic of what you're telling me, but what should I do with this? Add it within the AICT script? I know almost nothing about scripts, so I apologize if this question sounds dumb. Also, I see in your demonstration that this "script" can create an element in the turn tracker with a custom value that can go up and down whenever a specified element of the turn order progresses, but can it be created on the GM layer is my question. Sorry again for need to clarify, it stems from my lack of knowledge.
1697735337
timmaugh
Pro
API Scripter
No worries, Boris. You initially said: Whenever an invisible turn created by AICT is in the Turn Tracker, any macro or text input that references the Turn Tracker, such as: [[@{tracker|Whatever}+2]] Causes an issue that breaks the entire macro. So that's what I was trying to address... those other macros that might reference the token in the Turn Tracker. You wouldn't use the Fetch construction in the AICT command... you'd just let that operate as you would normally. Once AICT added the custom invisible entry to the Turn Tracker, you should be able to get the info out of it using the Fetch construction of: @(tracker.Whatever) So, run an AICT command line to get an entry, then make that entry "up" in the Turn Order (ie, that token's turn). Then run this command and see what you get: !The value is @(tracker.Whatever) {&simple} ...just changing "Whatever" to an actual attribute of the character... or a bar value of the token, etc. And, just to be clear, Fetch isn't creating the token or the entry on the Turn Tracker... it is just reading the information. When it takes an offset like... @(tracker+1.Whatever) ...it is actually looking at the next token in the Turn Tracker (the token with a turn after the current token). Give it a +2 and it will look at the third token in the list (or circle back to the top if there aren't enough tokens in the tracker). At this point I'm not sure if your need to have the token on the GM layer is an actual requirement of  your original setup, or if it is a problem that cropped up as you were trying to find a workaround to AICT causing issues (in other words, not sure if this is an X-Y problem). I would first suggest you try the above to see if AICT will work as is, with your other macros or Turn Tracker references utilizing Fetch constructions. If that doesn't work -- or if you do actually need the token to be on the GM layer, then I would look at SpawnDefaultToken and TokenMod. If Spawn can't spawn your token and move it to the GM Layer automatically (which I think it can), it's relatively simple to have TokenMod do it for you. But it seems that if you have the token on the GM layer, you wouldn't need the invisible turn entry, so I feel like that's superfluous.
So, I have tried running this command: !aict -1 3 --Demonic Armor It creates a custom turn item called Demonic Armor, with a value of 3, that decreases by 1 each turn. With this value in the turn tracker as the current turn, as you indicated, I used this command: !The value is @(tracker.Demonic Armor) {&simple} But nothing comes up in the chat... To reiterate my goal with this: I want to track monster ability cooldowns in the turn tracker, but I don't want player to see them, hence why they have to be invisible. So if an ennemy "Bear" uses the macro "Maul" with a cooldown of two turns, it should create an invisible entry called "Maul" with a value of 2, that decreases by 1 each turn.
1697745758
timmaugh
Pro
API Scripter
Oh! I didn't realize you were looking to read the TRACKER value (ie, the initiative of the token)... I thought you were trying to read some attribute/property of the token currently represented by the "tracker" designation in Fetch (ie, where "tracker" is a stand-in for the current token "up" in the turn order). To do what you want to do is also manageable. Fetch offers a "tracker" property representing the current tracker value of the token. Since you already know the name of your token, you can refer to it that way: @(Demonic Armor.tracker) So, to check it: !The value is @(Demonic Armor.tracker) {&simple} If *that* doesn't work, then I'll have to look closer to see if Fetch is running afoul of the same backend problems Aaron noted were plaguing AICT. Let me know!
timmaugh said: Oh! I didn't realize you were looking to read the TRACKER value (ie, the initiative of the token)... I thought you were trying to read some attribute/property of the token currently represented by the "tracker" designation in Fetch (ie, where "tracker" is a stand-in for the current token "up" in the turn order). To do what you want to do is also manageable. Fetch offers a "tracker" property representing the current tracker value of the token. Since you already know the name of your token, you can refer to it that way: @(Demonic Armor.tracker) So, to check it: !The value is @(Demonic Armor.tracker) {&simple} If *that* doesn't work, then I'll have to look closer to see if Fetch is running afoul of the same backend problems Aaron noted were plaguing AICT. Let me know! Believe it or not, I just understood that Fetch was an actual script that I needed to install... Anyway, I installed it and this command: !The value is @(Demonic Armor.tracker) {&simple} Does... nothing? No error in chat or anything else appears anywhere when I do it. The turn item still exists and it's still its turn though.
1697886530
timmaugh
Pro
API Scripter
That line actually requires the ZeroFrame script,too. You can 8nstall it, or install the MetaScriptToolbox script, which will get you ALL the metascript toys. =D Then give that line another run and it will work.
timmaugh said: That line actually requires the ZeroFrame script,too. You can 8nstall it, or install the MetaScriptToolbox script, which will get you ALL the metascript toys. =D Then give that line another run and it will work. Okay, it works! This is the usual type of macro that I use with Powercards that causes the issue: !power {{ --name|[img](<a href="https://s3.amazonaws.com/files.d20.io/images/55373345/WDSsGWpYJzTXDCWb9MzcOQ/max.jpg" rel="nofollow">https://s3.amazonaws.com/files.d20.io/images/55373345/WDSsGWpYJzTXDCWb9MzcOQ/max.jpg</a>) Armure démoniaque --format|Démoniste --Compétence :|Magie --[Effets](<a href="http://journal.roll20.net/handout/-LEAeOUTEo2eu2ZygM5Z" rel="nofollow">http://journal.roll20.net/handout/-LEAeOUTEo2eu2ZygM5Z</a>) :|Octroie une **Résistance** aux **Ombres** et au **Feu** et augmente tous les soins reçus de +[[9]].^^ Octroie un avantage aux JS Concentration.^^ Dure [[2]] tours. --PA :|AR (1) --Coût :|[[25]] Mana --Disponible :|Tour [[@{tracker|Tour Actuel}+3]] }} !modbattr --silent --name Urgan --Mana|-25 !aict -1 3 --Armure démoniaque Usually, the part that causes the error is this line, because it's the only one referencing the turn order: -Disponible :|Tour [[@{tracker|Tour Actuel}+3]] What do you suggest I replace this with that would not cause the issue? I have tried a few combinations of what you told me and couldn't make any work myself.
1697909870

Edited 1697999302
The Aaron
Roll20 Production Team
API Scripter
Just in time for an updated version from me. =D Code: // Add Invisible CustomTurn // 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.AddInvisibleCustomTurn={offset:Number.MAX_SAFE_INTEGER,lineCount:-1}; {try{throw new Error('');}catch(e){API_Meta.AddInvisibleCustomTurn.offset=(parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/,'$1'),10)-6);}} on('ready',() =&gt; { const version = '0.1.2'; API_Meta.AddInvisibleCustomTurn.version = version; 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 activeAICTs =(()=&gt;{ const aictRegex = new RegExp(`images/58732795/pfn3AoNw630KlzHP0dGMWw/`); const toIds = JSON.parse(Campaign().get('turnorder')||'[]').map(to=&gt;to.id); return findObjs({type:'graphic',layer:'gmlayer'}) .filter((t)=&gt;aictRegex.test(t.get('imgsrc'))) .filter((()=&gt;{ return t =&gt; { if( ! toIds.includes(t.id)){ t.remove(); return false; } return true; }; })()) .reduce((m,t)=&gt;({...m,[t.id]:t.get('bar1_value')}),{}); })(); 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.hasOwnProperty(to[0].id)) { sendChat('',`[[${to[0].pr}+(${activeAICTs[to[0].id]||0})]]`,(r)=&gt;{ to[0].pr=r[0].inlinerolls[0].results.total; Campaign().set('turnorder',JSON.stringify(to)); }); } lastTurnCache = to[0].id; } },10); 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; } }; on('chat:message',function(msg){ if('api' === msg.type) { if(msg.content.match(/^!aict\b/) ){ let who=(getObj('player',msg.playerid)||{get:()=&gt;'API'}).get('_displayname'); let content = processInlinerolls(msg); let args = content .replace(/&lt;br\/&gt;\n/g, ' ') .replace(/(\{\{(.*?)\}\})/g," $2 ") .split(/\s+--/); let cmds = args.shift().split(/\s+/); let change = parseFloat(cmds[1]); change = ( isNaN(change) ? '+1': change ); change = `${/^[+-]\d/.test(change)?'':'+'}${change}`; let initial = parseFloat(cmds[2])||0; let 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: 'gmlayer', top: -100, left: -100, width: 70, height: 70, name: entry, showname: true, bar1_value: change }); activeAICTs[token.id] = change; to.unshift({ id: token.id, pr: initial, _pageid: pageid, custom:'' }); Campaign().set('turnorder',JSON.stringify(to)); if(!playerIsGM(msg.playerid)){ sendChat('AICT',`/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('AICT',`/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); } } }); on('change:campaign:turnorder',()=&gt;{ _.defer(checkFormulaOnTurn); }); }); {try{throw new Error('');}catch(e){API_Meta.AddInvisibleCustomTurn.lineCount=(parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/,'$1'),10)-API_Meta.AddInvisibleCustomTurn.offset);}}
1697920279
timmaugh
Pro
API Scripter
OK, if you want that line to be an inline roll, you will need ZeroFrame deferrals to make it work (you'll basically prevent Roll20 from seeing the roll until after Fetch has had a chance to resolve the retrieval of the value from the tracker). The result would look like this: -Disponible :|Tour [\][\]@(Tour Actuel.tracker)+3\]\]
The Aaron said: Just in time for an updated version from me. =D Hey, thanks for the new code! I have tried running this macro with the new script: !power {{ --name|[img](<a href="https://s3.amazonaws.com/files.d20.io/images/55373345/WDSsGWpYJzTXDCWb9MzcOQ/max.jpg" rel="nofollow">https://s3.amazonaws.com/files.d20.io/images/55373345/WDSsGWpYJzTXDCWb9MzcOQ/max.jpg</a>) Armure démoniaque --format|Démoniste --Compétence :|Magie --[Effets](<a href="http://journal.roll20.net/handout/-LEAeOUTEo2eu2ZygM5Z" rel="nofollow">http://journal.roll20.net/handout/-LEAeOUTEo2eu2ZygM5Z</a>) :|Octroie une **Résistance** aux **Ombres** et au **Feu** et augmente tous les soins reçus de +[[9]].^^ Octroie un avantage aux JS Concentration.^^ Dure [[2]] tours. --PA :|AR (1) --Coût :|[[25]] Mana --Disponible :|Tour [[@{tracker|Tour Actuel}+3]] }} !modbattr --silent --name Urgan --Mana|-25 !aict -1 3 --Armure démoniaque But the issue persists, with this error in chat: TypeError: Cannot read properties of undefined (reading 'toLowerCase') Maybe I need to reboot something or do a hard reset? timmaugh said: OK, if you want that line to be an inline roll, you will need ZeroFrame deferrals to make it work (you'll basically prevent Roll20 from seeing the roll until after Fetch has had a chance to resolve the retrieval of the value from the tracker). The result would look like this: -Disponible :|Tour [\][\]@(Tour Actuel.tracker)+3\]\] This works! Amazing!
1697999350
The Aaron
Roll20 Production Team
API Scripter
Ok, I fixed the script above.&nbsp; It was caused by something completely unexpected that I'll probably need to fix in some other scripts like GroupInitiative and AddCustomTurn.
Thanks Aaron! I have copied the updated script (from the message above) in my game, and it seems to work! Thanks to both of you, I now have two solutions to tackle my issue, fantastic!&nbsp;