Skip to content

Instantly share code, notes, and snippets.

@SplenectomY
Last active December 29, 2018 05:01
Show Gist options
  • Save SplenectomY/58c2e74a062e140d11c988abeb963496 to your computer and use it in GitHub Desktop.
Save SplenectomY/58c2e74a062e140d11c988abeb963496 to your computer and use it in GitHub Desktop.
Performance and functionality improvements for a popular Roll20 script
/* ************ Splen's Easy Doors and Stairs *****************
* 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:
* ex: Two way doors: XXXXXXX02A, XXXXXXXX02B
In this example, "2" refers to the amount of stops.
This number must always match the total amount of
Stops for stairs. See the next example.
* ex: Three floor staircase: stairs03A, stairs03B, stairs03C
Now that same number is "3" since there are three stops.
"A" is the bottom floor, "B" is a middle floor and
"C" is the top floor. The "0" in either example
can be changed if multiple systems with similar names
are used. Everything else except for the last letter
must match in each destination token's name.
* To change the name of a stop (door, stairs, portal, ladder)
Just put the name in the gmnotes of the token.
* This system can handle up to a 9 floor staircase (9I max).
* When a token lands on a teleporter, whoever controls that
token will receive a whisper message in chat with buttons
to either use the object (door, 1-way stairs) or choose
to go up or down (two-way stairs). This is to prevent
unwanted auto-teleporting
* Use the in-game command !AUTOTELEPORTER to toggle this script.
Your settings are saved and should persist across restarts.
****************************************************************/
state.SplY_Teleporter = state.SplY_Teleporter || {};
// Config
state.SplY_Teleporter.Debug = false;
// Verbose logging to console
// End Config
(function() // Begin localization
{
let Teleporter = state.SplY_Teleporter;
Teleporter.GMs = {};
Teleporter.AUTOTELEPORTER = Teleporter.AUTOTELEPORTER || true;
// use the in-game command !AUTOTELEPORTER to toggle this bool
const debug = Teleporter.Debug;
const IsOnTeleporter = function(token, teleporter)
{
var maxLeft = teleporter.get("width") / 2;
var maxTop = teleporter.get("height") / 2;
if (token.get("left") <= teleporter.get("left") + maxLeft
&& token.get("left") >= teleporter.get("left") - maxLeft
&& token.get("top") <= teleporter.get("top") + maxTop
&& token.get("top") >= teleporter.get("top") - maxTop)
return true;
return false;
}
const SendPlayerMessage = function(player, message)
{
if (debug)
{
log("Player = " + player);
log("Displayname =" + player.get("_displayname"));
log("Narrator: /w " + player.get("_displayname") + ' Options: ' + message);
}
if (player.get("_displayname").includes('"'))
{
sendChat("AutoTeleporter", "/w gm Player " + player.get("_displayname")
+ ' has quotation marks (") in their name, which is interefering with '
+ "the AutoTeleport script's functionality. Consider using single quotes (') for this character's name instead!");
sendChat("Narrator", 'A token controlled by ' + player.get("_displayname") + ' has some options: ' + message, null, { noarchive: true });
}
else
sendChat("Narrator", '/w "' + player.get("_displayname") + '" Options: ' + message, null, { noarchive: true });
}
Teleporter.Teleport = function (tokenId, TargetName, pageid, initialTokenId, who)
{
try
{
var location = findObjs(
{
_type: "graphic",
_pageid: pageid,
layer: "gmlayer", //target location MUST be on GM layer
name: TargetName
});
if (location.length == 0)
{ // exit if invalid target location
log("Teleport destination "+ TargetName +" not found for token "+ tokenId)
return;
}
var LocX = location[0].get("left");
var LocY = location[0].get("top");
var tokenToMove = getObj("graphic", tokenId);
if (tokenToMove == undefined || tokenToMove == null)
return;
var initialToken = getObj("graphic", initialTokenId);
if (initialToken == undefined || initialToken == null)
return;
if (!IsOnTeleporter(tokenToMove, initialToken))
{
var player = getObj("player", who);
log("AUTOTELEPORT: Token " + tokenToMove.get("name")
+ " has been moved off the teleporter since the chat buttons were sent. Aborting.");
sendChat("Narrator", '/w "' + player.get("_displayname")
+ '" Your token ' + tokenToMove.get("name")
+ " has moved too far and cannot use that object.");
return;
}
log("Teleporting token "+ tokenToMove.get("name") +" to destination "+ TargetName);
tokenToMove.set("left", LocX);
tokenToMove.set("top", LocY);
}
catch(e)
{
sendChat("Autoteleporter", "/w gm An error occurred. Check the log.");
log(e.message);
}
};
var teleporterLogic = function(obj)
{
if (Teleporter.AUTOTELEPORTER == false || obj.get("layer") != "objects")
return;
var location = findObjs(
{
_pageid: obj.get("_pageid"),
_type: "graphic",
layer: "gmlayer"
});
if (location.length == 0)
return;
var initialToken = null;
_.each(location, function(tok)
{
if (IsOnTeleporter(obj, tok) && tok.get("name") != "")
initialToken = tok;
});
if (initialToken == null)
return;
var CurrName = initialToken.get("name");
const Letters = new Array("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L");
var doorCount = CurrName.substr(CurrName.length - 2, 1);
var currDoor = CurrName.substr(CurrName.length - 1, 1);
var tokenName = CurrName.substr(0, CurrName.length - 3);
var namePlusNumber = CurrName.substr(0, CurrName.length - 2);
if (debug)
log("doorCount is NaN = " + isNaN( parseInt(doorCount) ) );
if (currDoor == "" || doorCount == "" || isNaN( parseInt(doorCount) ) )
return;
var i = Letters.indexOf(currDoor);
var choices = 0;
var type = initialToken.get("gmnotes") != "" ? FixGmNotes(initialToken.get("gmnotes")) : tokenName;
var message = "";
var matchingTokens = [];
_.each(location, function(t)
{
var nameToTest = t.get("name").substr(0, CurrName.length - 2);
if (namePlusNumber === nameToTest && t.get("name") != CurrName)
matchingTokens.push(t);
});
if (debug)
log("matchingTokens amount = " + matchingTokens.length);
if (matchingTokens.length < 1)
return;
if (i == 0)
choices = 1; // bottom of the staircase, can only go up
else if (i == doorCount - 1)
choices = -1; // Top of the staircase, can only go down
else
choices = 0; // Can choose to go up or down
let targName = CurrName.substr(0, CurrName.length - 2) + doorCount;
let msgEnd = ", " + obj.id + ", " + obj.get("_pageid") + ", " + initialToken.id + ")";
if (choices == 0)
{
const dirSelector =
{
"up" : targName + Letters[i + 1],
"down" : targName + Letters[i - 1]
}
_.each(["up","down"], function(direction)
{ message += " [Go " + direction + " the " + type + "](!Teleport " + dirSelector[direction] + msgEnd; });
}
else
message += " [Use the "+ type +"](!Teleport " + targName + Letters[i + choices] + msgEnd;
// Who do we send the message to?
var playersString = obj.get("controlledby");
if (debug)
log("Controlledby = " + obj.get("controlledby"));
var playersArray = playersString.split(",");
var players = [];
if (playersArray.length === 0)
{
log("Couldn't send auto-teleport buttons to token " + obj.get("name") + " because it was assigned control to nobody.");
return;
}
_.each(playersArray, function(p)
{
if (p != "")
{
var player = getObj("player", p);
if (player != null || player != undefined)
players.push(player);
}
});
if (debug)
{
log("players = " + players);
log("players.length = " + players.length);
}
if (players.length > 0)
{ // found some player(s) who control the token. Send the message to them
_.each(players, function(player)
{ SendPlayerMessage(player, message); });
}
else if (obj.get("represents") != null && obj.get("represents") != undefined && obj.get("represents") != "")
{
if (debug)
{
log("AUTOTELEPORT: Token "+ obj.get("name") +" represented by character sheet.");
log("Attempting to find valid player owner of character sheet for messaging ...");
}
var character = getObj("character", obj.get("represents"));
var playersString2 = character.get("controlledby");
if (debug)
log("Character sheet is controlled by: " + playersString2);
var playersArray2 = playersString2.split(",");
var players2 = [];
if (playersArray2.length < 1)
{
log("Could not find any player controllers of the character sheet. Aborting.")
return;
}
_.each(playersArray2, function(p)
{
if (p != "")
{
var player = getObj("player", p);
if (player != null || player != undefined)
players2.push(player);
}
});
_.each(players2, function(player)
{ SendPlayerMessage(player, message); });
}
else // No players who control the token and no represented character sheets. Send the message to a GM
sendChat("AutoTeleport Script", "/w gm A token '" + obj.get("name") + "' can use a nearby "
+ type + ". Options: " + message, null, { noarchive: true });
log("Sent auto-teleport buttons to token " + obj.get("name"));
}
on("ready", function()
{
var players = findObjs({ _type: "player" });
_.each(players, function(p)
{
if (playerIsGM(p.id))
Teleporter.GMs[p.id] = p;
});
on ("change:graphic:lastmove", function(obj)
{
try
{teleporterLogic(obj);}
catch(e)
{
log(e.message);
sendChat("Autoteleporter", "/w gm An error occurred. Check the log.");
}
});
on ("chat:message", function(msg)
{
if (msg.type != "api")
return;
const cmdName = "!Teleport ";
if (msg.content.indexOf(cmdName) !== -1)
{
const cleanedMsg = msg.content.replace(cmdName, "");
const commands = cleanedMsg.split(", ");
const targetName = commands[0];
const pageid = commands[commands.length - 2];
const initialToken = commands[commands.length - 1];
var i = 1;
while (i < commands.length - 2)
{
try
{
log("Attempting to teleport token " + commands[i] + " to " + targetName + initialToken + " on page " + pageid);
Teleporter.Teleport(commands[i], targetName, pageid, initialToken, msg.playerid);
i++;
}
catch(e)
{
log(e.message);
sendChat("Autoteleporter", "/w gm An error occurred. Check the log.");
}
}
}
else if (msg.content.indexOf("!AUTOTELEPORTER") !== -1 && playerIsGM(msg.playerid) )
{
if (Teleporter.AUTOTELEPORTER)
{
sendChat("Autoteleporter", "Autoteleporting Disabled.");
Teleporter.AUTOTELEPORTER = false;
}
else
{
sendChat("Autoteleporter", "Autoteleporting Enabled.");
Teleporter.AUTOTELEPORTER = true;
}
}
});
});
const FixGmNotes = string=>{
const fixNotes = (match, offset, string)=>{
return {
"%20" : " ",
"%21" : "!",
"%28" : "(",
"%29" : ")",
"%3A" : ":",
"%2C" : ",",
"%3Cbr" : "", // <br> line break
"%3E" : "",
"%3Cp" : "", // <p> paragraph
"%3C/p" : "", // </p>
"%22" : '"',
"%27" : "'",
"%3F" : "?",
"%3Cstrong" : "<b>",
"%3C/strong" : "</b>",
"%3Cem" : "<i>",
"%3C/em" : "</i>",
"%3Cul" : "<ul>",
"%3C/ul" : "</ul>",
"%3Cli" : "<li>",
"%3C/li" : "</li>",
"%25" : "%"
}[match]
}
, re = /(%20|%21|%28|%29|%27|%25|%3(?:Cbr|(?:E)%3A|A|E|F|C(?:(?:\/)?strong|(?:\/)?em|(?:\/)?p|(?:\/)?ul|(?:\/)?li|))|%22|%2C)/g;
return string.replace(re, fixNotes);
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment