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 .
×

"add:graphic" event seems to fire before token is fully initialized

I have a character whose default token has a bar3_value specified. I need to operate on the token when it is added, based on this value. However, the "add:graphic" event seems to fire before the token is fully initialized. Is this expected behavior? And if so, what's the proper way to deal with this? I can see the bar3_value get initialized if I read the token again after a short delay, but this is obviously not a robust solution as the timing wouldn't be consistent. I could possibly add a "keep trying until the value shows up, with a sanity timeout to stop trying" hack, but that's pretty hokey. In the code below, I can see that "initial bar3" prints nothing, while "delayed bar3" prints out the actual value set on the default token.     on("add:graphic", (token) => {         if (token.get("_subtype") == "token" && token.get("represents") == lantern.id) {             sendChat("Lantern", `initial bar3: ${token.get("bar3_value")}`, null, noArchive);             setTimeout(() => {                 sendChat("Lantern", `delayed bar3: ${token.get("bar3_value")}`, null, noArchive);             }, 100);         }     });
And since I'm not sure what I'm doing wrong yet, for now I am just doing the timeout loop so I can move along:     on("add:graphic", (token) => {         if (token.get("_subtype") == "token" && token.get("represents") == lantern.id) {             let updateAttempts = [0];             let updateWhenInitted = () => {                 updateAttempts[0]++;                 sendChat("Lantern", `attempt #${updateAttempts[0]}`, null, noArchive);                 if (token.get("bar3_value")) {                     updateLantern(token);                 }                 else if (updateAttempts[0] <= 100) {                     setTimeout(updateWhenInitted, 100);                 }             };             updateWhenInitted();         }     });
1682272163
The Aaron
Roll20 Production Team
API Scripter
You're absolutely correct. It happened about 6 years ago when they rolled out game defaults for tokens.  For an example of how to deal with it, look at TokenNameNumber. 
Ok, cool, it's not just me doing something wrong then. ☺️ Workaround it is, lol. At least it's pretty easy to deal with.
1682272521

Edited 1682272706
This is what I'm currently working on btw, I'm having a blast learning roll20. And everyone on the forum is super helpful! ☺️ <a href="https://1drv.ms/v/s!Aj1lZvf1jJXtwtMHlRDam1uuETsmPQ?e=WqHMkR" rel="nofollow">https://1drv.ms/v/s!Aj1lZvf1jJXtwtMHlRDam1uuETsmPQ?e=WqHMkR</a> (Phandelver spoilers in the video, btw)
1682273154
The Aaron
Roll20 Production Team
API Scripter
Ooh!&nbsp; That's really cute! I just remembered a fairly clean way of handling this that I wrote but haven't used yet.&nbsp; Here it is: on('ready',()=&gt;{ const isAddChange = (()=&gt;{ const THRESHOLD = 300; const createRegistry = {}; on('add:graphic',(obj)=&gt;{ createRegistry[obj.id]= new Date() * 1; }); return (id) =&gt; ( (new Date()*1) - (createRegistry[id]||0) ) &lt; THRESHOLD; })(); on('change:graphic',(obj)=&gt;{ if( isAddChange(obj.id)) { log('Add Change'); } else { log('Real Change'); } }); }); Basically, isAddChange() is an IIFE that monitors graphic creation and keeps track of when new IDs are added, and returns a function that will check if a token ID was created in the last 300ms.&nbsp; You can then use it in the change event to distinguish between adds and modifies.
Oh, nice. I'm guessing that's because when e.g. the bar3_value does show up after being added, that then triggers a change event? So every add would be followed by a change? Don't know if that would only apply when there's actually bar values set.
1682273616
The Aaron
Roll20 Production Team
API Scripter
That's correct.&nbsp; Every add will always have at least one change after it.&nbsp; As it happens, it can have 2 or 3, but depending on what you're doing, the above should be sufficient.
Nice. It's something I know I'll have to deal with eventually (did I cause this change through API? or did a player really change?), so it's handy to see what you have. Next up is wanting to attach the lantern to a player, giving them a "pick up/drop" option, so that's where I'm gonna start needing to work with change events for the first time in order to "lock" the lantern to the player.
1682276155
The Aaron
Roll20 Production Team
API Scripter
One thing to be aware of is that most things you do with the API do not cause an event.&nbsp; One major exception to this is sendChat(), which will cause a "chat:message" event, so be careful to always qualify your chat handlers.&nbsp; If you end up with a sendChat(), when a sendChat() occurs, you'll have some pain dealing with it. =D You can look at TokenCondition for some example code for dealing with finding overlapping tokens, as well as "follower" behavior with a token being moved to keep up with an existing token.&nbsp; Facing and Bump also have some follower behavior, as does WeaponArcs (this time with Paths). I wrote a script one time to let a character "drop their torch", which was fun and is like the opposite of what you're doing. =D on('ready',()=&gt;{ const torchSrc = "<a href="https://s3.amazonaws.com/files.d20.io/images/119704545/iEuLX30JcxYYqwE98nUmcg/thumb.png?158640249955" rel="nofollow">https://s3.amazonaws.com/files.d20.io/images/119704545/iEuLX30JcxYYqwE98nUmcg/thumb.png?158640249955</a>"; const dropTorch = (obj) =&gt; { if(obj.get('light_hassight') &amp;&amp; obj.get('light_otherplayers')){ let size = Math.min(parseFloat(obj.get('width')),parseFloat(obj.get('width'))); let torch = createObj('graphic',{ imgsrc: torchSrc, pageid: obj.get('pageid'), layer: obj.get('layer'), controlledby: 'all', width: size, height: size, left: obj.get('left'), top: obj.get('top'), light_otherplayers: true, light_angle: obj.get('light_angle'), light_radius: obj.get('light_radius'), light_dimradius: obj.get('light_dimradius') }); setTimeout(()=&gt;toBack(torch),50); obj.set({ light_angle: 360, light_radius: 0, light_dimradius: 0 }); } }; on('chat:message',(msg)=&gt;{ if('api'===msg.type &amp;&amp; /^!drop-torch(\b\s|$)/i.test(msg.content)){ (msg.selected || []) .map(o=&gt;getObj('graphic',o._id)) .filter(g=&gt;undefined !== g) .forEach(dropTorch) ; } }); });
That's super handy, thanks again! And I'm glad to hear I won't have to deal with filtering out change events coming in from the API.
1682281584

Edited 1682281609
One problem I'm running into when you change a token's position is that it always animates to the new position. This locks the token out of being moved until it reaches the final destination. (Annoying for arrow key movement) I was looking at your TokenCondition code and didn't see anything in relation to that. Do you know of a way to set a token's position and just have it go there immediately, without the slide animation? I couldn't find anything about controlling this animation on a google search either.
1682295545
The Aaron
Roll20 Production Team
API Scripter
If you move it to the GM later then back, that might do it, but will break selection. Removing it and recreating it at the new location might also give the desired effect, but will break selection and have a different id.&nbsp;
Kk, thanks. Not a huge deal then to just live with it, but I figured I'd ask the expert. ☺️