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

How to Programmatically Undo a Token Move

Here's a simple script to move a token forward 70 pixels (1 default r20 hex): // Move forward 1 hex on('chat:message', function(msg) { if(msg.type == 'api' && msg.selected && msg.content.indexOf('!move 1') == 0){ var selectedObjs = msg.selected; _.each(selectedObjs, function(obj) { if(obj._type == 'graphic'){ var token = getObj('graphic', obj._id); var top = token.get("top"); var left = token.get("left"); var rotation = token.get("rotation"); top = top - Math.cos(rotation * Math.PI / 180) * 70; left = left + Math.sin(rotation * Math.PI / 180) * 70; token.set({top: top, left: left}); }; }); }; }); I notice that while the GM's 'x' can show me where the token started its move, Ctrl+Z (undo) doesn't seem to have any knowledge of (basically) anything in regards to the token's movement via the script. Is there an easy way to track a token's movement history to enable "undo" actions?   For example, player uses my API to move 1 space, turn right 1 facing and move another 1 space <- undo each of these in reverse order.
1760807342
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
Hi omonubi! The Undo function does not recognize changes made by the API, only the player. This is kind of necessary. The API functions like another player. If undo affected API changes, it would also affect changes made by other players. I.e. one person would be able to undo anybody's change, not just their own. So the solution lies in the script itself. If you want the script's movement to be undoable, you must build the undo action into the script. The easiest method would be to have a state object in the script (this is a protected object that persists from session to session), that records each movement. You could create a command that can backtrack a token through those moves. A simple example can be found in the script Map Lock, which locks a graphic in place. It was written before the ability to lock a token with the GUI had been introduced. Map Lock records a given token's settings, and if they are changed, immediately undoes them. Not quite locking, but an example of an immediate undo.
1760820586
timmaugh
Forum Champion
API Scripter
Like Keith said... you have 3 options for storage locations: the state, a game object (like a character, macro, table, handout, etc.), or the closure of the script, itself. The state persists between games, but it is limited in what it can store (and can make the scripts in a game behave unpredictably if it gets filled). A game object might seem like the easiest, but you would have to create the object, then have a way to encode and parse the information you want to store. (You might be storing data for multiple tokens and multiple back-moves...) The script closure gives flexibility in storing the data (using native JS structures), but it will be lost if the sandbox crashes or closes (ie, between sessions). Depending on whether you care if you lose "move-backs" when the sandbox reboots, I would suggest using either the state or the script closure. To use the closure of the script, you'll need to nest your on:chat  function in an on:ready  function (this will also make it so that metascripts can still be useful, if you needed them). Then your data structure can be stored in the on:ready,  at a level parallel to your on:chat: on('ready', () => {   const tokenHistory = {};   on('chat:message', msg => {     // ... chat code here   }); }); Regardless of using the state or the closure of the script, I think I would have the keys of the object be token IDs, and below that "Undo" and "Redo" properties for each token. The value of those properties would be an array where each entry was the data you needed to track for undoing a move. Maybe that's just the left and top properties, or maybe you want the rotation, too (to reset the side facing). The "Undo" array would the "the moves that can be UNDONE"... and the "Redo" array would be the moves that can be REDONE". {   '-M1234567890abcdef': {     Undo:  [  { left: 450, top: 70, rotation: 0 },  { left: 520, top: 70, rotation, 0} ],     Redo: []   },   '-Nabcdef0987654321': {     Undo: [ { left: 376, top: 140, rotation: 45 } ],     Redo: [ { left: 400, top: 220, rotation: 45 } ]   },   // etc... } Decide a number of moves to track (maybe 5 for each token... maybe 10). Every time the token moves, drop the appropriate data into the array. If you commit something to the "Undo" array (e.g., the token makes a new move), empty the "Redo" array, since you've lost that branch point in your backtracking. If you choose to "Redo" a move, move the last entry in the "Redo" array to the "Undo" array. In this way, you give yourself the option to do - undo - redo as much as you like. That would require a command to trigger the undo vs redo. Maybe with handles like: !undo @{target|token_id} !redo @{target|token_id} Then, you could click that button, click the token, and execute the code. You could even drop buttons in chat at that point that would have the token_id already configured in the command line (instead of the targeting statement), saving you having to re-target the same tokens for "undoing" or "redoing":
Thank you, both! I will try the scripting approach. :D 
1760885971
timmaugh
Forum Champion
API Scripter
oh, one other thing... you can add a listener for a graphic move (graphic:change, I think), that can capture human-moves of a token... and you can register a listener for TokenMod to catch TokenMod-initiated moves of tokens... if you want to. I'm not sure if your scope is to capture (and be able to undo/redo) all token moves, or only the moves initiated by your own script. If you want to go the listener route and don't know how to do it, post back and we can help!