// Github: <a href="https://gist.github.com/finalfrog/124f67ad84204546caf16fffd84115e4" rel="nofollow">https://gist.github.com/finalfrog/124f67ad84204546caf16fffd84115e4</a> // // =Inspired by= // MapChange by TheWhiteWolves (<a href="https://github.com/TheWhiteWolves/MapChange.git" rel="nofollow">https://github.com/TheWhiteWolves/MapChange.git</a>) // Teleporter Without Movement Tracker by DarokinB (<a href="https://gist.github.com/DarokinB/5806230" rel="nofollow">https://gist.github.com/DarokinB/5806230</a>) // =Author= // FinalFrog // =Contact= // <a href="https://app.roll20.net/users/585874/finalfrog" rel="nofollow">https://app.roll20.net/users/585874/finalfrog</a> // // GENERAL RULES FOR CREATING AND USING TELEPORTERS: // // * Teleporters are defined by Tokens on the GM layer. // // * Tokens for Teleports must end in two digits followed by a letter between A and L. // Example: "TeleportToken02A" // // * Teleporters will only teleport tokens which meet the following requriements: // 1. Token represents a character controlled by a player. // 2. Token is on the object layer. // 3. Token with the same name exists on the page being teleported to. // // * The string preceeding the two digits and letters defines a unique teleport chain and all // teleporters in a chain must have the same two digits. // Example: "TeleportToken02A" and "TeleportToken02B" are both members of the "TeleportToken" chain // // * The two digits shared by all members of a chain define how many teleporters the chain will expect to find // before looping back to the first. // Example: "TeleportToken02A" will take you to "TeleportToken02B" which will take you back to "TeleportToken02A" // // * If an expected link in the teleporter chain does not exist then the teleport will break on the last // existing expected teleporter in the chain. // Example: If only "TeleportToken04A", "TeleportToken04B", and "TeleportToken04D" exist, then "TeleportToken04B" will teleport anywhere. // // EXAMPLE OF HOW TO CREATE AND USE AN SIMPLE TWO-WAY INTRA-MAP TELEPORTER PAIR: // // 1. Create a token on the GM layer named "Teleport02A" // 2. Create a token on the GM layer named "Teleport02B" // 3. Create a token on the Object layer named "Traveler" // 4. When you move "Traveler" into the same space as "Teleport02A" it should jump to "Teleport02B" and vis versa. // 5. You can change this to a one-way teleporter pair by naming the teleporters "Teleport03A" and "Teleport03B". // // EXAMPLE OF HOW TO CREATE AND USE AN SIMPLE TWO-WAY INTER-MAP TELEPORTER PAIR: // 1. Create a map named "MAP 1" // 2. Create a map named "MAP 2" // 3. Create a token on the GM layer in "MAP 1" named "Teleport02A" // 4. Create a token on the GM layer in "MAP 2" named "Teleport02B" // 5. Create a character controlled only by a player (Eg. Jeanie). // 6. Create a token on the Object layer in "MAP 1" named Jeanie and set it to represent the above character // 7. Create a token on the Object layer in "MAP 2" named Jeanie and set it to represent the above character // 8. When you move the Jeanie token on "MAP 1" onto the same space as "Teleport02A", the player (Jeanie) will be // moved on their own to "MAP 2" and their token on "MAP 2" will jump to the location of "Teleport02B", and the // Jeanie token on "MAP 1" is now moved to the GM layer // 9. Moving the token off of "Teleport02B" and back will reverse the process and take the player (Jeanie) // back to "Map 1", move the "MAP 2" token to the GM layer, and move the "MAP 1" token to the Object layer. var MapTeleporters = MapTeleporters || (function() { 'use strict'; // Special thanks to The Aaron for this function var findContains = function(obj,layer){ "use strict"; var cx = obj.get('left'), cy = obj.get('top'); if(obj) { layer = layer || 'gmlayer'; return _.chain(findObjs({ _pageid: obj.get('pageid'), _type: "graphic", layer: layer })) .reduce(function(m,o){ var l=o.get('left'), t=o.get('top'), w=o.get('width'), h=o.get('height'), ol=l-(w/2), or=l+(w/2), ot=t-(h/2), ob=t+(h/2); if( ol <= cx && cx <= or && ot <= cy && cy <= ob && o.get('name') != "" ){ m.push(o); } return m; },[]) .value(); } return []; }; var handleGraphicChange = function(obj) { if (obj.get("layer") !== "objects") { return; // Only teleport tokens on the object layer } // Check for token controlled by only one player var characterId = obj.get("represents"); var characters = findObjs({ _type: "character", _id: characterId, }); if (characters.length == 0) { // No characters found return; } // Handle first matching representing character var character = characters[0]; var controllers = character.get("controlledby"); if (controllers == "") { return; } /* To use this system, you need to name two Teleportation locations the same * with only an A and B distinction. For instance Teleport01A and Teleport01B * will be linked together. When a token gets on one location, it will be * Teleported to the other automatically */ // Find any teleporters in the same location as moved token var sourceTeleporters = findContains(obj,"gmlayer"); if (sourceTeleporters.length == 0) { // No source teleporters found return; } else { // Handle first matching source teleporter var sourceTeleporter = sourceTeleporters[0]; var sourceOffsetX = sourceTeleporter.get("left") - obj.get("left"); var sourceOffsetY = sourceTeleporter.get("top") - obj.get("top"); // Store the page of the source teleporter var sourcePage = sourceTeleporter.get("_pageid"); // Get name of current teleporter var CurrName = sourceTeleporter.get("name"); var Letters = new Array("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L"); // Number of doors in the cycle (second to last character) var doorCount = CurrName.substr(CurrName.length - 2, 1); // Current Letter of the Door var currDoor = CurrName.substr(CurrName.length - 1, 1); // Finds the pair location and moves target to that location var i = Letters.indexOf(currDoor); if (i == doorCount - 1) { i = 0; } else { i = i + 1; } var NewName = CurrName.substr(0,CurrName.length - 2) + doorCount + Letters[i]; var destTeleporters = findObjs({ _type: "graphic", layer: "gmlayer", //target location MUST be on GM layer name: NewName, }); if (destTeleporters.length == 0) { // No destination teleporters found return; } else { // Handle first matching destination teleporter var destTeleporter = destTeleporters[0]; // Coordinates of destination teleporter var NewX = destTeleporter.get("left") - sourceOffsetX; var NewY = destTeleporter.get("top") - sourceOffsetY; // Page of destination teleporter var destPage = destTeleporter.get("_pageid"); if (destPage == sourcePage) { // Handle intra-page teleports by just changing position of // original token. obj.set("left", NewX); obj.set("top", NewY); } else { // Handle inter-page teleports by searching for graphic token // on destination page with same name as teleporting token var teleportedTokens = findObjs({ _pageid: destPage, _type: "graphic", name: obj.get("name"), }); if (teleportedTokens.length == 0) { // No tokens with the same name as teleporting token found return; } else { // Handle first matching teleported token var teleportedToken = teleportedTokens[0]; // Update teleported token on new page with coordinates of // destination teleporter teleportedToken.set("left", NewX); teleportedToken.set("top", NewY); // Show teleported token if it is hidden teleportedToken.set("layer", "objects"); var controllerArray = controllers.split(); if (controllerArray.length == 1) { // Teleport player to the page with the destination // teleporter teleport(controllerArray[0], destPage); // NOTE: sendPing moveAll argument currently broken (See <a href="https://wiki.roll20.net/Talk:API:Utility_Functions" rel="nofollow">https://wiki.roll20.net/Talk:API:Utility_Functions</a>) sendPing(NewX, NewY, destPage, null, true); // Hide original token if player has moved to new map obj.set("layer", "gmlayer"); } } } } } }; var teleport = function(playerId, pageId) { var playerPages = Campaign().get("playerspecificpages"); if (playerPages === false) { playerPages = {}; } if (playerId == "all") { // Move the whole group to the target page Campaign().set("playerspecificpages", false); Campaign().set("playerpageid", pageId); } // Remove player from playerPages if (playerId in playerPages) { delete playerPages[playerId]; } // Update playerPages with player on target page playerPages[playerId] = pageId; Campaign().set("playerspecificpages", false); Campaign().set("playerspecificpages", playerPages); }; var registerEventHandlers = function() { on('change:graphic:left', handleGraphicChange); on('change:graphic:top', handleGraphicChange); }; return { RegisterEventHandlers: registerEventHandlers, }; }()); on("ready", function() { 'use strict'; MapTeleporters.RegisterEventHandlers(); }); Heres the copy I am using if this helps at all, I don't seem to have the same layering issues.