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

Teleport Rework with Ping

1602033995
Pat
Pro
API Scripter
This is the latest rework I've been trying - to integrate the ping api with the old Teleport script, including for the macro teleport. Added to this is support for the FX engine on teleport targets, also working for both the macro and auto teleport. Ping is configurable, as is FX. The macro also now moves to gm layer before movement. Please give it a try and kick the tires - I've put in support for suppressing the ping fx or not separately. With a transparent gm controlled token, set to controlled by "all," you could use the macro to target players to sections of the map without click-and-hold, for instance.  /* ************ TELEPORTING SCRIPT ************************** * The intention of this script is to allow the DM to teleport * one or all characters to a location based on a token placed * on the DM layer of the map. * To activate the script, type "!Teleport " and add the name * of the teleport location (must not contain spaces) and then * the name of the party member to teleport there. They must be * seperated by commas. If you want all to teleport, type all. * ie. !Teleport teleport01, all - teleports all players to teleport01 * * AUTOTELEPORTING: This feature allows you to place a token on * One square (for example stairs) and it will auto move a token * to the linked location and back again should you choose. * Linked locations need to be tokens placed on the GMLayer. * Naming conventions: * Two way doors: XXXXXXXX2A, XXXXXXXXX2B * Three way dooes: XXXXXXXX3A, XXXXXXXXX3B, XXXXXXXXX3C * (in the case of one way doors, dont create a 3C) * This system can handle up to 9 way doors (9I max). ****************************************************************/ on('ready',() => { var Teleporter = Teleporter || function(){ // These following three functions parse out and then compare token markers // on teleporting tokens and teleportation targets, but only for auto-teleport const statusmarkersToObject = (stats) => _.reduce(stats.split(/,/), function(memo, value) { let parts = value.split(/@/), num = parseInt(parts[1] || '0', 10); if (parts[0].length) { memo[parts[0]] = Math.max(num, memo[parts[0]] || 0); } return memo; }, {}); var findContains = function(obj,layer){ "use strict"; let cx = obj.get('left'), cy = obj.get('top'); if(obj) { layer = layer || 'gmlayer'; return _.chain(findObjs({ _pageid: obj.get('pageid'), _type: "graphic", layer: layer })) .filter((o)=>/Teleport/.test(o.get('name'))) .reduce(function(m,o){ let 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 ){ m.push(o); log("Teleporter Name:" + o.get('name')); } return m; },[]) .value(); } return []; }; const CheckLock = (portal, obj) => { let objKey=statusmarkersToObject(obj.get('statusmarkers')); return _.reduce(statusmarkersToObject(portal.get('statusmarkers')),(m,v,k) => m && _.has(objKey,k) && objKey[k] === v, true); }; /* The msg based teleporter works slightly differently from the teleporter-intersection script It relies on the list of passed tokens to grab and teleport tokens - it right now has no swap to GM layer, but it is hoped to integrate both functions into the same architecture to avoid redundancy, if it is at all possible. Right now: the msg based teleporter needs: - Swap to GM layer for each token DONE - Control for single FX generation on "All" DONE - Check on all "all" to make sure it is PLAYER or ALL controlled explicitly * otherwise this results in all tokens, player and GM controlled as long as they have a sheet, being teleported. DONE - Add ping if it is not "ALL", or add GM Ping (default) on any call of "ALL" - consider a pass of an fx for fun, maybe not - the way it is done now. (pass attr?) - consider audio default for teleport as well.(pass and in destination?) - function is inefficient since it performs a MASSIVE search multiple times to move a single token in series. DONE - Need to adapt this to digest all of the entries at once - can the findObjs only take a single entry? DONE */ var Teleport = function (CharName, TargetName) { "use strict"; var LocX = 0; var LocY = 0; var LocFX = ""; var player = (CharName === "all")?Teleporter.DEFAULTPLAYER:""; var follow = true; var oldColor = "transparent"; var lastObj = null; var location = findObjs({ _pageid: Campaign().get("playerpageid"), _type: "graphic", layer: "gmlayer", //target location MUST be on GM layer name: TargetName }); if (location.length === 0) { sendChat("System", "/w gm No location named " + TargetName + " found. Try checking the spelling and making sure it is on the gm layer."); return; //exit if invalid target location } LocX = location[0].get("left"); LocY = location[0].get("top"); // Determine if there is any FX associated with the target obj in the gmnotes LocFX = unescape(location[0].get('gmnotes')).replace(/<[^>]*>/g,''); // If the unescaped content has curly braces, parse as an object, otherwise it's an unescaped string. if(LocFX !== '' && LocFX.indexOf("{") !== -1){ LocFX = JSON.parse(LocFX); } //just get tokens on the objects layer - don't specify name if all. if (CharName === "all"){ var targets = findObjs({ _pageid: Campaign().get("playerpageid"), _type: "graphic", layer: "objects" }); } else { var targets = findObjs({ _pageid: Campaign().get("playerpageid"), _type: "graphic", layer: "objects", name: CharName }); } if(targets.length === 0){ sendChat("System", "/w gm No character named " + CharName + " found. Please check the spelling, make sure it is on the object layer, and try again."); return; } _.each(targets, function(obj) { //Only player tokens get moved if the character is "all". if (CharName === "all") { if (obj.get("represents") !== "" && getObj("character", obj.get('represents')).get('controlledby') !== "") { obj.set({layer:'gmlayer'}); _.delay(()=>{ obj.set({ left: LocX + 1, top: LocY, lastmove: '' }); _.delay(()=>{ obj.set({ layer: 'objects' }); },500); },100); } } else { let character=(obj.get('represents'))?getObj("character", obj.get('represents')):null; let controller = (character)?character.get('controlledby'):''; if(controller !== '' && controller !== 'all' ){ player=getObj("player", controller); follow=true; }else{ // set player to GM (eventually), and if not "all" set follow to false player=Teleporter.DEFAULTPLAYER; follow=(controller === 'all')?true:false; } if((!Teleporter.AUTOPINGMOVE && follow) || !follow){ follow=false; }else{ oldColor=player.get("color"); player.set({color:"transparent"}); } obj.set({layer:'gmlayer'}); _.delay(()=>{ obj.set({ left: LocX + 1, top: LocY, lastmove: '' }); _.delay(()=>{ obj.set({ layer: 'objects' }); if(LocFX !== '' && Teleporter.AUTOPLAYFX ){ if(_.isString(LocFX)){ spawnFx(LocX, LocY, LocFX, obj.get('pageid')); }else{ spawnFxWithDefinition(LocX, LocY, LocFX, obj.get('pageid')); } } if(follow){sendPing(LocX, LocY, obj.get('pageid'), player.get("_id"), true, player.get("_id"));} _.delay(()=>{ // Longer delay to re-set the player color to allow the ping to finish. if(follow){player.set({color: oldColor});} },1000); },500); },100); } lastObj = obj; }); if (CharName === "all"){ if(!Teleporter.AUTOPINGMOVE){ follow=false; }else{ follow=true; oldColor=player.get("color"); player.set({color:"transparent"}); } /**/ _.delay(()=>{ if(LocFX !== '' && Teleporter.AUTOPLAYFX ){ if(_.isString(LocFX)){ spawnFx(LocX, LocY, LocFX, Campaign().get("playerpageid")); }else{ spawnFxWithDefinition(LocX, LocY, LocFX, Campaign().get("playerpageid")); } } if(follow){sendPing(LocX, LocY, lastObj.get('pageid'), player.get("_id"), true, player.get("_id"));} _.delay(()=>{ // Longer delay to re-set the player color to allow the ping to finish. if(follow){player.set({color: oldColor});} },1000); },500); } }; on("change:graphic", function(obj) { "use strict"; if (obj.get("name").indexOf("Teleport") !== -1 || /(walls|map)/.test(obj.get('layer'))) { return; //Do not teleport teleport pads!! } if (Teleporter.AUTOTELEPORTER === false) { return; //Exit if auto Teleport is disabled } /* 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 */ //Finds the current teleportation location var CurrName = ""; var location = findContains(obj,'gmlayer'); if (location.length === 0) { return; } let Curr = location[0]; if(!CheckLock(Curr,obj)){ return; } CurrName = Curr.get("name"); var Letters = ["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 = 0; if( CurrName.match(/^R:/) ) { i = randomInteger(doorCount)-1; } else { 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 NewX = 0; var NewY = 0; var LocFX = ""; var newLocation = findObjs({ _pageid: obj.get('pageid'), _type: "graphic", layer: "gmlayer", //target location MUST be on GM layer name: NewName }); _.each(newLocation, function(Loc){ //Get the new Location NewX = Loc.get("left"); NewY = Loc.get("top"); LocFX = unescape(Loc.get('gmnotes')).replace(/<[^>]*>/g,''); }); if (NewX === 0 ) { return; } if(LocFX !== '' && LocFX.indexOf("{") !== -1){ LocFX = JSON.parse(LocFX); // convert LocFX to an object } let currLayer=obj.get('layer'); let character=(obj.get('represents'))?getObj("character", obj.get('represents')):null; let player, follow, oldColor; var controller = (character)?character.get('controlledby'):''; if(controller !== '' && controller !== 'all' ){ player=getObj("player", controller); follow=true; }else{ // set player to GM (eventually), and if not "all" set follow to false player=Teleporter.DEFAULTPLAYER; follow=(controller === 'all')?true:false; } // use override for send ping if((!Teleporter.AUTOPINGMOVE && follow) || !follow){ follow=false; }else{ oldColor=player.get("color"); player.set({color:"transparent"}); } obj.set({ layer: 'gmlayer' }); _.delay(()=>{ obj.set({ left: NewX, top: NewY, lastmove: '' }); _.delay(()=>{ obj.set({ layer: currLayer }); // Ping only the specified player to this location - (get controlling player, have to account for multiple/"all") if(follow){sendPing(NewX, NewY, obj.get('pageid'), player.get("_id"), true, player.get("_id"));} if(LocFX !== '' && Teleporter.AUTOPLAYFX ){ if(_.isString(LocFX)){ spawnFx(NewX, NewY, LocFX, obj.get('pageid')); }else{ spawnFxWithDefinition(NewX, NewY, LocFX, obj.get('pageid')); } } _.delay(()=>{ // Longer delay to re-set the player color to allow the ping to finish. if(follow){player.set({color: oldColor});} },1000); },500); },100); }); on("chat:message", function(msg) { "use strict"; var cmdName = "!Teleport "; if (msg.type === "api" && msg.content.indexOf(cmdName) !== -1 && playerIsGM(msg.playerid)) { var cleanedMsg = msg.content.replace(cmdName, ""); var commands = cleanedMsg.split(", "); var targetName = commands[0]; var i = 1; // Fires off one command for each entry - meaning multiple SFX and refocus etc. unless ALL while ( i < commands.length ) { Teleporter.Teleport(commands[i], targetName); i = i + 1; } } if (msg.content.indexOf("!AUTOTELEPORTER") !== -1 && playerIsGM(msg.playerid)) { if ( Teleporter.AUTOTELEPORTER === true) { sendChat("System", "/w gm Autoteleporting Disabled."); Teleporter.AUTOTELEPORTER = false; } else { sendChat("System", "/w gm Autoteleporting Enabled."); Teleporter.AUTOTELEPORTER = true; } } if (msg.content.indexOf("!AUTOPINGMOVE") !== -1 && playerIsGM(msg.playerid)) { if ( Teleporter.AUTOPINGMOVE === true) { sendChat("System", "/w gm Ping-move Disabled."); Teleporter.AUTOPINGMOVE = false; } else { sendChat("System", "/w gm Ping-move Enabled."); Teleporter.AUTOPINGMOVE = true; } } if (msg.content.indexOf("!AUTOPLAYFX") !== -1 && playerIsGM(msg.playerid)) { if ( Teleporter.AUTOPLAYFX === true) { sendChat("System", "/w gm Auto Play FX Disabled."); Teleporter.AUTOPLAYFX = false; } else { sendChat("System", "/w gm Auto Play FX Enabled."); Teleporter.AUTOPLAYFX = true; } } if (msg.content.indexOf("!HIDEPINGFX") !== -1 && playerIsGM(msg.playerid)) { if ( Teleporter.HIDEPINGFX === true) { sendChat("System", "/w gm Hide Ping FX Disabled."); Teleporter.HIDEPINGFX = false; } else { sendChat("System", "/w gm Hide Ping FX Enabled."); Teleporter.HIDEPINGFX = true; } } }); return { AUTOTELEPORTER: true, // Set to true if you want teleports to be linked AUTOPINGMOVE: true, // Set to true if you want individual auto-teleports/teleports to also move the view. AUTOPLAYFX: true, // Set to true if you want FX to play for teleport targets. // - Still checks for FX before playing. False turns all off. HIDEPINGFX: true, // Set to true if you want the ping FX to be hidden, turn false for visible ping FX // Run this onece to get a player object for the GM for "default" pings for "all" DEFAULTPLAYER: (function(){ let player; let playerlist = findObjs({ _type: "player", }); _.each(playerlist, function(obj) { if(playerIsGM(obj.get("_id"))){ player = obj; }; }); return player; })(), Teleport: Teleport } }(); });
1602610499
Andreas J.
Forum Champion
Sheet Author
Translator
This is the latest rework I've been trying - to integrate the ping api with the old Teleport script, including for the macro teleport. Is this based on your thread from one month ago ? Did you figure out how to make it work for teleporting player from one page to another, like the title of the last post was talking about? If not, could code from the MapChange API(in the one click install) be helpful to figure that part out?
ᐰndreas J. said: This is the latest rework I've been trying - to integrate the ping api with the old Teleport script, including for the macro teleport. Is this based on your thread from one month ago ? Did you figure out how to make it work for teleporting player from one page to another, like the title of the last post was talking about? If not, could code from the MapChange API(in the one click install) be helpful to figure that part out? Yeah, map-to-map teleporting would be insanely good! But thanks, Pat, for reworking this script! Will give it a try this week.
1602611002
Andreas J.
Forum Champion
Sheet Author
Translator
Jay R. said: But thanks, Pat, for reworking this script! Will give it a try this week. Do tell how it turned out, I'm looking into what potions there are for Teleportation through portals, and this was the most recent thread on the topic. Nick Olivo also have something that looks promising from one year ago, which have a Youtube video explaining the usage.
1602611944
Pat
Pro
API Scripter
I'm starting in an actual project-related git instead of fly-by-the-seat-of-the-pants, because I want to have page-to-page and make it happen, I want it elegant, and I want a way to use menus in the chat to check on the status of portal-tokens so there's less mystery on why token-teleport may not be working. 
1602612483
Andreas J.
Forum Champion
Sheet Author
Translator
Pat said: I'm starting in an actual project-related git instead of fly-by-the-seat-of-the-pants, because I want to have page-to-page and make it happen, I want it elegant, and I want a way to use menus in the chat to check on the status of portal-tokens so there's less mystery on why token-teleport may not be working.  Oh man, that sounds fantastic. Is it meant to optionally allow players to move themselves between maps/teleport locations by moving their token, or is this meant to be a GM-only thing, launched by a command? Are you intending to use this teleportation script like how things would happen if someone where to cast a Teleport/Dimension Door/Plane Shift, or also to work for simulating a situation where someone walks through a portal that moves them to the other side?
1602627739

Edited 1602629008
Pat
Pro
API Scripter
Right now, it was making the player-controlled "walk onto a portal" for fantastic things and mundane things (portal to another realm on the same page, portal to the upstairs on the same page via stairs) work with the ping , and controls to allow a GM to allow or disallow these things, or to target predefined portals for transport with a script/command and include the ping . Special effects are optional, and currently only occur at the destination, but could be configured to occur at the origin too (disappear in a puff of smoke, etc.)  Currently, any token can teleport via portal token, the logic figures out if there is a subsequent "ping" (is it player controlled or not determines this) The script macros needed rework - "teleport all" was grabbing every token on the token layer regardless of ownership, so I added some logic to only grab player controlled tokens, not tokens with an NPC sheet, or tokens with no sheet at all. The macros were not layer-swapping, so tokens just scooted to the teleport target, so there was some adding layer-swaps to hide the movement.  First part was adding "ping" and the timers necessary to be able to hide it if you want , second was adding SFX and the ability to toggle these on and off globally . I'm going to try and bring all the internal teleport scripts into alignment (there are three right now, I'd rather it be one with configuration), set up parameters and options for individual macros (add sfx just for one teleport at the start instead of coding it into all of them).  I'm going to eventually do things like see if one can detect if the current map is hex or square (center-align teleport for hex, left-upper corner align for square), set up a config to set parameters and to tell you if your teleport targets can be detected and what they are, and then branch from there to cross-page teleport, which will be a lot more tricky. 
Playing with the script right now. The teleport all command works just fine, but I can't figure out how to enable autoteleporting. The old !atp command doesn't seem to be valid, and I also don't know how to bring up any configuration menu (!Teleport help didn't work). 
1602637549
Pat
Pro
API Scripter
Right - no Teleport menu, no config yet - to create two teleport pads, it's a little counter-intuitive (may fix that too, and retain legacy for back-compat) - you create two tokens on the GM layer. Name them like so: TeleportP2A and TeleportP2B - that's it. The way the current script works is it looks for a token on the GM layer that has a name that is suffixed with a number and a letter. The number is the total number of teleport pads, the A, B, etc is the place in the sequence of those teleport pads. Not sure if "Teleport" is still required (not required for targeted teleport) but just in case. 
Pat said: Right - no Teleport menu, no config yet - to create two teleport pads, it's a little counter-intuitive (may fix that too, and retain legacy for back-compat) - you create two tokens on the GM layer. Name them like so: TeleportP2A and TeleportP2B - that's it. The way the current script works is it looks for a token on the GM layer that has a name that is suffixed with a number and a letter. The number is the total number of teleport pads, the A, B, etc is the place in the sequence of those teleport pads. Not sure if "Teleport" is still required (not required for targeted teleport) but just in case.  Ah! I had them with 2A and 2B but without that P, which is new, I think (the old Teleport script didn't require it). Will give it a shot right now! (you might want to update the comments at the top of the script) Edit: So I tried it using the exact names (TeleportP2A and TeleportP2B) and it worked. But it didn't work whenever I entered a name that wasn't Teleport. The old script let you name the pads whatever you wanted, so long as you had the number and letter at the end. Or is it an issue of the name needing to be eight characters long? Edit 2: OK, I can at least add numbers to the end of Teleport, but before the P, so as to distinguish one set of pads from another on a single map. Cool!
Next dumb question: how do I turn FX, ping, and emote on? Or is that functionality not yet available?
1602645268

Edited 1602645703
Pat
Pro
API Scripter
Ping should already be on by default, so should FX - for FX, you need to add the FX you want into the teleport pad you want it on in the GM Notes. So, you pick one of the teleport target tokens you've created (that you know works) and you add the following, for example, to the GM Notes:  burn-magic For now, whenever a character token arrives at that teleport token, that FX will play. I'm working on firing an FX from the initiating token but that's not ready yet.  When you move a token that belongs to a player (it can be you) it will ping and move the view of that player to the token. For that, you need to go to the character sheet and set the controlled by to you, the person. It will then work with the ping for you as the GM or you as the Player. 
Pat said: Ping should already be on by default, so should FX - for FX, you need to add the FX you want into the teleport pad you want it on in the GM Notes. So, you pick one of the teleport target tokens you've created (that you know works) and you add the following, for example, to the GM Notes:  burn-magic For now, whenever a character token arrives at that teleport token, that FX will play. I'm working on firing an FX from the initiating token but that's not ready yet.  When you move a token that belongs to a player (it can be you) it will ping and move the view of that player to the token. For that, you need to go to the character sheet and set the controlled by to you, the person. It will then work with the ping for you as the GM or you as the Player.  Right on! I'll try this out, thanks.
The FX work great. Thank you! I appreciate being able to tether specific FX to specific pads (i.e. I don't need flashy FX for stairs between dungeon levels, but I do need them for portals).  Feature request: the ability to tack on /desc text to each teleport pad. For example: "<selected token> races up the stairs into the next level of the dungeon!" or "<selected token> disappears in a flash of smoke!"). 
1602648534
Pat
Pro
API Scripter
With the auto-teleport, it's unlikely "selected token" would work, as no msg object is passed to make that work, unfortunately. The msg gets a selected token when a macro is kicked off, but the auto teleport doesn't make any use of macros or msg objects. 
Pat said: With the auto-teleport, it's unlikely "selected token" would work, as no msg object is passed to make that work, unfortunately. The msg gets a selected token when a macro is kicked off, but the auto teleport doesn't make any use of macros or msg objects.  That's too bad. The old Teleport script seemed to take into account the token name, or character name anyway, and it threw that name into the emote message for autoteleporting. But the ability to add any descriptive text would still be good!
1602689737
Pat
Pro
API Scripter
Jay R. said: That's too bad. The old Teleport script seemed to take into account the token name, or character name anyway, and it threw that name into the emote message for autoteleporting. But the ability to add any descriptive text would still be good! I'm not sure which script you're looking at - I took the one that was in the forums here, for autoteleport/teleport - maybe the cross-Page teleporters did something like that, I'm not sure - but there was no emote statement in this one that I've been modifying. I could have it cough out the character name, but you wouldn't want that for the "all" teleport. 
Pat said: Jay R. said: That's too bad. The old Teleport script seemed to take into account the token name, or character name anyway, and it threw that name into the emote message for autoteleporting. But the ability to add any descriptive text would still be good! I'm not sure which script you're looking at - I took the one that was in the forums here, for autoteleport/teleport - maybe the cross-Page teleporters did something like that, I'm not sure - but there was no emote statement in this one that I've been modifying. I could have it cough out the character name, but you wouldn't want that for the "all" teleport.  I was using one that yours seemed to be based on. Someone had updated it a while back (maybe it was you?).  /* ************ TELEPORTING SCRIPT V3 ************************ * The intention of this script is to allow the DM to teleport * one or all characters to a location based on a token placed * on the DM layer of the map. * To activate the script, type "!tp " and add the name * of the teleport location (must not contrain spaces) and then * the name of the party member to teleport there. They must be * seperated by commas. If you want all to teleport, type all. * ie. !tp teleport01, all - teleports all players to teleport01 * * AUTOTELEPORTING: (Command !atp) This feature allows you to place a token on * One square (for example stairs) and it will auto move a token * to the linked location and back again should you choose. * Linked locations need to be tokens placed on the GMLayer. * Naming conventions: * Two way doors: XXXXXXXX2A, XXXXXXXXX2B * Three way dooes: XXXXXXXX3A, XXXXXXXXX3B, XXXXXXXXX3C * (in the case of one way doors, dont create a 3C) * This system can handle up to 9 way doors (9I max). ****************************************************************/ on("ready", function() { log(">> Itialized Auto Teleport - V 1.0"); LoadSettings () CreateMacro_Emote () CreateMacro_FX () CreateMacro_TP () sendChat("System","/w gm Auto Teleport Loaded - [Click Here](!tp help) or type !tp help for commands.") }); var Teleporter = Teleporter || {}; function sendHelp () { var finalMessage = "/w gm &{template:default} {{name=Teleport Commands !tp help}}"+ "{{[Auto]\n!tp atp=[Toggle](!tp atp) ["+Teleporter.AUTOTELEPORTER+"]\n Automatic teleporting}}"+ "{{[Ping]\n!tp ptp=[Toggle](!tp ptp) ["+Teleporter.PINGTELEPORTER+"]\n Ping on teleport}}"+ "{{[FX]\n!tp fx=[Toggle](!tp fx) ["+Teleporter.FXTELEPORTER+"]\n "+Teleporter.FXTYPE+"}}"+ "{{[Emote]\n!tp etp=[Toggle](!tp etp) ["+Teleporter.EMOTETELEPORTER+"]\n "+Teleporter.EMOTE+"}}"+ "{{setfx\nMacro=#tp-fx\n Change the FX.}}"+ "{{setemote\nMacro=#tp-emote\n Change the emote.}}"+ "{{[Teleport]\n!tp [t],[p]\n!tp [t],all=Where t is the name of target token to teleport to on the GM layer and p is each player token name seperated by commas or 'all' to teleport everyone.}}"+ "{{Setup=[How To](!tp setup)}}" sendChat("", finalMessage); } function sendHelp_Setup () { var setupMessage = "/w gm &{template:default} {{name=Setup}}"+ "{{Setup=To set up auto teleporting you must create objects on the GM layer with identical name except for the last 2 characters. For this example XXXXX2A and XXXXX2B, this indicates a two way system and links between A and B. If you want to create a 3 way system it becomes 3A, 3B, and 3C and so on teleporting players to each node in sequence. Up to 9 teleports are supported for a system.}}"+ "{{Switches=You can flag teleport tokens on the GM layer with status markers to disable individual effects.}}"+ "{{Red X=Disables the teleporter}}"+ "{{Purple=Disables FX}}"+ "{{Pink=Disables Emotes}}"+ "{{Yellow=Disables Ping}}" sendChat("", setupMessage); } function CreateMacro_Emote () { macro = findObjs({ _type: 'macro', name: 'tp-emote' })[0]; if(!macro) { players = findObjs({ _type: 'player' }); gms = _.filter(players, player => { return playerIsGM(player.get('_id')); }); _.each(gms, gm => { createObj('macro', { _playerid: gm.get('_id'), name: 'tp-emote', action: '!tp setemote, ?{Emote}', istokenaction: false }); }); } } function CreateMacro_FX () { macro = findObjs({ _type: 'macro', name: 'tp-fx' })[0]; if(!macro) { players = findObjs({ _type: 'player' }); gms = _.filter(players, player => { return playerIsGM(player.get('_id')); }); _.each(gms, gm => { createObj('macro', { _playerid: gm.get('_id'), name: 'tp-fx', action: '!tp setfx, ?{Emote}', istokenaction: false }); }); } } function CreateMacro_TP () { var tp_macro = (findObjs({ _type: 'macro', name: 'teleport' })[0]||createObj('macro',{name:'teleport'})); var tp_all_macro = (findObjs({ _type: 'macro', name: 'teleport-all' })[0]||createObj('macro',{name:'teleport-all'})); //find the objects on the GM layer var gmObjs = findObjs({ _pageid: Campaign().get("playerpageid"), _type: "graphic", layer: "gmlayer", }); var tp_objs = ""; _.each(gmObjs, function(obj) { if (obj.get("name") !== ""){ tp_objs += "|" + obj.get("name"); } }); var tp_macro_action = '!tp ?{Teleport to?' + tp_objs + '}, @{target|token_name}'; var tp_all_macro_action = '!tp ?{Teleport to?' + tp_objs + '}, all'; players = findObjs({ _type: 'player' }); gms = _.filter(players, player => { return playerIsGM(player.get('_id')); }); if (tp_objs.length !== 0) { if(!tp_macro) { _.each(gms, gm => { createObj('macro', { _playerid: gm.get('_id'), name: 'teleport', action: tp_macro_action, istokenaction: false }); }); sendChat("System","/w gm Macro Created > #teleport"); } else { tp_macro.set('action', tp_macro_action); } if(!tp_all_macro) { _.each(gms, gm => { createObj('macro', { _playerid: gm.get('_id'), name: 'teleport-all', action: tp_all_macro_action, istokenaction: false }); }); sendChat("System","/w gm Macro Created > #teleport-all"); } else { tp_all_macro.set('action', tp_all_macro_action); } } else { tp_all_macro.set('action', '/w gm No teleport locations on this map!'); tp_macro.set('action', '/w gm No teleport locations on this map!'); } } function LoadSettings () { TeleportSettings = findObjs({ type: "character", name: "TeleportSettings" })[0]; if (!TeleportSettings) { log(">> Auto Teleport -> No token settings detected, initializing token default settings.") Teleporter.AUTOTELEPORTER = true; //Set to true if you want teleports to be linked Teleporter.EMOTETELEPORTER = true; //Set to true if you want teleporters to emote Teleporter.PINGTELEPORTER = true; //Set to true if you want teleporters to emote Teleporter.FXTELEPORTER = true; //Set to true if you want teleporters with fx Teleporter.EMOTE = "vanishes into thin air"; //Set the emote to use Teleporter.FXTYPE = "burn-smoke"; //Set the emote to use CharacterSettings = createObj("character", { name: "TeleportSettings" }); createObj('attribute', { name: 'AutoTeleport', current: true, characterid: CharacterSettings.id }); createObj('attribute', { name: 'Emote', current: true, characterid: CharacterSettings.id }); createObj('attribute', { name: 'Ping', current: true, characterid: CharacterSettings.id }); createObj('attribute', { name: 'FX', current: true, characterid: CharacterSettings.id }); createObj('attribute', { name: 'EmoteString', current: "vanishes into thin air", characterid: CharacterSettings.id }); createObj('attribute', { name: 'FXType', current: 'burn-smoke', characterid: CharacterSettings.id }); } else { Teleporter.AUTOTELEPORTER = getAttrByName(TeleportSettings.id, 'AutoTeleport') Teleporter.EMOTETELEPORTER = getAttrByName(TeleportSettings.id, 'Emote') Teleporter.PINGTELEPORTER = getAttrByName(TeleportSettings.id, 'Ping') Teleporter.FXTELEPORTER = getAttrByName(TeleportSettings.id, 'FX') Teleporter.EMOTE = getAttrByName(TeleportSettings.id, 'EmoteString') Teleporter.FXTYPE = getAttrByName(TeleportSettings.id, 'FXType') } } function UpdateSettings () { TeleportSettings = findObjs({ type: "character", name: "TeleportSettings" })[0]; var atp = findObjs({_type: "attribute",name: "AutoTeleport",_characterid: TeleportSettings.id})[0]; var etp = findObjs({_type: "attribute",name: "Emote",_characterid: TeleportSettings.id})[0]; var ptp = findObjs({_type: "attribute",name: "Ping",_characterid: TeleportSettings.id})[0]; var emote = findObjs({_type: "attribute",name: "EmoteString",_characterid: TeleportSettings.id})[0]; var fx = findObjs({_type: "attribute",name: "FX",_characterid: TeleportSettings.id})[0]; var fxtype = findObjs({_type: "attribute",name: "FXType",_characterid: TeleportSettings.id})[0]; atp.set('current', Teleporter.AUTOTELEPORTER); etp.set('current', Teleporter.EMOTETELEPORTER); ptp.set('current', Teleporter.PINGTELEPORTER); fx.set('current', Teleporter.FXTELEPORTER); emote.set('current', Teleporter.EMOTE); fxtype.set('current', Teleporter.FXTYPE); } function ToggleSettings (tset) { switch (tset) { case "atp": if ( Teleporter.AUTOTELEPORTER === true) { Teleporter.AUTOTELEPORTER = false; } else { Teleporter.AUTOTELEPORTER = true; } break; case "etp": if ( Teleporter.EMOTETELEPORTER === true) { Teleporter.EMOTETELEPORTER = false; } else { Teleporter.EMOTETELEPORTER = true; } break; case "ptp": if ( Teleporter.PINGTELEPORTER === true) { Teleporter.PINGTELEPORTER = false; } else { Teleporter.PINGTELEPORTER = true; } break; case "fx": if ( Teleporter.FXTELEPORTER === true) { Teleporter.FXTELEPORTER = false; } else { Teleporter.FXTELEPORTER = true; } break; } UpdateSettings() sendHelp() } on('change:campaign:playerpageid', function(campaign) { var currMap = getObj('page', campaign.get('playerpageid')); CreateMacro_TP() }); Teleporter.Teleport = function (CharName, TargetName) { "use strict"; var LocX = 0; var LocY = 0; //find the target location var location = findObjs({ _type: "graphic", layer: "gmlayer", //target location MUST be on GM layer name: TargetName }); if (location.length === 0) { return; //exit if invalid target location } // Get the page ID of the triggering object. var targetPageID = location[0].get('pageid'); LocX = location[0].get("left"); LocY = location[0].get("top"); //if all are indicated, it lists all //finds all tokens with the name var targets = findObjs({ _pageid: targetPageID, _type: "graphic" }); //Move characters to target location _.each(targets, function(obj) { //Only player tokens if (CharName === "all") { if (obj.get("represents") !== "") { log("Setting all"); if (Teleporter.FXTELEPORTER === true) { spawnFx(obj.get("left"), obj.get("top"), Teleporter.FXTYPE, targetPageID); } if (Teleporter.PINGTELEPORTER === true) { sendPing(LocX, LocY, targetPageID, null, true); } obj.set("left", LocX + 1); obj.set("top", LocY); } } else { if (obj.get("name").indexOf(CharName) !== -1 && obj.get("layer") !== "gmlayer") { if (obj.get("represents") !== "") { if (Teleporter.FXTELEPORTER === true) { spawnFx(obj.get("left"), obj.get("top"), Teleporter.FXTYPE, targetPageID); } if (Teleporter.PINGTELEPORTER === true) { sendPing(LocX, LocY, targetPageID, null, true); } obj.set("left", LocX + 1); obj.set("top", LocY); } } } }); }; on("chat:message", function(msg) { "use strict"; var cmdName = "!tp "; if (msg.type === "api" && msg.content.indexOf(cmdName) !== -1 && playerIsGM(msg.playerid)) { var cleanedMsg = msg.content.replace(cmdName, ""); var commands = cleanedMsg.split(", "); var targetName = commands[0]; switch (targetName){ case "atp": ToggleSettings("atp"); break; case "etp": ToggleSettings("etp"); break; case "ptp": ToggleSettings("ptp"); break; case "fx": ToggleSettings("fx"); break; case "help": sendHelp() break; case "setup": sendHelp_Setup() break; case "setfx": log(">> Set Teleport FX To: "+commands[1]) Teleporter.FXTYPE = commands[1]; UpdateSettings() sendHelp() break; case "setemote": log(">> Set Teleport Emote To: "+commands[1]) Teleporter.EMOTE = commands[1]; UpdateSettings() sendHelp() break; default: var i = 1; while ( i < commands.length ) { Teleporter.Teleport(commands[i], targetName); i = i + 1; } break; } } }); 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 ){ m.push(o); } return m; },[]) .value(); } return []; }; on("change:graphic", function(obj) { "use strict"; // Get the page ID of the triggering object. var currentPageID = obj.get('pageid'); if(obj.get("layer") === "gmlayer" || obj.get("layer") === "map") { return; //Don't trigger if it's an object on the gm or map layer. } if (Teleporter.AUTOTELEPORTER === false) { return; //Exit if auto Teleport is disabled } /* 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 */ //Finds the current teleportation location var CurrName = ""; var location = findContains(obj,'gmlayer'); if (location.length === 0) { return; } //Don't teleport if marked dead (with an X) if(location[0].get('status_dead')) { return; } CurrName = location[0].get("name"); var Letters = ["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 = 0; if( CurrName.match(/^R:/) ) { i = randomInteger(doorCount)-1; } else { 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 NewX = 0; var NewY = 0; var newLocation = findObjs({ _pageid: currentPageID, _type: "graphic", layer: "gmlayer", //target location MUST be on GM layer name: NewName }); _.each(newLocation, function(Loc){ //Get the new Location NewX = Loc.get("left"); NewY = Loc.get("top"); }); if (NewX === 0 ) { return; } if (Teleporter.EMOTETELEPORTER === true && !location[0].get('status_pink')) { //Display an emote when vanishing sendChat(obj.get("name"), "/e "+Teleporter.EMOTE); } if (Teleporter.PINGTELEPORTER === true && !location[0].get('status_yellow')) { sendPing(NewX, NewY, currentPageID, null, true); } if (Teleporter.FXTELEPORTER === true && !location[0].get('status_purple')) { spawnFx(obj.get("left"), obj.get("top"), Teleporter.FXTYPE, currentPageID); } obj.set("left", NewX); obj.set("top", NewY); });
1602813619
Pat
Pro
API Scripter
Nope. Have never seen that one before, ever. I took the old one that I'd had before (2018 or so), turned out to be the one Aaron had modified a while back - completely separate and with no knowledge of what you just posted... yeah, this fires a ping, it doesn't try the color swap to hide the ping-ring, which was what I was trying to tackle with ping - and with this one, you set a bunch of macros, I'm not sure how it works exactly. The way I tried to set it up was as a per-teleport-pad-FX coded in the pad - keeping the data in the token object - meaning it's easier to copy a set of pads to another page that have the traits you want without re-programming. Not sure exactly how this one handles teleport all either - I had to tear apart the macro to push the tokens to the GM layer, and only scrape the tokens that had a player id to pull them to the "all" destination... 
1603222154
Finds
API Scripter
That was one I was tinkering with a while back from here <a href="https://app.roll20.net/forum/post/4694539/teleport-script-tweaked" rel="nofollow">https://app.roll20.net/forum/post/4694539/teleport-script-tweaked</a>