Skip to content

Instantly share code, notes, and snippets.

@Cazra
Last active December 18, 2015 10:49
Show Gist options
  • Save Cazra/5771375 to your computer and use it in GitHub Desktop.
Save Cazra/5771375 to your computer and use it in GitHub Desktop.
roll20 Marching Order script.
/**
* A set of chat commands used to specify a marching order.
* When a token moves, the one behind it in the marching order will move to its
* former location recursively.
*
* The following commands are available:
* !follow [myTokName], [leaderName] Tells your token specifed by myTokName
* to follow another token specified by leaderName.
* !unfollow [myTokName] Makes your token stop following other tokens.
* You can also have a token stop following another token just by
* moving it. Any tokens behind yours will still follow yours.
* !march [list of token names separated by commas] GM-only:
* Specify a marching order for a list of tokens from front to back.
* !unmarch [leaderName] GM-only:
* Tell a token and all of the tokens following behind it to stop
* following each other.
* !clearFollow Clears the following link data for all tokens on the
* current page.
*/
/** Clears all following data for tokens on the current page. */
var clearFollowCmd = function(msg) {
var curPageID = findObjs({_type: "campaign"})[0].get("playerpageid");
var characters = findObjs({_type: "graphic", layer: "objects", _pageid: curPageID});
for(var i in characters) {
var character = characters[i];
_unfollowCmd(character);
}
sendChat(msg.who, "clear follow");
}
/** Command one of your tokens to follow another token. */
var followCmd = function(msg, myTokenName, prevTokenName) {
var myToken = getToken(myTokenName);
if(!myToken) {
sendChat(msg.who, "/w " + msg.who + " " + myTokenName + " not found.")
return;
}
var prevToken = getToken(prevTokenName);
if(!prevToken) {
sendChat(msg.who, "/w " + msg.who + " " + prevTokenName + " not found.")
return;
}
var myPlayer = getObj("player",msg.playerid);
// You may only tell a token you control to follow another token.
if(isTokenControlledBy(msg.playerid, myTokenName)) {//(myPlayer && myToken.get("controlledby").indexOf(myPlayer.get("_id")) !== -1) || msg.who.indexOf("(GM)") !== -1) {
_followCmd(prevToken, myToken);
sendChat(msg.who, "/em : " + myTokenName + " is following " + prevTokenName);
}
else {
sendChat(msg.who, "/w " + msg.who + " " + myTokenName + " does not belong to you!");
}
}
/** Helper function: Makes one token follow another token. */
var _followCmd = function(prev, cur) {
if(!prev || !cur) {
return;
}
// unbind all of cur's following links.
_unfollowCmd(cur);
var next = prev.nextToken;
cur.prevToken = prev;
cur.nextToken = next;
prev.nextToken = cur;
if(next) {
next.prevToken = cur;
}
}
/** Tells all of your character tokens to stop following other tokens. */
var unfollowCmd = function(msg, myTokenName) {
var myToken = getToken(myTokenName);
if(!myToken) {
sendChat(msg.who, "/w " + msg.who + " " + myTokenName + " not found.")
return;
}
var myPlayer = getObj("player",msg.playerid);
// You may only tell a token you control to stop following another token.
if(isTokenControlledBy(msg.playerid, myTokenName)) {//(myPlayer && myToken.get("controlledby").indexOf(myPlayer.get("_id")) !== -1) || msg.who.indexOf("(GM)") !== -1) {
_unfollowCmd(myToken);
}
else {
sendChat(msg.who, "/w " + msg.who + " " + myTokenName + " does not belong to you!");
}
sendChat(msg.who, "/em stepped out of marching order. ");
}
/** Helper method for making a character token stop following another token and stopped being followed by another token. */
var _unfollowCmd = function(token) {
if(token.prevToken) {
token.prevToken.nextToken = token.nextToken;
}
if(token.nextToken) {
token.nextToken.prevToken = token.prevToken;
}
token.prevToken = null;
token.nextToken = null;
}
/** Create a marching order from a comma-separated list of token names. */
var marchCmd = function(msg, targets) {
var targetsArr = targets.split(",");
var tokens = new Array();
// make the tokens follow in the order given.
var prev = null;
for(var i in targetsArr) {
var token = getToken(trimString(targetsArr[i]));
if(!token) {
sendChat(msg.who, "/w gm Cannot march. Bad token name: " + targetsArr[i]);
}
if(prev) {
_followCmd(prev, token);
}
prev = token;
}
sendChat(msg.who, "/w gm Marching " + targets);
}
/** Disband a marching order by specifying its leader. */
var unmarchCmd = function(msg, leader) {
var token = getToken(leader);
if(!token) {
sendChat(msg.who, "/w gm Cannot unmarch. Bad token name: " + leader);
}
while(token.nextToken) {
var tokenNext = token.nextToken;
_unfollowCmd(token);
token = tokenNext;
}
sendChat(msg.who, "/w gm Disbanded " + leader + "'s marching line. ");
}
/** Interpret the chat commands. */
on("chat:message", function(msg) {
var cmdName;
var msgTxt;
cmdName = "!clearFollow";
msgTxt = msg.content;
if(msg.type == "api" && msgTxt.indexOf(cmdName) !== -1) {
clearFollowCmd(msg);
}
cmdName = "!follow ";
msgTxt = msg.content;
if(msg.type == "api" && msgTxt.indexOf(cmdName) !== -1) {
var targetName = msgTxt.slice(cmdName.length);
if(targetName.indexOf(",") == -1) {
sendChat(msg.who, "/w " + msg.who + " must specify two comma-separated token names for !follow command.")
}
else {
var targets = targetName.split(",");
var me = trimString(targets[0]);
var infront = trimString(targets[1]);
followCmd(msg, me, infront);
}
}
cmdName = "!unfollow ";
msgTxt = msg.content;
if(msg.type == "api" && msgTxt.indexOf(cmdName) !== -1) {
var targetName = msgTxt.slice(cmdName.length);
unfollowCmd(msg, targetName);
}
// GM only commands
if(msg.who.indexOf("(GM)") === -1) {
return;
}
cmdName = "!march ";
msgTxt = msg.content;
if(msg.type == "api" && msgTxt.indexOf(cmdName) !== -1) {
var targets = msgTxt.slice(cmdName.length);
marchCmd(msg, targets);
}
cmdName = "!unmarch ";
msgTxt = msg.content;
if(msg.type == "api" && msgTxt.indexOf(cmdName) !== -1) {
var targetName = msgTxt.slice(cmdName.length);
unmarchCmd(msg, targetName);
}
});
/** Do the marching order effect when the leader tokens move! */
on("change:graphic", function(obj, prev) {
var prevToken = obj;
var curToken = prevToken.nextToken;
prevToken.prevLeft = prev["left"];
prevToken.prevTop = prev["top"];
// We stepped out of line. Stop following the guy in front of us.
if(prevToken.prevToken) {
prevToken.prevToken.nextToken = null;
prevToken.prevToken = null;
}
// move everyone to the previous position of the token in front of them.
while(curToken) {
curToken.prevLeft = curToken.get("left");
curToken.prevTop = curToken.get("top");
curToken.set("left",prevToken.prevLeft);
curToken.set("top",prevToken.prevTop);
prevToken = curToken;
curToken = curToken.nextToken;
// avoid cycles.
if(curToken == obj) {
return;
}
}
});
////// Utility functions:
/** Returns true iff the player with the specified ID controls the token with the specified name. */
var isTokenControlledBy = function(playerID, tokenName) {
return (getControllers(tokenName).indexOf(playerID) !== -1);
}
/** Returns the list of player IDs controlling the specified token. */
var getControllers = function(tokenName) {
var token = getToken(tokenName);
// The token doesn't exist.
if(token === null) {
return [];
}
// Return a list of all player ids controlling this token, including GMs.
else {
var character = getCharacterOf(token);
if(character === null) {
return getGMs();
}
else {
return character.get("controlledby").split(",");
}
}
}
/** Returns the Character that controls the specified token. */
var getCharacterOf = function(token) {
var cID = token.get("represents");
if(cID === "") {
return null;
}
else {
return getObj("character", cID);
}
}
/**
* Gets a token on the current map by name.
*/
var getToken = function(tokenName) {
var curPageID = findObjs({_type: "campaign"})[0].get("playerpageid");
return findObjs({_type: "graphic", layer: "objects", _pageid: curPageID, name: tokenName})[0];
}
/**
* Gets the list of the player IDs for all GMs of the campaign. (Just me)
*/
var getGMs = function() {
var result = new Array();
// populate result with the player IDs of the GMs for your campaign.
return result;
}
/** Trims a string */
var trimString = function(src) {
return src.replace(/^\s+|\s+$/g, '');
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment