/** * A script with a command for specifying tokens to follow other tokens. * Simply select the tokens in the marching order and enter the "!follow" * commandwith either a direction for the marching order of the selected tokens * or the name of a token for the selected tokens to follow behind. * * E.G. "!follow west" will make the selected tokens follow each other in order * from east to west, with the westmost token being the leader and the eastmost * token being the caboose. * Alternatively if we want the selected tokens to follow a character named * "Drizt", enter the command "!follow Drizt". * * !follow [north|south|east|west|name of leader token] * * To make a token stop following the token in front of it, just manually move * the token anywhere. */ var MarchingOrder = (function() { var MENU_CMD = '!showMarchingOrderMenu'; var FOLLOW_CMD = '!marchingOrderFollow'; /** * Makes tokens follow each other in order of some compass direction. * @param {Graphic[]} tokens * @param {String} direction */ var _cmdFollowDirection = function(tokens, direction) { tokens.sort(function(a,b) { var aX = parseFloat(a.get("left")); var bX = parseFloat(b.get("left")); var aY = parseFloat(a.get("top")); var bY = parseFloat(b.get("top")); if(direction === "north") return (aY - bY); else if(direction === "south") return (bY - aY); else if(direction === "west") return (aX - bX); else // east return (bX - aX); }); setMarchingOrder(tokens); }; /** * Makes the tokens follow a token with the specified name. * The tokens behind the leader form a line in no particular order. * @param {Graphic[]} tokens * @param {String} leaderName */ var _cmdFollowNamedToken = function(tokens, leaderName) { var curPageID = Campaign().get("playerpageid"); var leader = findObjs({ name: leaderName, _pageid: curPageID })[0]; if(leader) { tokens.unshift(leader); setMarchingOrder(tokens); } }; /** * Makes a token's followers move to the token's previous position. * @param {Graphic} leader */ var _doFollowMovement = function(leader) { var follower = leader.follower; follower.prevLeft = follower.get("left"); follower.prevTop = follower.get("top"); follower.prevRotation = follower.get("rotation"); follower.set("left",leader.prevLeft); follower.set("top",leader.prevTop); follower.set("rotation", leader.prevRotation); }; /** * Makes a token follow another token. * @param {Graphic} leader * @param {Graphic} follower */ var follow = function(leader, follower) { if(!leader || !follower) return; // unbind all of follower's following links. unfollow(follower); var prevFollower = leader.follower; follower.leader = leader; follower.follower = prevFollower; leader.follower = follower; if(prevFollower) { prevFollower.leader = follower; } }; /** * Gets the argument of the API command. * @param {Msg} msg * @return {String} */ var _getCmdArg = function(msg) { var msgTxt = msg.content; var index = msgTxt.indexOf(' '); if(index >= 0) { index++; return msgTxt.substring(index); } else { return null; } }; /** * Returns a list of the tokens selected by the player. * @param {Msg} msg * @return {Graphic[]} */ var _getSelectedTokens = function(msg) { var tokens = []; var curPageID = Campaign().get("playerpageid"); var selected = msg.selected; // Not actually tokens. for(var i=0; i'; // Cardinal directions (GM only) if(playerIsGM(playerId)) { actionsHtml += '
March in order:
'; actionsHtml += '
'; actionsHtml += ''; actionsHtml += ''; actionsHtml += ''; actionsHtml += '
[North](' + FOLLOW_CMD + ' north)
[West](' + FOLLOW_CMD + ' west)[East](' + FOLLOW_CMD + ' east)
[South](' + FOLLOW_CMD + ' south)
'; } html += _showMenuPanel('Menu Actions', actionsHtml); _whisper(who, html); } function _showMenuPanel(header, content) { var html = '
'; html += '
' + header + '
'; html += '
' + content + '
'; html += '
'; return html; } /** * Makes a token stop following other tokens. * @param {Graphic} token */ var unfollow = function(token) { if(token.leader) { token.leader.follower = token.follower; } if(token.follower) { token.follower.leader = token.leader; } token.leader = null; token.follower = null; }; /** * @private * Whispers a Marching Order message to someone. */ function _whisper(who, msg) { var name = who.replace(/\(GM\)/, '').trim(); sendChat('Marching Order', '/w "' + name + '" ' + msg); } // When the API is loaded, install the Custom Status Marker menu macro // if it isn't already installed. on('ready', function() { var macro = findObjs({ _type: 'macro', name: 'MarchingOrderMenu' })[0]; if(!macro) { var players = findObjs({ _type: 'player' }); var gms = _.filter(players, function(player) { return playerIsGM(player.get('_id')); }); _.each(gms, function(gm) { createObj('macro', { _playerid: gm.get('_id'), name: 'MarchingOrderMenu', action: MENU_CMD }); }); } }); /** * Set up our chat command handler. */ on("chat:message", function(msg) { if(_msgStartsWith(msg, FOLLOW_CMD)) { var arg = _getCmdArg(msg); var tokens = _getSelectedTokens(msg); var direction = _parseDirection(arg); if(direction) _cmdFollowDirection(tokens, direction); else if(arg) _cmdFollowNamedToken(tokens, arg); else _replyHowToMessage(msg); } else if(_msgStartsWith(msg, MENU_CMD)) { _showMenu(msg.who, msg.playerid); } }); /** * Set up an event handler to do the marching order effect when the * leader tokens move! */ on("change:graphic", function(obj, prev) { var leader = obj; leader.prevLeft = prev["left"]; leader.prevTop = prev["top"]; leader.prevRotation = prev["rotation"]; // Only move the followers if there was a change in either the leader's // left or top attributes. if(leader.get("left") != leader.prevLeft || leader.get("top") != leader.prevTop) { // We stepped out of line. Stop following the guy in front of us. if(leader.leader) { unfollow(leader); } // move everyone to the previous position of the token in front of them. while(leader.follower) { _doFollowMovement(leader); leader = leader.follower; // avoid cycles. if(leader == obj) { return; } } } }); // The exposed API. return { follow: follow, set: setMarchingOrder, unfollow: unfollow }; })();