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

Adding Token Collisions to a custom token movement script (and additional questions)

1769998327

Edited 1769998572
Hello all,&nbsp; I recently posted a question asking about a specific way to move a targeted token a specified distance directly away (rounded to the nearest grid square) from a selected token using Token-Mod here:&nbsp; <a href="https://app.roll20.net/forum/post/12651687/token-mod-and-moving-targeted-tokens-away-from-a-selected-token" rel="nofollow">https://app.roll20.net/forum/post/12651687/token-mod-and-moving-targeted-tokens-away-from-a-selected-token</a> I had trouble getting things to work with what was suggested by Timmaugh on that thread, so I've spent the last week and a half learning enough JavaScript to write my own script to do what I needed, and I have had success up to the point of integrating TokenCollisions. I'm a little lost on where to start with it, which is my primary question.&nbsp; My script is provided below for reference, and takes the command "!displace &lt;direction&gt; &lt;distance&gt; @{target|token_id}".&nbsp; — Direction is either push or pull. — Distance is parsed as an integer and uses the grid size of the page the tokens are on, so that the distance input is essentially in grid squares. Additional questions I have:&nbsp; — Currently, the way I wrote this requires me to use SelectManager to specify a source token, I think because the msg.selected[0]._id is lost with the @{target|token_id} call dropping selection. I would like this to be able to function without dependency on SelectManager, if possible. — I would love to be able to target multiple tokens at once with this script a la "!displace &lt;direction&gt; &lt;distance&gt; &lt;number of targets&gt;". I think this would involve an extensive rewrite.&nbsp; — The new coordinates the token is moved to are rounded to the nearest multiple of 70 to snap the token to the grid, but I think that wouldn't work on pages where the grid size is not 70.&nbsp; — Any JavaScript-veteran suggestions on cleaning up this script for readability, simplicity, ease-of-use, or fixing unnoticed problems or oversights are exceedingly welcome.&nbsp; on('chat:message', function (msg) { if (msg.type !== 'api') return; if (!msg.content.startsWith('!displace')) return; const args = msg.content.split(/\s+/); &nbsp;&nbsp;&nbsp;&nbsp;// Usage error message if (args.length &lt; 4) { sendChat('Token Displacement', '❌ Usage: !displace &lt;direction&gt; &lt;distance&gt; @{target|token_id}'); return; } &nbsp;&nbsp;&nbsp;&nbsp;//Direction error usage &nbsp;&nbsp;&nbsp;&nbsp;if (args[1] != "push" &amp;&amp; args[1] != "pull") { sendChat('Token Displacement', '❌ Direction must be either push or pull.'); return; &nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;&nbsp;&nbsp;//Distance and Target variables const distanceUnits = parseFloat(args[2]); const targetId = args[3]; &nbsp;&nbsp;&nbsp;&nbsp;//Distance error message if (isNaN(distanceUnits) || distanceUnits &lt;= 0) { sendChat('Token Displacement', '❌ Displacement distance must be a positive number.'); return; } &nbsp;&nbsp;&nbsp;&nbsp;//Selected token error message if (!msg.selected || msg.selected.length !== 1) { sendChat('Token Displacement', '❌ Please select exactly one source token.'); return; } &nbsp;&nbsp;&nbsp;&nbsp;//Source and Target definitions const source = getObj('graphic', msg.selected[0]._id); // SelectManager is currently necessary for overriding Source const target = getObj('graphic', targetId); &nbsp;&nbsp;&nbsp;&nbsp;//Source or Target error message if (!source || !target) { sendChat('Token Displacement', '❌ Invalid source or target token.'); return; } &nbsp;&nbsp;&nbsp;&nbsp;//Grid variable definitions const page = getObj('page', source.get('pageid')); const gridSize = page ? page.get('snapping_increment') * 70 : 70; &nbsp;&nbsp;&nbsp;&nbsp;// Displacement distance variable const displaceDistance = distanceUnits * gridSize; &nbsp;&nbsp;&nbsp;&nbsp;//Source and Target coordinates const sx = source.get('left'); const sy = source.get('top'); const tx = target.get('left'); const ty = target.get('top'); &nbsp;&nbsp;&nbsp;&nbsp;//Difference(Delta) between Target and Source coordinates const dx = tx - sx; const dy = ty - sy; &nbsp;&nbsp;&nbsp;&nbsp;//Distance between Source and Target coordinates const length = Math.hypot(dx,dy); &nbsp;&nbsp;&nbsp;&nbsp;//Same position usage error if (length === 0) { sendChat('Token Displacement', '❌ The target token is in the same position as the source token.'); return; } // Normalized direction vector const nx = dx / length; const ny = dy / length; &nbsp;&nbsp;&nbsp;&nbsp;// Rounding the coordinate changes &nbsp;&nbsp;&nbsp;&nbsp;const xChange = (Math.round((nx * displaceDistance) / 70 ) * 70); &nbsp;&nbsp;&nbsp;&nbsp;const yChange = (Math.round((ny * displaceDistance) / 70 ) * 70); &nbsp;&nbsp;&nbsp;&nbsp;//Direction operator output variables &nbsp;&nbsp;&nbsp;&nbsp;let xFinal, yFinal; &nbsp;&nbsp;&nbsp;&nbsp;if (args[1] === "push") { xFinal = tx + xChange; yFinal = ty + yChange; &nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;&nbsp;&nbsp;if (args[1] === "pull") { xFinal = tx - xChange; yFinal = ty - yChange; &nbsp;&nbsp;&nbsp;&nbsp;} // Apply push target.set({ left: xFinal, top: yFinal }); });
1770141826
timmaugh
Roll20 Production Team
API Scripter
First, I'm sorry you didn't get my suggestion to work. It is working in my game, and you hadn't posted back to the thread, so I figured it was working for you. Should you want to return to that solution, let me know. Next, you really have a finite set of options of how to approach token involvement in the script: 1) select the source of the radiation (via SelectManager) and target the affected (moving) tokens. 2) select the affected (moving) tokens, and reference the source token by name. 3) select tokens automatically based on proximity Number 1 is really cumbersome because you don't know (at the time of building your command line) how many tokens you will need to target, and therefore how many targeting statements to include. However, if you wanted to go this direction, you could skip SelectManager and instead use my Assemble &nbsp;script... which would give you an automated process to build your "targeted tokens" set (with however many targeting statements you need), and automatically reference that set of tokens in your script's command line. Then your targeting statements would be separate from your command, and your selected token (presumably the source/radiation token in this case) would be preserved for your script to find on the message. Number 2 is how the solution works that I gave you in the other thread. In that thread, I used a forselected handle to iterate over the tokens with individualized math. In your case, you would want to iterate over them to move them the correct individual distance/direction. Number 3 can get code-beefy or it can get interface-beefy. The code-beefy version of it would be to use something like a quad tree processing of the tokens on your page to limit the number of permutations required to calculate. The Aaron has a Roll20 version of that which I believe is available on his repo. I used that and wrote an interface to build a "Ranger" metascript... basically a way to quickly gather, via a metascript tag, the tokens within such-and-such range of a source token. Unfortunately, I hadn't yet added ellipse path intersection to the script when Roll20 released Jumpgate and introduced pathv2 objects... so I haven't gotten back to it to finish it. Since you likely only need to collect tokens, you might be able to leverage that build more easily/directly. Alternatively, you could forego the code-heavy solution and give yourself a menu. First collect all of the tokens that are within "such-and-such" distance of your source token (this is the part that quadtree would minimize the calculations of). Your "distance" might be the length of the attack range? Or the limit of the maximum "push"? Whatever it is, gather those tokens and iterate over them to produce lines in a default template output. Give each token a button that has a command line for your script. The button doesn't need to have already done the math; it just needs to run a command line for your script that will supply the "token to move", the "source token" so you can calculate direction, and some "amount of force" metric. Then your attack workflow would be: select source token run menu-producing command with "range" metric, receive template menu with buttons for each potentially affected token click button for each token you wish to move/affect as a part of this repulsion (But I would suggest, if you're already willing to commit to this level of process, that you investigate the Assemble suggestion mentioned above... it is less work, less computation, less individual-token-focused, and probably quicker to implement and to utilize).
1771021892

Edited 1771021924
timmaugh said: First, I'm sorry you didn't get my suggestion to work. It is working in my game, and you hadn't posted back to the thread, so I figured it was working for you. Should you want to return to that solution, let me know. ... (But I would suggest, if you're already willing to commit to this level of process, that you investigate the Assemble suggestion mentioned above... it is less work, less computation, less individual-token-focused, and probably quicker to implement and to utilize). Hi Timmaugh, Sorry for the delayed response. I think your Assemble script would work pretty well for the case I'm trying to use this for, so I think I will pursue editing/rewriting my script to take multiple targets at once, to be called from the appropriately Assembled list on a character ability. Thank you for providing that link.&nbsp; I think I will also edit the script to have a targeted source token, or at least a command-entered source token id, so that it is more adaptable for general use and less case specific.&nbsp; Both of those aspects are coding concepts that I am confident I can figure out with enough trial and error, but I would love it if someone could explain how to set up the Token Collisions functions inside my script, at least the 'getFirstCollisions...' function, so that I can define variables to be used as alternate coordinates for stopping token movement early in collision cases.&nbsp; Thanks in advance for any continued help that gets provided!&nbsp;
1771421820
timmaugh
Roll20 Production Team
API Scripter
To be clear what you're asking for, Rock... are you looking for a way to determine "what is the first stopping point along this path?" ...OR... "is there any stopping point along this path within X distance?" ...OR... "what are the tokens within X distance of this source point?" ...or something else?
timmaugh said: To be clear what you're asking for, Rock... are you looking for a way to determine "what is the first stopping point along this path?" ...OR... "is there any stopping point along this path within X distance?" ...OR... "what are the tokens within X distance of this source point?" ...or something else? The answer is "What is the first stopping point along this path?" With my end goal for the script including a moved token being stopped by other tokens and dynamic lighting walls, I would like to use the Token Collisions function(s) to report the coordinates at which a moved token collides with a wall or other token so that I can use those coordinates as new variables along with additional math to create alternate ending coordinates (rounded to the nearest grid square) for the moved token in case something collidable is in its path.&nbsp;
1771526546
timmaugh
Roll20 Production Team
API Scripter
Ah! You mean the script "Token Collisions"? OK, in that case, it looks like that script returns a class with some static methods attached. Since they're static, you wouldn't need to instantiate a new object of that type. You'd just reference TokenCollisions, then run the appropriate function and give it the parameters it needs. I think that would look like (air-coded, and from a quick scan through Token Collisions): let srcToken = // ... code to get the moving token let potentialObstacles = // ... code to derive the potential obstacles let waypoint = // ...code to derive a waypoint, which is an array of 2 pts denoting start and end of the travel: [ [x1,y1], [x2,y2] ] let collisionObj = TokenCollisions.getFirstCollision(srcToken, potentialObstacles, waypoint); The returned object will be whichever item out of the "others" parameter was the first item to be collided with. Some caveats... beside the fact that this came from air-coding a quick example after an equally quick reading of Token Collisions... 1) I don't know that Token Collisions takes into account the initial rotation of the moving token; I'll have to read closer and/or engage with The Aaron (who wrote the script) 2) this will not return the coordinates of the collision, but the object that was collided with. Further math (and possibly ray casting) would need to establish the coordinates: ...if the object being moved is not a circle, then determine the rotation (because for you placing the token, you *will* care about rotation and proximity to the collision obstacle) ...get the actual points of the object's corners as they sit (rotated) into an array (trigonometry) ...segment-ize the obstacle into an array of segments ...reduce() over the pt / segment combinations, using the vector of travel to get you a slope to the ray, to see a) if they intersect, and b) how far away they are so that you always keep the shortest value... this will be the point that intersects that obstacle first ...having that corner and that intersection point, apply the transformation of "that-corner-given-the-object's-rotation to the left/top location of the object" to reverse engineer the NEW left/top location given the intersection point. (That is, if the intersecting corner, as the object sits rotated, is normally 20 pixels less than the "top" value and 56 pixels more than the "left" property, then when you get the intersection point you'll want to add 20 pixels to the top (y) and subtract 56 pixels from the left (x) to get the new "location" 3) This doesn't address the "what potential obstacles are there?" that would be a part of gathering the objects to feed to the second argument. Maybe you only care about paths, and so could minimize the amount of objects you have to iterate over (hoping you'd have less paths than total objects on the page), which would simply let you collect all of the paths as an array. Or maybe you also want to implement The Aaron's QuadTree script and use your "direction of travel" vector to determine how to query the potential obstacles out of the tree... 4) I'm not sure if Token Collisions has been updated to work with the pathv2 objects that were introduced with Jumpgate... but my quick reading says that it outsources the handling of paths to PathMath, and I am pretty sure that PathMath *was* updated to work with pathv2s. So... maybe this one is nothing, but I'm mentioning it just in case you get some odd returns. I'll try to ping Aaron to get him to weigh in. I believe he was the one that updated PathMath.