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

Can a button on a character sheet be used to control light emission?

1508775396

Edited 1508775477
MSG
Pro
Sheet Author
Not sure if this is something for the API, or if a SheetWorker can do. What I would like to do is to place a button on a character, say it's noted as name="attr_useTorch" , and when clicked, it changes the selected token's light emission for [x] amount of time. For example:  Char A has a light emission set to 18/12 (light/dim).  However, if selected and the Use Torch button is clicked, it changes their light emission to 36/24 for 10 minutes - then revert it back to the 18/12 when time runs out?  Obviously, it would need to temporarily save the original values so it knows what to set it back to. Is this doable at all?  Could a macro do this (no worries if that's not possible)? Bonus:  Could that same click decrement the number of torches they have in inventory by 1? Thanks!
1508776153
The Aaron
Pro
API Scripter
The only way this could be possible is with the button on the Character Sheet triggering an API command.  Sheet Workers cannot modify tokens. But at that point, you can do whatever you like, for the most part.  You can do both of the above with off-the-shelf api scripts.  Torch or TokenMod for the lighting, and ChatSetAttr for the decrementing of the torch count.  You'll probably want to hide (or disable) the button when the count is 0 using a sheetworker.
1508776968
MSG
Pro
Sheet Author
The Aaron said: The only way this could be possible is with the button on the Character Sheet triggering an API command.  Sheet Workers cannot modify tokens. But at that point, you can do whatever you like, for the most part.  You can do both of the above with off-the-shelf api scripts.  Torch or TokenMod for the lighting, and ChatSetAttr for the decrementing of the torch count.  You'll probably want to hide (or disable) the button when the count is 0 using a sheetworker. Thank you for the reply! However, as I've got no idea on how to have a button trigger an API command (I'm not even sure how such a command would be written), and I know nothing of the ChatSetAttr (is that a sheetworker thing?), I'll probably just have to go with changing them manually every time they use a torch. It's nice to know that it's doable, though - just beyond my knowledge right now (I've only been Pro for a few months and know nothing about API scripting).  I've looked at a few API scripts and they seem to be ES6 - and I am very much a novice in ES5, and ES6 just confuses me a lot right now.  I've written plugins for my forum (in ES5), but the coding is most likely laughable by experienced coders. I'll see what I might be able to do with Torch or TokenMod, though - they sound like they may save me some manual work. Thank you again for your insight and feedback.  Much appreciated!
1508777443
The Aaron
Pro
API Scripter
for the sheet to call an API command, you literally just put the command in where you would otherwise put the roll.  I've not messed with it, but someone should be able to pop in and give an example. Torch, TokenMod, and ChatSetAttr are all API scripts in the repo (and I think all in the 1-click).
1508779066
MSG
Pro
Sheet Author
The Aaron said: for the sheet to call an API command, you literally just put the command in where you would otherwise put the roll.  I've not messed with it, but someone should be able to pop in and give an example. Torch, TokenMod, and ChatSetAttr are all API scripts in the repo (and I think all in the 1-click). I've just installed Torch.  Before I continue, please forgive my ignorance - as this is the first time I've used an API script. I've typed in !torch in chat to get the help.  The format of the command seems a bit confusing, as it shows this in the help: !torch [<Radius> [<Dim Start> [<All Players> [<Token ID>|<--Angle> ... ]]]] I selected a token and did a @{selected|token_id} to get that token's ID (which came up as -KsaYdrJ8zPpDErmhVz4).  If I wanted only that token affected, is it typed in as: !torch [36 24 false -KsaYdrJ8zPpDErmhVz4 360] or !torch [36 [24 [false [-KsaYdrJ8zPpDErmhVz4 [360]]]] or something different.  My apologies for the newbie question.
1508780210
The Aaron
Pro
API Scripter
Yeah, that one is particularly arcane.  I use a common syntax for specifying command line arguments, that one didn't turn out too clear though. Basically, whatever is in [ ] is optional and ... means repeat as many times as you like. So that expands out to this: !torch !torch <Radius> !torch <Radius> <Dim Start> !torch <Radius> <Dim Start> <All Players> !torch <Radius> <Dim Start> <All Players> <Token ID> ... !torch <Radius> <Dim Start> <All Players> <--Angle> <Token ID> ... The practical upshot is you use it like this: !torch 60 30 no for a 60' light that dims at 30' and isn't visible to anyone but the controlling player, on the selected token !torch 50 - - --180 @{target|token_id} for a 50' light, dims at 25' (default second arg because of the -, which means half the first arg), visible to all players (- is default, which is all), 180º angle, and affecting a token which will be selected with the @{target} specifier. Hope that helps.
1508781722
MSG
Pro
Sheet Author
It does.  Thank you very much!
1508781780
The Aaron
Pro
API Scripter
No worries! =D
1508783111
MSG
Pro
Sheet Author
I've made a couple of macros for it: UseTorchOn - !torch 40 36 yes --360 @{selected|token_id} UseTorchOff - !torch 20 18 yes --120 @{selected|token_id} I used @{selected|token_id} instead of @{target|token_id} as I've made those 2 macros as token actions.
1508783894
The Aaron
Pro
API Scripter
You can leave the @{selected} part off, if you don't specify a token ID, it will affect the selected token(s).  =D
1508852024

Edited 1508852721
MSG
Pro
Sheet Author
The Aaron said: You can leave the @{selected} part off, if you don't specify a token ID, it will affect the selected token(s).  =D Thanks for the tip!  I'll probably leave it on, though, just so I remember that there is an optional parameter there.  :) Do you have a support thread for Torch?  I've got a question about it, but it would probably start putting this thread off topic by asking here.  Thanks! EDIT:  I found it, but it appears that the thread's been closed.  :(
1508852713
The Aaron
Pro
API Scripter
It's long since closed:&nbsp; <a href="https://app.roll20.net/forum/post/1104897/script-t" rel="nofollow">https://app.roll20.net/forum/post/1104897/script-t</a>... You can start a new thread, post it here, or PM me, whichever you like. =D
1508852849
MSG
Pro
Sheet Author
LOL - You must have posted while I was doing my edit.&nbsp; :D It seems that !daytime and !nighttime only work on the maps the Players ribbon is on?&nbsp; I was on my Pinjerris map while the Player ribbon in on the Pinj Inn map - and doing either command only affected the Pinj Inn map, and not the map I was on.&nbsp; Is this intended behavior? Thanks!
1508853516
The Aaron
Pro
API Scripter
Yes, at least it was.&nbsp; There didn't used to be a way to tell what page the GM was on.&nbsp; I should probably update it to fix that.
1508853855
MSG
Pro
Sheet Author
Okay.&nbsp; Thank you! I'll keep an eye out if you do an update for it.&nbsp; Love the script, by the way.&nbsp; I'm also thinking of putting it in Notepad++ to see if I can learn how to do scripts.&nbsp; You wouldn't happen to have any scripts that have comments in them do you - to explain the code line / section / function?
1508854916
The Aaron
Pro
API Scripter
I've got a few small ones. Here's a snippet that spawns a random fx using the command !randomfx : // This registers a function to run when the 'ready' event occurs //&nbsp; &nbsp;'ready' happens when the API Sandbox has fully loaded all the elements of the current game //&nbsp; &nbsp;[ <a href="https://wiki.roll20.net/API:Events#Campaign_Event" rel="nofollow">https://wiki.roll20.net/API:Events#Campaign_Event</a>... ] on('ready',function(){ &nbsp; &nbsp; // This tells Javascript to enforce certain strict rules when evaluating the code (generally things that avoid errors). &nbsp; &nbsp; "use strict"; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; // Setting up variables.&nbsp; These exist in the current function and for all functions defined within it. &nbsp; &nbsp; //&nbsp; &nbsp;These are in a Javascript Closure, something you may want to read up on. &nbsp; &nbsp; //&nbsp; &nbsp;[ <a href="https://wiki.roll20.net/API:Events#Campaign_Event" rel="nofollow">https://wiki.roll20.net/API:Events#Campaign_Event</a>... ] &nbsp; &nbsp; var affectedTokens={},&nbsp; //&lt; an object to store tokens that are in the rotation to receive random animations &nbsp; &nbsp; &nbsp; &nbsp; minSeconds = 5,&nbsp; &nbsp; &nbsp;//&lt; the minimum time to wait before applying another effect &nbsp; &nbsp; &nbsp; &nbsp; maxSeconds = 15,&nbsp; &nbsp; //&lt; the maximum time to wait before applying another effect &nbsp; &nbsp; &nbsp; &nbsp; // Arrays of choices for building a random effect &nbsp; &nbsp; &nbsp; &nbsp; eColor = [ 'acid', 'blood', 'death', 'fire', 'frost', 'holy', 'slime' ], &nbsp; &nbsp; &nbsp; &nbsp; eType = [ 'bubblingcauldron','burn','burst','explosion','healinglight','holyfire','nova','smokebomb','splatter' ], &nbsp; &nbsp; // a function that assembles a random effect name &nbsp; &nbsp; randomEffect=function(){ &nbsp; &nbsp; &nbsp; &nbsp; // _.sample() returns a random subset from a collection.&nbsp; By default, it returns a single value. &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; [ <a href="https://wiki.roll20.net/API:Events#Campaign_Event" rel="nofollow">https://wiki.roll20.net/API:Events#Campaign_Event</a>... ] &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; This will return a string such as 'burn-acid' or 'nova-frost' &nbsp; &nbsp; &nbsp; &nbsp; return _.sample(eType)+'-'+_.sample(eColor); &nbsp; &nbsp; }, &nbsp; &nbsp; // a function that creates a random effect at a particular location (x,y) on a particular page (p) &nbsp; &nbsp; applyEffect=function(x,y,p){ &nbsp; &nbsp; &nbsp; &nbsp; // spawnFx() creates an effect at a point on a page. &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;[ <a href="https://wiki.roll20.net/API:Events#Campaign_Event" rel="nofollow">https://wiki.roll20.net/API:Events#Campaign_Event</a>... ] &nbsp; &nbsp; &nbsp; &nbsp; spawnFx(x,y,randomEffect(),p); &nbsp; &nbsp; }; &nbsp; &nbsp; // This registers a function to run when the 'chat:message' event occurs &nbsp; &nbsp; //&nbsp; &nbsp;'chat:message' happens whenever anything is put into the chat box by anyone connected to the game &nbsp; &nbsp; //&nbsp; &nbsp;[ <a href="https://wiki.roll20.net/API:Events#Campaign_Event" rel="nofollow">https://wiki.roll20.net/API:Events#Campaign_Event</a>... ] &nbsp; &nbsp; on('chat:message',function(msg){ &nbsp; &nbsp; &nbsp; &nbsp; // do some checks to make sure we want to run for this message &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;'api' message type is for messages that begin with '!' &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;playerIsGM() will insure that only a gm can run this command &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;[ <a href="https://wiki.roll20.net/API:Events#Campaign_Event" rel="nofollow">https://wiki.roll20.net/API:Events#Campaign_Event</a>... ] &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;.match(/^!randomfx/) checks to make sure the command is the right one. &nbsp; &nbsp; &nbsp; &nbsp; if('api' === msg.type && playerIsGM(msg.playerid) && msg.content.match(/^!randomfx/) ){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // _.chain() is a powerful functional programming method in underscore.js &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;The basic idea is you take a collection as the start of a chain and &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;perform various operations on it.&nbsp; The input to an operation is the&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;output from the previous and so on. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;[ <a href="https://wiki.roll20.net/API:Events#Campaign_Event" rel="nofollow">https://wiki.roll20.net/API:Events#Campaign_Event</a>... ] &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;msg.selected is an array of { _type: &lt;typename&gt; ,_id: &lt;id&gt; } objects which &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;describe the currently selected objects. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _.chain(msg.selected) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // .map() takes a collection and call a function on each element, then returns &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;an array containing the result of each call.&nbsp; Using .chain(), the first argument &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;behind the scenes is the collection in the chain. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;[ <a href="https://wiki.roll20.net/API:Events#Campaign_Event" rel="nofollow">https://wiki.roll20.net/API:Events#Campaign_Event</a>... ] &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;msg.selected -&gt; .map() -&gt; all the Roll20 graphics that were selected &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .map(function(o){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // This will get the Roll20 graphic associated with the selected id. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;If what was selected wasn't a graphic (a drawing, some text, etc) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;it will return the javascript identifier 'undefined'. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;[ <a href="https://wiki.roll20.net/API:Events#Campaign_Event" rel="nofollow">https://wiki.roll20.net/API:Events#Campaign_Event</a>... ] &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return getObj('graphic',o._id); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // .compact() returns the collection it was given without any of the 'falsy' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;entries, things like 'undefined', 'false', 'null', etc.&nbsp; Using .chain(), the argument &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;behind the scenes is the collection in the chain. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;[ <a href="https://wiki.roll20.net/API:Events#Campaign_Event" rel="nofollow">https://wiki.roll20.net/API:Events#Campaign_Event</a>... ] &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // selected graphics and undefined -&gt; .compact() -&gt; selected graphics &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .compact() &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // .each() is just like .map(), except what it returns is what it was given. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;Effectively, it gives you the opportunity to do something with each element &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;without changing the list.&nbsp; Using .chain(), the first argument &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;behind the scenes is the collection in the chain. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;[ <a href="https://wiki.roll20.net/API:Events#Campaign_Event" rel="nofollow">https://wiki.roll20.net/API:Events#Campaign_Event</a>... ] &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;selected graphics -&gt; .each() -&gt; selected graphics&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .each(function(t){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // Now that we've filtered down the results to just selected graphics, the real work &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;starts. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // _.has() is an alias for &lt;object&gt;.hasOwnProperty() which simply takes a given object &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;and returns 'true' if it contains the specified property.&nbsp; In this case, I'm using &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;the id of the graphic as the property name, effectively treating the affectedTokens &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;object as a hash. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;[ <a href="https://wiki.roll20.net/API:Events#Campaign_Event" rel="nofollow">https://wiki.roll20.net/API:Events#Campaign_Event</a>... ] &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(_.has(affectedTokens,t.id)){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // If affectedTokens has the id in it, it means effects are already happening for&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;that token, so I want to remove it from the rotation of things having effects. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;delete &lt;object&gt;[&lt;property name&gt;] removes that property from the object, effectively&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;removing this token from the hash. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; delete affectedTokens[t.id]; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // If affectedTokens didn't have the id, it means I'm adding it into rotation. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;I set the property for the graphic's id to be true.&nbsp; I considered &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;storing an object with some timing information instead of true, which &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;is why I made affectedTokens an object instead of a simple array, but &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;the implementation went another way. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; affectedTokens[t.id]=true; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // This creates another Javascript Closure to capture the id and page number and &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;save them for executions of the function I'll be defining within it.&nbsp; Technically, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;the function on each is already a Closure for this function so I probably didn't&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;need another one here.&nbsp; I have a tendency to over Closure, but it's not generally a &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;problem to do so and likely the encapsulation makes it somewhat clearer... maybe. =D &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;'id' and 'p' here are what is passed further down, t.id and t.get('pageid'). &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;This is an Immediately Invoking Function Expression (IIFE or "iffy") &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;[ <a href="https://wiki.roll20.net/API:Events#Campaign_Event" rel="nofollow">https://wiki.roll20.net/API:Events#Campaign_Event</a>... ] &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;Basically, this function gets created and executed all in one, making a Closure to &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;save the parameters for use internally. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (function (id,p){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // Here I'm creating a function that will apply effects to the current graphic and also deal &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;with kicking off the next effect on the current graphic.&nbsp; A different copy of this function &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;will get created for each graphic, safely in it's own Closure. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var effectRunner=function(){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // First, check if the graphic is still in rotation.&nbsp; If a later execution of &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;!randomfx removed the id from affectedTokens, that means I want to stop &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;running the effects.&nbsp; Doing nothing (not going into this if block) means &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;no further timers are set and the function stops getting executed. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;I could have used _.has() here, or used this style above, they are almost equivalent &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;and are interchangeable in almost every circumstance. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(affectedTokens[id]){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // Grab the graphic for this id &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var t = getObj('graphic',id); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // If we found it, we'll do some things (otherwise, it was deleted and we'll stop) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(t){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // With the graphic in hand, we can now apply an effect to it's current position. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; applyEffect(t.get('left'),t.get('top'),p); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // Next we set a timer to run this function again.&nbsp; setTimeout() is asynchronous, so &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;we don't need to worry about stack depth for recursion. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;randomInteger() returns a number between 1 and the supplied argument, like rolling &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;a die.&nbsp; Technically, this means the minimum time between effects is&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;(minSeconds seconds + 1 millisecond), but hopefully you'll forgive that oversight! &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;[ <a href="https://wiki.roll20.net/API:Events#Campaign_Event" rel="nofollow">https://wiki.roll20.net/API:Events#Campaign_Event</a>... ] &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; setTimeout( &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; effectRunner, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; randomInteger((maxSeconds-minSeconds)*1000)+(minSeconds*1000) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // In the case where we didn't find the graphic, it means someone deleted it. For that &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;case, we want to stop the rotation on the graphic be removing it from the&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;collection of affectedTokens. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; delete affectedTokens[id]; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // Start the rotation for this graphic by running the created function.&nbsp; This will trigger the &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;first effect and set a random timer for the next effect. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; effectRunner(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }(t.id,t.get('pageid'))); //&lt; invoking the IIFE to set 'id' and 'p' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // End of the chain.&nbsp; If we wanted the results, we would call &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;.value() to cause _.chain() above to return them.&nbsp; In this &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;case we don't need the results. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp;[ <a href="https://wiki.roll20.net/API:Events#Campaign_Event" rel="nofollow">https://wiki.roll20.net/API:Events#Campaign_Event</a>... ] &nbsp; &nbsp; &nbsp; &nbsp; } // end of the check for !randomfx &nbsp; &nbsp; }); // end of the on('chat:message') }); // end of the on('ready') Here's one that moves a token half their move in a random direction&nbsp; (if they have the overdrive swirly status marker).&nbsp; I call it DurnkMove: // run this function when the api is fully loaded.&nbsp; light going when the light turns green, instead of one of the yellow ones. on('ready',function(){ &nbsp; &nbsp; // tells the javascript engine to make certain sloppy coding practices into errors &nbsp; &nbsp; "use strict"; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; // starts defining things.&nbsp; first is a distance function &nbsp; &nbsp; var distance=function(p1,p2) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // pythagorean theorem for distance between two points &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return Math.sqrt(Math.pow(p2[0]-p1[0],2)+Math.pow(p2[1]-p2[1],2));&nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; // function to determine how long a move chain was &nbsp; &nbsp; &nbsp; &nbsp; moveDistance=function(lastMove){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return _.chain(lastMove.split(/,/))&nbsp; &nbsp;//&lt; break the move string up by commas &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .map(parseFloat)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&lt; turn each thing into a number &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .groupBy(function(v,k){&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;//&lt; break up the array into points &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return Math.round((k-1)/2); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .reduce(function(m,p){&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&lt; walk the points adding the distance between them. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(m.pp){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; m.sum+=distance(m.pp,p); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; m.pp=p; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return m; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; },{ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pp:null, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sum:0 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .value() &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .sum;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;//&lt; return just the sum distance &nbsp; &nbsp; &nbsp; &nbsp; }; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; // run this function when graphics (tokens) are changed &nbsp; &nbsp; on('change:graphic',function(obj,prev){ &nbsp; &nbsp; &nbsp; &nbsp; // setup some variables for later use &nbsp; &nbsp; &nbsp; &nbsp; var page, move, theta,tprime,lprime; &nbsp; &nbsp; &nbsp; &nbsp; // determine if we want to affect this object &nbsp; &nbsp; &nbsp; &nbsp; if( 'objects' === obj.get('layer')&nbsp; &nbsp;/* on the objects layer */ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; && -1 !== obj.get('statusmarkers').indexOf('overdrive')&nbsp; /* has the overdrive symbol */ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; && (obj.get('top') !== prev.top || obj.get('left') !== prev.left)&nbsp; /* has changed position */ &nbsp; &nbsp; &nbsp; &nbsp; ) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // grab the page object based on the page the token is on (for later use bounding) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; page=getObj('page',obj.get('pageid')); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // figure out how far the token moved &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; move = moveDistance(obj.get('lastmove')); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(!move){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // if it was just dragged directly, the lastmove field won't give us a distance, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // use the distance between where it is now and where it was instead &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; move=distance([obj.get('left'),obj.get('top')],[prev.left,prev.top]); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // cut move in half.&nbsp; move/=2&nbsp; is the same as move = move/2&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; move/=2; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // get a random angle in radians &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; theta=(randomInteger(360) * (Math.PI/180)); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // calculate the new top, bounded by half a unit from the top and bottom edge of the map &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; tprime = Math.max(35,Math.min((page.get('height')*70)-35,obj.get('top')+move*Math.sin(theta))); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // calculate the new left, bounded by half a unit from the left and right edge of the map &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; lprime = Math.max(35,Math.min((page.get('width')*70)-35,obj.get('left')+move*Math.cos(theta))); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // set the object's new position &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; obj.set({ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; top: tprime, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; left: lprime &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }); });
1508855262
MSG
Pro
Sheet Author
Thank you very much!&nbsp; Much appreciated!