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

[Script] Token Patrol

With this script you will be able to set patrols for various tokens. Tokens will only move on the page where your player banner is located. Tokens can move in a closed loop patrol, or backtrack previous patrol points. The length of the time between the movements can also be modified. For a better overview, you can also toggle a visual aid. You can save and load these patrols. Please note that the save function overrides the currently saved patrol, so always load first, then set a new patrol and then save. Otherwise your previous saved patrols will be gone. I would appreciate feedback, weather to the functionality or the code. Github link:&nbsp; <a href="" rel="nofollow"></a>

Edited 1664363286
Sheet Author
API Scripter
Hi Patrick, I just had a very quick look over your script, and a couple of things - 1. You've got most of your functions and variables exposed in the top scope. This is problematic in the Roll20 sandbox, because all scripts are concatenated before being executed - every single Mod script is pasted into one big fat string and executed in the same context. It's extra bad with the use of the var keyword (variable hoisting) and generic variable names like 'selectedToken' - there's very good opportunity for collisions and mix-ups there. The most-used pattern to solve this on Roll20 is Revealing Module Pattern - there's a fair amount of explanation here , and some more here . It essentially makes your script scope private, apart from anything you choose to expose via the return { } object at the bottom. This is generally wrapped in an IIFE so it executes as soon as it's declared (the IIFE part is why there's extra parentheses around the function). As Jakob points out in that first link, you don't even need to name the outer function unless you need to expose some internal methods or functions. If your script functions 100% off chat events, you can just wrap your script in an anonymous IIFE: const selected = null; // This will cause a crash if any other script author has used it in their outer scope (() =&gt; { // or '(function() {' if you prefer normal function syntax over arrow syntax const selected = 'stuff'; // This is only visible to your script, and will cause no conflict with anyone else's on('ready', () =&gt; { on('chat:message', (msg) =&gt; { sendChat('', `${msg.who} posted a message.`); }); }); })(); This all protects scripts from interfering with each other - everything inside that outer function is private from other sandbox scripts. 2. On the same note, I'd probably try to come up with something a little longer than !tp for your keyword - there's a fair chance someone's used it as a keyword or alias for another script, so it could end up firing multiple scripts for someone with a big script load. It might be fine, but I probably wouldn't chance it with the number of scripts floating around. 3. The use of &amp;{template:desc} means the menu will only work for the Roll20 5e sheet, or any other sheet that coincidentally has a template with the same name. &amp;{template:default} is the only universal roll template. Of course, this is fine if you only wanted the script to work with 5e, but maybe chuck that info in the top post if that was the plan. Looks good otherwise!
Hi Oosh, Thanks for the awsome feedback. I really appreciate&nbsp;that. 1) I fixed the scope problem by wrapping my code in the IIFE. 2) Yeah i was wondering that too, so i changed it to !tokPat (should be better?). 3) I was not aware that&nbsp; &amp;{template:desc} only works on the 5e sheet, i changed that too. The updated code is now available.
API Scripter
The DiscreteWhisper script actually includes a method for users to "move" that script's handle. The script relies on a very simple: !w as the default handle, so if you need to move it, you can. It stores the available handles for the script in the state object under the project's name: const apiproject = 'DiscreteWhisper'; //... later... state[apiproject].apihandles = ['w', 'discrete']; Then, when it's time to test whether the script should do anything with the message, I test both the project name and &nbsp;all of those handles by building them into new regular expressions: &nbsp; &nbsp; const handleInput = (msg_orig) =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; if (msg_orig.type !== 'api') return; &nbsp; &nbsp; &nbsp; &nbsp; if (!(new RegExp(apiproject)).test(msg_orig.content)) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (!state[apiproject].apihandles.reduce((a, h) =&gt; { return new RegExp(`^!${h}\\b`).test(msg_orig.content) ? true : a; }, false)) return; &nbsp; &nbsp; &nbsp; &nbsp; } I check the project name, too, because like I said I give the user a way to add or remove handles for this script. If they remove all the handles, I still want to be able to reach the script somehow... and since I've named my revealing module pattern IIFE the same as my apiproject variable: const DiscreteWhisper = (() =&gt; { &nbsp; &nbsp; // ... &nbsp; &nbsp; const apiproject = 'DiscreteWhisper'; &nbsp; &nbsp; // ... ...I am pretty guaranteed that it will work: if someone else steps on that particular "const" declaration, they'll get an error. Since the new regex I build for each of the registered handles (in the state) looks for word boundaries, I can offer ways to add/delete handles. I use a hash and then keywords: &nbsp; &nbsp; &nbsp; &nbsp; let args = msg.content.split(/\s+--/); &nbsp; &nbsp; &nbsp; &nbsp; let handleArg = args.shift(); &nbsp; &nbsp; &nbsp; &nbsp; if (playerIsGM(msg.playerid)) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let handlerx = /^!.*#(remapi(?=\|(.+))|addapi(?=\|(.+))|getapi|silent|report)\|?(.*)/g; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // FROM&nbsp; &nbsp; : !apihandle#option|hande1|handle2 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // group 1 : option &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // group 4 : handle1|handle2 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let handle = handlerx.exec(handleArg); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (handle) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; switch (handle[1]) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case 'remapi': &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; remHandle(handle[4]); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case 'addapi': &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; addHandle(handle[4]); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case 'getapi': &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; getHandles(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // ... &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; default: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; } Anyway, just pointing out a way you could offer !tp as a handle, but give the user a way to remove that particular handle if they run into a collision.

Edited 1664405430
Sheet Author
API Scripter
Wow.... yeah !w is a bold move :) Hmmmm.... there's must be a script starting with 'r' that needs writing.... maybe another called 'token-mode'?
Hi Timmaugh, thanks for the code sniped. I just changed the handle to&nbsp; !tokPat,&nbsp; that should hopefully do the trick.