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

Marching order script stopped working?

My last two sessions it just was dead. I deleted and reloaded it, but no joy.
I don't know if the script author is supporting that MOD anymore. The last update was in 2020.
☹️ I really liked that Mod, it helped keep my players from running in five directions, then complaining that they were lost. Oh well, C'est la vie.
1789150881

Edited 1789151075
I have one player that insists on having his PC wander off on his own. Often that PC gets in a predicament far from help. He never seem to learn - and this is not a young or inexperienced player, either. al e. said: ☹️ I really liked that Mod, it helped keep my players from running in five directions, then complaining that they were lost. Oh well, C'est la vie.
Some people just love chaos
Is this the error you're getting?  "MarchingOrder ERROR: 'set' on proxy: trap returned falsish for property 'followers'" "TypeError: 'set' on proxy: trap returned falsish for property 'followers'     at _initFollowLeader (apiscript.js:44871:22)     at Object.follow (apiscript.js:44748:5)     at _cmdFollow (apiscript.js:45317:19)     at apiscript.js:45396:9     at file:///home/node/d20-api-server/src/pubsub2.js:68:9     at Map.forEach (<anonymous>)     at PubSub.publish (file:///home/node/d20-api-server/src/pubsub2.js:66:35)     at Timeout.processChatQueue [as _onTimeout] (file:///home/node/d20-api-server/api.js:1583:14)     at listOnTimeout (node:internal/timers:605:17)     at process.processTimers (node:internal/timers:541:7)"
something like that.
For Marching order, could you do something like this? 1. Disable the existing MarchingOrder scripts, including any version installed through the Roll20 Mod library. Otherwise, the old code may continue producing the same error. 2. Create a new script and paste this in? /*  * MarchingOrder - Proxy-Safe Edition  *  * Commands:  * !follow Token, Leader  * !unfollow Token  * !march Leader, Token2, Token3  * !unmarch Leader  * !clearFollow  *  * Tokens follow the previous positions of the  * tokens directly ahead of them.  *  * No custom properties are written to Roll20 objects.  */ var MarchingOrderSafe = MarchingOrderSafe || (function () {     'use strict';     var VERSION = '2.0';     var KEY = 'MarchingOrderSafe';     // Tracks movement generated by this script.     // This is intentionally not persisted.     var pendingMoves = {};     function data() {         if (!state[KEY]) {             state[KEY] = {                 parent: {},                 child: {}             };         }         return state[KEY];     }     function pageID() {         return Campaign().get('playerpageid');     }     function tokenByName(name) {         var matches = findObjs({             _type: 'graphic',             _subtype: 'token',             _pageid: pageID(),             layer: 'objects',             name: name.trim()         });         return matches.length ? matches[0] : null;     }     function tokenByID(id) {         return id ? getObj('graphic', id) : null;     }     function whisper(msg, text) {         var target = playerIsGM(msg.playerid)             ? 'gm'             : '"' + msg.who.replace(/\s+\(GM\)$/, '') + '"';         sendChat('MarchingOrder', '/w ' + target + ' ' + text);     }     function canControl(playerID, token) {         if (playerIsGM(playerID)) {             return true;         }         if (!token) {             return false;         }         var controllers = [];         var direct = token.get('controlledby') || '';         if (direct) {             controllers = controllers.concat(direct.split(','));         }         var characterID = token.get('represents');         var character = characterID             ? getObj('character', characterID)             : null;         if (character) {             var characterControllers =                 character.get('controlledby') || '';             if (characterControllers) {                 controllers = controllers.concat(                     characterControllers.split(',')                 );             }         }         return controllers.indexOf('all') !== -1 ||             controllers.indexOf(playerID) !== -1;     }     // Remove a token from a marching line,     // connecting the tokens on either side.     function unlink(id) {         var d = data();         var ahead = d.parent[id];         var behind = d.child[id];         if (ahead) {             if (behind) {                 d.child[ahead] = behind;             } else {                 delete d.child[ahead];             }         }         if (behind) {             if (ahead) {                 d.parent[behind] = ahead;             } else {                 delete d.parent[behind];             }         }         delete d.parent[id];         delete d.child[id];     }     // Insert follower immediately behind leader.     function link(leaderID, followerID) {         if (leaderID === followerID) {             return false;         }         var d = data();         // Detach the follower from any previous line.         unlink(followerID);         var nextID = d.child[leaderID];         d.child[leaderID] = followerID;         d.parent[followerID] = leaderID;         if (nextID && nextID !== followerID) {             d.child[followerID] = nextID;             d.parent[nextID] = followerID;         }         return true;     }     function follow(msg, followerName, leaderName) {         var follower = tokenByName(followerName);         var leader = tokenByName(leaderName);         if (!follower || !leader) {             whisper(msg, 'One or both tokens were not found.');             return;         }         if (!canControl(msg.playerid, follower)) {             whisper(msg, 'You do not control ' + followerName + '.');             return;         }         if (!link(leader.id, follower.id)) {             whisper(msg, 'A token cannot follow itself.');             return;         }         sendChat('MarchingOrder',             '/em ' + followerName +             ' is following ' + leaderName + '.'         );     }     function unfollow(msg, name) {         var token = tokenByName(name);         if (!token) {             whisper(msg, 'Token not found: ' + name);             return;         }         if (!canControl(msg.playerid, token)) {             whisper(msg, 'You do not control ' + name + '.');             return;         }         unlink(token.id);         sendChat('MarchingOrder',             '/em ' + name + ' stepped out of marching order.'         );     }     function march(msg, names) {         var tokens = [];         var seen = {};         // Validate the full order first.         for (var i = 0; i < names.length; i++) {             var name = names[i].trim();             var token = tokenByName(name);             if (!token) {                 whisper(msg, 'Token not found: ' + name);                 return;             }             if (seen[token.id]) {                 whisper(msg, 'Duplicate token: ' + name);                 return;             }             seen[token.id] = true;             tokens.push(token);         }         if (tokens.length < 2) {             whisper(msg, 'Specify at least two tokens.');             return;         }         // Remove existing links for all participants.         tokens.forEach(function (token) {             unlink(token.id);         });         // Build the new order from front to back.         for (var j = 1; j < tokens.length; j++) {             link(tokens[j - 1].id, tokens[j].id);         }         whisper(msg, 'Marching: ' + names.join(', '));     }     function unmarch(msg, leaderName) {         var leader = tokenByName(leaderName);         if (!leader) {             whisper(msg, 'Token not found: ' + leaderName);             return;         }         var d = data();         var currentID = leader.id;         var visited = {};         while (currentID && !visited[currentID]) {             visited[currentID] = true;             var nextID = d.child[currentID];             delete d.parent[currentID];             delete d.child[currentID];             currentID = nextID;         }         whisper(msg,             'Disbanded ' + leaderName + "'s marching line."         );     }     function clearFollow(msg) {         var d = data();         var tokens = findObjs({             _type: 'graphic',             _subtype: 'token',             _pageid: pageID()         });         tokens.forEach(function (token) {             unlink(token.id);         });         whisper(msg, 'Marching orders cleared on current page.');     }     // Called only when a token actually changes position.     function handleMovement(obj, prev) {         if (obj.get('layer') !== 'objects') {             return;         }         var id = obj.id;         var oldLeft = prev.left;         var oldTop = prev.top;         var newLeft = obj.get('left');         var newTop = obj.get('top');         if (oldLeft === newLeft && oldTop === newTop) {             return;         }         // Ignore movement generated by this script.         // Followers should not independently break         // formation when the API moves them.         var pending = pendingMoves[id];         if (pending &&             pending.left === newLeft &&             pending.top === newTop) {             delete pendingMoves[id];             return;         }         delete pendingMoves[id];         var d = data();         // Manual movement of a follower detaches it         // from the person ahead, but its own followers         // remain attached.         var aheadID = d.parent[id];         if (aheadID) {             delete d.child[aheadID];             delete d.parent[id];         }         var currentID = id;         var targetLeft = oldLeft;         var targetTop = oldTop;         var visited = {};         visited[id] = true;         // Each follower moves to the previous position         // of the token immediately ahead.         while (d.child[currentID]) {             var nextID = d.child[currentID];             // Safety against corrupted circular links.             if (visited[nextID]) {                 log('MarchingOrder: Circular link detected.');                 break;             }             visited[nextID] = true;             var follower = tokenByID(nextID);             if (!follower) {                 delete d.child[currentID];                 delete d.parent[nextID];                 break;             }             var followerOldLeft = follower.get('left');             var followerOldTop = follower.get('top');             // Record the generated movement before             // modifying the Roll20 token.             if (followerOldLeft !== targetLeft ||                 followerOldTop !== targetTop) {                 pendingMoves[nextID] = {                     left: targetLeft,                     top: targetTop                 };                 follower.set({                     left: targetLeft,                     top: targetTop                 });             }             targetLeft = followerOldLeft;             targetTop = followerOldTop;             currentID = nextID;         }     }     function handleChat(msg) {         if (msg.type !== 'api') {             return;         }         var content = msg.content.trim();         var match = content.match(/^!(\S+)(?:\s+([\s\S]*))?$/);         if (!match) {             return;         }         var command = match[1].toLowerCase();         var args = (match[2] || '').trim();         if (command === 'follow') {             var pair = args.split(',');             if (pair.length !== 2) {                 whisper(msg,                     'Usage: !follow Follower, Leader'                 );                 return;             }             follow(msg, pair[0].trim(), pair[1].trim());             return;         }         if (command === 'unfollow') {             if (!args) {                 whisper(msg, 'Usage: !unfollow Token');                 return;             }             unfollow(msg, args);             return;         }         // Remaining commands are GM-only.         if (!playerIsGM(msg.playerid)) {             return;         }         if (command === 'march') {             march(msg, args.split(','));         }         if (command === 'unmarch') {             unmarch(msg, args);         }         if (command === 'clearfollow') {             clearFollow(msg);         }     }     function handleDestroy(obj) {         unlink(obj.id);         delete pendingMoves[obj.id];     }     on('ready', function () {         data();         log('MarchingOrder Safe v' + VERSION + ' ready.');     });     on('chat:message', handleChat);     on('change:graphic', handleMovement);     on('destroy:graphic', handleDestroy);     return {         version: VERSION     }; }()); --- Commands remain unchanged Command Action !follow Nighthawk, Hyperion Nighthawk follows Hyperion !unfollow Nighthawk Nighthawk steps out of line !march Hyperion, Nighthawk, Whizzer Establishes the entire marching order !unmarch Hyperion Disbands Hyperion's line !clearFollow Clears marching orders on the current page
I will give it a try when I get a chance, thanks
1791468225
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
I got that reference.
keithcurtis said: I got that reference. I was not sure what you meant there, I will try it, today I am populating rumors and setting up NPCs for the next two nights of games.  I find all mods have a learning curve and I never met a rabbit hole that I will not dive down.
1791473651
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
al e. said: keithcurtis said: I got that reference. I was not sure what you meant there, I will try it, today I am populating rumors and setting up NPCs for the next two nights of games.  I find all mods have a learning curve and I never met a rabbit hole that I will not dive down. Sorry, I was referring to David Gallaher's choice of characters in their example. Responding to an in-joke.
No problem, I was trying desperately to find what was sarcastic about my reply.
1791482245
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
al e. said: No problem, I was trying desperately to find what was sarcastic about my reply. I should have quoted. Sorry for any incidental angst.