diff --git a/MapChange/1.7/MapChange.js b/MapChange/1.7/MapChange.js new file mode 100644 index 000000000..1a633b8ef --- /dev/null +++ b/MapChange/1.7/MapChange.js @@ -0,0 +1,1298 @@ +// Github: https://github.com/TheWhiteWolves/MapChange.git +// By: TheWhiteWolves +// Contact: https://app.roll20.net/users/1043/thewhitewolves + +var MapChange = MapChange || (function() { + 'use strict'; + // Defaults. + // Date last modified in unix timestamp format. + var lastModified = "1717542000"; + // Name of the person who last modified the script. + var modifiedBy = "TheWhiteWolves, keithcurtis"; + // Local version of the script. + var version = "1.7"; + // Set to true to use built in debug statements + var debug = false; + // Set to false to turn off notifing the GM when a player moves. + var gmNotify = false; + // The marker used to decide what is placed in the private map. + var marker = "[GM]"; + // The marker used to decide what is placed in the hidden map. + var hideMarker = "[Hide]"; + // When true this places the pages with name containing the marker into the public list. + // Use this if you want maps to be private by default instead of public by default. + var invertedMarker = false; + // Check the installation of the script and setup the default configs. + var checkInstall = function() { + if (!state.MapChange || state.MapChange.version !== version) { + state.MapChange = state.MapChange || {}; + state.MapChange = { + // Version number + version: version, + // Date last modified in unix timestamp format. + lastModified: lastModified, + // Name of the person who last modified the script. + modifiedBy: modifiedBy, + // Timestamp when the global config was last updated. + gcUpdated: 0, + // List of available user configs. + config: { + // Set to true to use built in debug statements + debug: debug, + // Set to false to turn off notifing the GM when a player moves. + gmNotify: gmNotify, + // The marker used to decide what is placed in the private map. + marker: marker, + // The marker used to decide what is placed in the hidden map. + hideMarker: hideMarker, + // When true this places the pages with name containing the marker into the public list. + // Use this if you want maps to be private by default instead of public by default. + invertedMarker: invertedMarker + }, + // These are maps that players are able to move to using the commands. + publicMaps: {}, + // These are maps that only the GM can move people to. + privateMaps: {}, + // These are maps that have been moved to the archived folder. + archiveMaps: {}, + // These are maps that have been marked as hidden. + hiddenMaps: {} + }; + } + // Check if the state doesn't contain the blocked players list. + if (!_.has(state.MapChange, "blockedPlayers")) { + // If it doesn't then initilise it with an empty array. + state.MapChange.blockedPlayers = []; + } + // Load and changes to the defaults from the global config. + loadGlobalConfig(); + }; + + // Loads the config options from the global config. + var loadGlobalConfig = function() { + // Get a reference to the global config. + var gc = (globalconfig && (globalconfig.MapChange || globalconfig.mapchange)); + // Get a reference to the state. + var st = state.MapChange; + // Check if the settings need updating from the global config. + if (gc && gc.lastsaved && gc.lastsaved > st.gcUpdated) { + // Get the last saved time. + st.gcUpdated = gc.lastsaved; + // Get the debug setting from the global config. + st.config.debug = gc['Debug Mode'] == true; + // Get the gmNotify setting from the global config. + st.config.gmNotify = gc['GM Notification'] == true; + // Get the marker setting from the global config. + st.config.marker = gc['Marker'] || "[GM]"; + // Get the hide marker setting from the global config. + st.config.hideMarker = gc['Hide Marker'] || "[Hide]"; + // Get the invertedMarker setting from the global config. + st.config.invertedMarker = gc['Inverted Marker'] == true; + } + // Debug + if (st.config.debug) { + log("State Config:"); + log(st.config); + } + }; + + // Constructs the private and public maps for use in the api script. + var constructMaps = function() { + // Clear the public maps. + state.MapChange.publicMaps = {}; + // Clear the private maps. + state.MapChange.privateMaps = {}; + // Clear the archive maps. + state.MapChange.archiveMaps = {}; + // Clear the hidden maps. + state.MapChange.hiddenMaps = {}; + // Get an object containing all the pages in the campaign. + var pages = findObjs({_type: 'page'}); + // Loop through the pages adding them to their relevent maps. + for (var key in pages) { + if (pages.hasOwnProperty(key)) { + // Get the name of the page that is current being processed. + var name = pages[key].get("name"); + // Get the id of the page that is current being processed. + var id = pages[key].get("_id"); + // Check if the page has been marked as hidden. + if (name.indexOf(state.MapChange.config.hideMarker) > -1) { + // If it has then remove the hidden marker and trim off any whitespace. + name = name.replace(state.MapChange.config.hideMarker, "").trim(); + // Place the name and id in the hidden maps. + state.MapChange.hiddenMaps[name] = id; + } + else { + // Check if the page is an archived page. + if (pages[key].get("archived") === true) { + // If it is then remove the private map marker if it exists and trim off any whitespace. + name = name.replace(state.MapChange.config.marker, "").trim(); + // Place the name and id in the archive maps. + state.MapChange.archiveMaps[name] = id; + } + else { + // Check if the name of the page contains the marker. + if (name.indexOf(state.MapChange.config.marker) > -1) { + // If the name does then remove the marker from the name and trim off any whitespace. + name = name.replace(state.MapChange.config.marker, "").trim(); + // If invertedMarker is being used then place the name and id of the page in the + // public map else place it in the private map. + state.MapChange.config.invertedMarker ? state.MapChange.publicMaps[name] = id : state.MapChange.privateMaps[name] = id; + } + else { + // If the name does not contain the marker then place the name and id in the public map + // if invertedMarker is being used else place it in the private map. + state.MapChange.config.invertedMarker ? state.MapChange.privateMaps[name] = id : state.MapChange.publicMaps[name] = id; + } + } + } + } + } + // Debug + if (state.MapChange.config.debug) { + log("Public:"); + log(state.MapChange.publicMaps); + log("Private:"); + log(state.MapChange.privateMaps); + log("Archived:"); + log(state.MapChange.archiveMaps); + log("Hidden:"); + log(state.MapChange.hiddenMaps); + } + }; + + // Handle the input message call for the api from the chat event. + var handleInput = function(msg) { + // Check that the message sent is for the api, if not return as we don't need to do anything. + if (msg.type !== "api") { + return; + } + // Grab the contents of the msg sent and split it into the individual arguments. + var args = msg.content.split(/\s+--/); + // Parse the first section of the arguments to get an array containing the commands. + var commands = parseCommands(args.shift()); + // Parse the remaining aruments to get any parameters passed in. + var params = parseParameters(args); + // Check the lower cased version of the message to see if it contains the call for + // this script to run, if it doesn't then return. + switch (commands.shift().toLowerCase()) { + case "!mapchange": + case "!mc": + // Check if the sending player is on the list of blocked players and is not a GM. + if (_.contains(state.MapChange.blockedPlayers, msg.playerid) && !playerIsGM(msg.playerid)) { + // Send the player a message + chat("/w", msg.who, "Your GM has blocked you from using commands from MapChange.
Please contact a GM to remove the block."); + } + else { + // Check to see if any further commands were passed in and process them, else + // show the help test on how to use the script. + if (commands.length > 0) { + // Process the remaining commands with the passed in paramters. + processCommands(msg, commands, params); + } + else { + // Show the sender the script help message. + showHelp(msg, "index"); + } + } + break; + default: + // If we reached here it means that the call to the api was not meant for us. + return; + } + }; + + // Parses the commands of the call to the api script. + var parseCommands = function(args) { + if (args === undefined) { + // If it is then return an empty array. + return []; + } + // Split the arguments by spaces and return the array containing them all. + return args.split(/\s+/); + }; + + // Parses the parameters of the call to the api script. + var parseParameters = function(args) { + // Check if args is undefined. + if (args === undefined) { + // If it is then return an empty object. + return {}; + } + // Declare a new object to hold the parameters. + var params = {}; + // Loop through all the passed in arguments and construct them in into the parameters. + for (var param in args) { + if (args.hasOwnProperty(param)) { + // Split the parameter down by spaces and temporarily store it. + var tmp = args[param].split(/\s+/); + // Take the first element in tmp and use it as the parameter name and reassemble the + // remaining elements and replace the commas with spaces for the parameter value. + params[tmp.shift()] = tmp.join().replace(/,/g, " "); + } + } + // Return the constructed object of parameters. + return params; + }; + + // Processes the commands provided in the call to the api script. + var processCommands = function(msg, commands, params) { + // Take the command and decide what function to run. + switch (commands.shift().toLowerCase()) { + case "help": + // Specify the default show behaviour to be "all". + var show = "index"; + // Check to see if the show parameter was provided in the api call. + if (params.hasOwnProperty("show")) { + // If it was then check that it is not empty and if it isn't then change show to + // the value of the parameter. + show = (params.show !== "") ? params.show.toLowerCase() : "index"; + } + // Send the help text to the player who sent the message. + showHelp(msg, show); + break; + case "menu": + // Specify the default show behaviour to be "all". + var show = "all"; + // Check to see if the show parameter was provided in the api call. + if (params.hasOwnProperty("show")) { + // If it was then check that it is not empty and if it isn't then change show to + // the value of the parameter. + show = (params.show !== "") ? params.show.toLowerCase() : "all"; + } + // Show the menu to the sender of the api call with the applicable filters. + showMenu(msg, show); + break; + case "refresh": + // Refresh the public and private maps to pull in any changes made since the script + // was last started. + refresh(msg); + break; + case "move": + // Check to see if the sender has provided and target map for the move, if they + // haven't then send them a chat message to tell them it is missing. + if (params.hasOwnProperty("target")) { + // Check to see if the sender has provided a player to be moved, if they + // haven't then user the id of the sender. + if (params.hasOwnProperty("player")) { + // Move the provided player to the map with the provided name. + move(msg, getPlayerIdFromDisplayName(params.player), params.target); + } + else { + // Move the sender to the map with the provided name. + move(msg, msg.playerid, params.target); + } + } + else { + // Send a chat message to tell teh sender that they missed the target map parameter. + chat("/w", msg.who, "Target map parameter missing, use !mc help to see how to use this script."); + } + break; + case "rejoin": + // Check to see if a player name was provided, if so then run rejoin on that player only if + // the sender is either a GM or the provided player is the sender. + if (params.hasOwnProperty("player")) { + // Check the sender is either a GM or the provided player. + if (playerIsGM(msg.playerid) || params.player === msg.who) { + // Run the rejoin on the provided player. + rejoin(msg, getPlayerIdFromDisplayName(params.player)); + } + else { + // Send a warning to the sender to tell them that they cannot perform the action. + chat("/w", msg.who, "You do not have the permission required to perform that action."); + } + } + else { + // Run rejoin on the sender of the api call. + rejoin(msg, msg.playerid); + } + break; + case "rejoinall": + // Move all the players back to the bookmark. + rejoinall(msg); + break; + case "moveall": + // Move all the players back to the bookmark and then move the bookmark to the map with + // the provided name. + moveall(msg, params.target); + break; + case "block": + // Check if the sender of the message is a GM. + if (playerIsGM(msg.playerid)) { + // If they are then check to see if the params contain the player parameter. + if (params.hasOwnProperty("player")) { + // Toggle the block on the provided player. + block(msg, getPlayerIdFromDisplayName(params.player)); + } + else { + // Toggle the block on the sender of the message + block(msg, msg.playerid); + } + } + else { + // Send a warning to the sender to tell them that they cannot perform the action. + chat("/w", msg.who, "You do not have the permission required to perform that action."); + } + break; + default: + // Show the scripts help text is no further command was provided. + showHelp(msg, "index"); + break; + } + }; + + // Convert the provided display name into the player id for that player. + var getPlayerIdFromDisplayName = function(name) { + // Remove the GM tag from a players name and trim any leftover whitespace + name = name.replace("(GM)", "").trim(); + // Find all the player objects in the campaign. + var players = findObjs({_type: 'player'}); + // Loop through them and try to convert the display name into the player's id. + for (var key in players) { + if (players.hasOwnProperty(key)) { + // Check if the current players display name is equal to the provided one. + if (players[key].get("_displayname") === name) { + // If it is then return that players id. + return players[key].get("_id"); + } + } + } + // If no match was found then return undefined. + return undefined; + }; + + // Convert the provided player id into the display name for that player. + var getDisplayNameFromPlayerId = function(id) { + // Find all the player objects in the campaign. + var players = findObjs({_type: 'player'}) + // Loop through them and try to convert the id into the player's display name. + for (var key in players) { + if (players.hasOwnProperty(key)) { + // Check if the current players id is equal to the provided one. + if (players[key].get("_id") == id) { + // If it is then return that players display name. + return players[key].get("_displayname"); + } + } + } + // If no match was found then return undefined. + return undefined; + }; + + // TODO + var showHelp = function(msg, show) { + // Create the variable to hold the assembled menu text. + var text = ""; + // Assemble the text for the help menu. + if (show === "index") { + // Add the opening tag for the table. + text += ""; + // Add in the header row for the help menu. + text += ""; + // Add a heading row to provide names for the columns. + text += ""; + // Add a row for the menu command. + text += ""; + // Add a row for the move command. + text += ""; + // Check if the calling player is a GM or not. + if (playerIsGM(msg.playerid)) { + // If they are then add a row for the moveall command. + text += ""; + } + // Add a row for the rejoin command. + text += ""; + // Check if the calling player is a GM or not. + if (playerIsGM(msg.playerid)) { + // If they are then add a row for the rejoinall command. + text += ""; + // Add a row for the refresh command. + text += ""; + } + // Add a row for the help command. + text += ""; + // Check if the calling player is a GM or not. + if (playerIsGM(msg.playerid)) { + // Add a row for the block command. + text += ""; + } + // Add the closing tag for the table. + text += "
Help Menu
CommandDescription
menuMenu for running commands.Info
moveMoves a player to a map.Info
moveallMoves all players to a map.Info
rejoinRejoins a player to the bookmark.Info
rejoinallRejoins all players to the bookmark.Info
refreshRefreshes the map lists.Info
helpShows the help for the script.Info
blockToggle blocking of command use.Info
"; + // Add in a blank line to seperate the command information from the general information. + text += "
"; + // Add the opening tag for the table. + text += ""; + // Add a header for the general information table. + text += ""; + // Check if the calling player is a GM or not. + if (playerIsGM(msg.playerid)) { + text += ""; + } + // Add a row for the information on constructing an API call. + text += ""; + // Add a row for the information on using parameters. + text += ""; + // Add a row for the credits. + text += ""; + // Add a row for the version information. + text += ""; + // Add the closing tag for the table. + text += "
General Information:
Configuring MapsInfo
Constructing an API callInfo
Using ParametersInfo
CreditsInfo
VersionInfo
"; + } + // Assemble the text for the menu documentation. + if (show === "menu") { + // Add the opening tag for the table. + text += ""; + // Add in the header row for the move help. + text += ""; + // Add a row for the description header. + text += ""; + // Add a row for the description of the command. + text += ""; + // Add a row for the parameters section headers. + text += ""; + // Add a row for the show parameter. + text += ""; + // Add a row for the example header. + text += ""; + // Add a row with an example and an api button to launch that example. + text += ""; + // Add the closing tag for the table. + text += "
Menu
Description
The menu command provides a menu for the user to launch commands that are available to them.
ParameterDescriptionOptions
--show[Optional]
Used to filter the returned view.
All
Public
" + ((playerIsGM(msg.playerid)) ? "Private
Archive
Hidden
" : "") + "Utilities
Utils
Example
!mc menuShow Me!
"; + // Add in a back button for going back to the menu. + text += navigation("", "move"); + } + // Assemble the text for the move documentation. + if (show === "move") { + // Add the opening tag for the table. + text += ""; + // Add in the header row for the move help. + text += ""; + // Add a row for the description header. + text += ""; + // Add a row for the description of the command. + text += ""; + // Add a row for the parameters section headers. + text += ""; + // Add a row for the target parameter. + text += ""; + // Check if the calling player is a GM or not. + if (playerIsGM(msg.playerid)) { + // If they are then add a row for the player parameter. + text += ""; + } + // Add a row for the example header. + text += ""; + // Add a row with an example and an api button to launch that example. + text += ""; + // Add the closing tag for the table. + text += "
Move
Description
The move command moves a player to the provided target map.
ParameterDescriptionOptions
--target[Required]
The target map to move to.
Name of the Map
--player[Optional]
The player to move.
Player Name
Example
!mc move --target " + _.first(_.keys(state.MapChange.publicMaps)) + "Show Me!
"; + // Add in a back button for going back to the menu. + text += navigation("menu", "moveall"); + } + // Assemble the text for the moveall documentation. + if (show === "moveall") { + // Add the opening tag for the table. + text += ""; + // Add in the header row for the move help. + text += ""; + // Add a row for the description header. + text += ""; + // Add a row for the description of the command. + text += ""; + // Add a row for the parameters section headers. + text += ""; + // Add a row for the target parameter. + text += ""; + // Add a row for the example header. + text += ""; + // Add a row with an example and an api button to launch that example. + text += ""; + // Add the closing tag for the table. + text += "
Moveall
Description
The moveall command moves all players to the provided target map.
ParameterDescriptionOptions
--target[Required]
The target map to move to.
Name of the Map
Example
!mc moveall --target " + _.first(_.keys(state.MapChange.publicMaps)) + "Show Me!
"; + // Add in a back button for going back to the menu. + text += navigation("move", "rejoin"); + } + // Assemble the text for the rejoin documentation. + if (show === "rejoin") { + // Add the opening tag for the table. + text += ""; + // Add in the header row for the move help. + text += ""; + // Add a row for the description header. + text += ""; + // Add a row for the description of the command. + text += ""; + // Add a row for the parameters section headers. + text += ""; + // Check if the calling player is a GM or not. + if (playerIsGM(msg.playerid)) { + // If they are then add a row for the player parameter. + text += ""; + } + // Add a row for the example header. + text += ""; + // Add a row with an example and an api button to launch that example. + text += ""; + // Add the closing tag for the table. + text += "
Rejoin
Description
The rejoin command moves a player back to the bookmark.
ParameterDescriptionOptions
--player[Optional]
The player to move.
Player Name
Example
!mc rejoinShow Me!
"; + // Add in a back button for going back to the menu. + text += navigation("moveall", "rejoinall"); + } + // Assemble the text for the rejoinall documentation. + if (show === "rejoinall") { + // Add the opening tag for the table. + text += ""; + // Add in the header row for the move help. + text += ""; + // Add a row for the description header. + text += ""; + // Add a row for the description of the command. + text += ""; + // Add a row for the parameters section headers. + text += ""; + // Add a row for the example header. + text += ""; + // Add a row with an example and an api button to launch that example. + text += ""; + // Add the closing tag for the table. + text += "
Rejoinall
Description
The rejoinall command moves all players back to the bookmark.
ParameterDescriptionOptions
Example
!mc rejoinallShow Me!
"; + // Add in a back button for going back to the menu. + text += navigation("rejoin", "refresh"); + } + // Assemble the text for the refresh documentation. + if (show === "refresh") { + // Add the opening tag for the table. + text += ""; + // Add in the header row for the move help. + text += ""; + // Add a row for the description header. + text += ""; + // Add a row for the description of the command. + text += ""; + // Add a row for the parameters section headers. + text += ""; + // Add a row for the example header. + text += ""; + // Add a row with an example and an api button to launch that example. + text += ""; + // Add the closing tag for the table. + text += "
Refresh
Description
The refresh command clears and reloads the map lists without needing to restart the script.
ParameterDescriptionOptions
Example
!mc refreshShow Me!
"; + // Add in a back button for going back to the menu. + text += navigation("rejoinall", "help"); + } + // Assemble the text for the help documentation. + if (show === "help") { + // Add the opening tag for the table. + text += ""; + // Add in the header row for the move help. + text += ""; + // Add a row for the description header. + text += ""; + // Add a row for the description of the command. + text += ""; + // Add a row for the parameters section headers. + text += ""; + // Add a row for the show parameter. + text += ""; + // Add a row for the example header. + text += ""; + // Add a row with an example and an api button to launch that example. + text += ""; + // Add the closing tag for the table. + text += "
Help
Description
The help command provides an interactive menu for the documentation of the script.
ParameterDescriptionOptions
--show[Optional]
Used to filter the returned view.
Menu
Move
" + ((playerIsGM(msg.playerid)) ? "Moveall
" : "") + "Rejoin
" + ((playerIsGM(msg.playerid)) ? "Refresh
" : "") + "Help
Api
Params
Credits
Version
Example
!mc helpShow Me!
"; + // Add in a back button for going back to the menu. + text += navigation("refresh", "block"); + } + // + if (show === "block") { + // Add the opening tag for the table. + text += ""; + // Add in the header row for the move help. + text += ""; + // Add a row for the description header. + text += ""; + // Add a row for the description of the command. + text += ""; + // Add a row for the parameters section headers. + text += ""; + // If they are then add a row for the player parameter. + text += ""; + // Add a row for the example header. + text += ""; + // Add a row with an example and an api button to launch that example. + text += ""; + // Add the closing tag for the table. + text += "
Block
Description
The block command toggles the players ability to use MapChange commands.
ParameterDescriptionOptions
--player[Optional]
The player to block/unblock.
Player Name
Example
!mc blockShow Me!
"; + // Add in a back button for going back to the menu. + text += navigation("help", ""); + } + // Assemble the text for the configuring maps documentation. + if (show === "map") { + // Add the opening tag for the table. + text += ""; + // Add in the header row for the configuring maps information. + text += ""; + // Add the decription on how to configure the campaigns maps. + text += ""; + // Add the closing tag for the table. + text += "
Configuring Maps
By default all maps are made public and available for any user to move to, there are a couple of\ + options included in the script to modify this behaviour.

\ + The first option available is to mark a map as private, to do this the GM must include the marker in \ + the maps name, by default this is [GM] (this is also configurable), so for example,\ + if you have a map called Baron Trevis' Keep then you would add the marker to this name\ + to make it [GM] Baron Trevis' Keep, this would then add that map to the private list\ + instead of public.

\ + The second way to modify the behaviour is to invert the map markings, for this the GM must set the\ + Inverted Marker option to true, what this will do is place all maps into the private listing by default\ + instead of the public listings, this then requires the GM to mark a map in the above way to make it public.\ + (this is where changing the marker may be useful).
"; + // Add in a back button for going back to the menu. + text += navigation("", "api"); + } + // Assemble the text for the constructing an API documentation. + if (show === "api") { + // Add the opening tag for the table. + text += ""; + // Add in the header row for the constructing an api call information. + text += ""; + // Add the decription on how to construct an api call. + text += ""; + // Add the closing tag for the table. + text += "
Constructing an API call
An API call in MapChange consists of two required components and one optional component.

\ + The first required component is the call to the script, this is started by using a exclamation \ + mark followed by the script name or alias (e.g. !mc or !mapchange)

\ + The second required component is the command for the script, this must be seperated from the \ + script call marker by using a space. (e.g. !mc help)

\ + Finally the optional component is the parameters for the command you are using, this is started by \ + using two dashes (e.g. --show), note that sometimes a command may allow or require more than one \ + parameter.

As with the command this must be seperated from the command using a space and each \ + parameter must be seperated using a space. (e.g. !mc help --show index)
"; + // Add in a back button for going back to the menu. + text += navigation("map", "params"); + } + // Assemble the text for the using parameters documentation. + if (show === "params") { + // Add the opening tag for the table. + text += ""; + // Add in the header row for the using parameters information. + text += ""; + // Add in a row for the information on parameters. + text += ""; + // Add the closing tag for the table. + text += "
Using Parameters
Parameters in MapChange are composed of three pieces, the first is the Parameter Marker, the second \ + is the Parameter Name and the third is the Parameter Value.

\ + The Parameter Marker consists of two dashes (e.g. --), this allows the script to know that the \ + following text is a parameter.

\ + The Parameter Name is the name that the script will use when applying it to the command (e.g. show), see \ + the help on each command to find out what parameters they accept.

\ + The Parameter Value is the piece of information or option you pass to the script to use with the \ + Parameter.

\ + Some commands only accept a set amount of options whereas others will accept what the user sends and \ + attempt to use it, see the help on each command to find out what can be used with each parameter.
"; + // Add in a back button for going back to the menu. + text += navigation("api", "credits"); + } + // Assemble the text for the credits. + if (show === "credits") { + // Declare the styling for the profile link, this makes it look like an api button. + var buttonStyle = "background-color: #CE0F69; color: white; padding: 6px 6px; text-decoration: none; display: inline-block; font-family: Arial;"; + // Add the opening tag for the table. + text += ""; + // Add in the header row for the credits. + text += ""; + // Add in the header row for the authors. + text += ""; + // Add in in TheWhiteWolves as an author. + text += ""; + // Add in the header row for the testers. + text += ""; + // Add in in WhiteStar as a tester. + text += ""; + // Add in in Kaelev as a tester. + text += ""; + // Add in in Enzo S.as a tester. + text += ""; + // Add the closing tag for the table. + text += "
Credits
Authors
TheWhiteWolvesProfile
Testers
WhiteStarProfile
KaelevProfile
Enzo S.Profile
"; + // Add in a back button for going back to the menu. + text += navigation("params", "version"); + } + // Assemble the text for the version information. + if (show === "version") { + // Add the opening tag for the table. + text += ""; + // Add a row for the version number. + text += ""; + // Add a row for the last modified date and time. + text += ""; + // Add a row for who last modified the script. + text += ""; + // Add the closing tag for the table. + text += "
Version" + state.MapChange.version + "
Last Modified" + new Date(state.MapChange.lastModified * 1000).toUTCString() + "
By" + state.MapChange.modifiedBy + "
"; + // Add in a back button for going back to the menu. + text += navigation("credits", ""); + } + // Send the assembled menu text to the chat to be displayed. + chat("/w", msg.who, text); + // Debug + if (state.MapChange.config.debug) { + log(msg); + log(text); + log(show); + } + }; + + // Displays a chat based menu for the teleportation, this provides users with a set of + // easy to use api buttons in the chat that will launch the commands for them. + var showMenu = function(msg, show) { + // Specify what the max display length of the map names will be on the api buttons. + var displayLength = 20; + // Find all the player objects in the campaign. + var players = findObjs({_type: 'player'}); + // Create the variable to hold the assembled menu text. + var text = ""; + // Check if the show parameter is set to show any of the maps. + if (show === "all" || show === "public" || show === "private" || show === "archive" || show === "hidden") { + // Start off the chat message with the Available Maps title. + text += "Available Maps:"; + } + // Check if the "show" parameter is set to either "all" or "public". + if (show === "all" || show === "public") { + // If it is then check if the calling player is a GM or not. + if (playerIsGM(msg.playerid)) { + // If they are then add a row for the Public title. + text += "Public"; + } + // Loop through the map displaying an api button for each one. + for (var key in state.MapChange.publicMaps) { + if (state.MapChange.publicMaps.hasOwnProperty(key)) { + // Add a tag to open start a row on the table. + text += ""; + // Generate an api button with the map name that will teleport the user to that map. + // If the map name is longer than 20 characters then trim it and add an elipse. + text += "" + ((key.length > displayLength) ? key.substr(0, displayLength) + "..." : key) + ""; + // Check if the calling player is a GM or not. + if (playerIsGM(msg.playerid)) { + // If they are then add extra GM only buttons. + // Add a button to teleport all players to the chosen map. + text += "All"; + // Add a button to teleport a differnet player to the chosen map. + text += "Other"; + } + // Add a closing tag to finish the row in the table. + text += ""; + } + } + } + // Check if the "show" parameter is set to either "all" or "private". + if (show === "all" || show === "private") { + // If it is then check if the calling player is a GM or not. + if (playerIsGM(msg.playerid)) { + // If they are then add a row for the Private title.. + text += "Private"; + // Loop through the map displaying an api button for each one. + for (var key in state.MapChange.privateMaps) { + if (state.MapChange.privateMaps.hasOwnProperty(key)) { + // Add a tag to open start a row on the table. + text += ""; + // Generate an api button with the map name that will teleport the user to that map. + // If the map name is longer than 20 characters then trim it and add an elipse. + text += "" + ((key.length > displayLength) ? key.substr(0, displayLength) + "..." : key) + ""; + // Add a button to teleport all players to the chosen map. + text += "All"; + // Add a button to teleport a differnet player to the chosen map. + text += "Other"; + + // Add a closing tag to finish the row in the table. + text += ""; + } + } + } + } + // Check if the "show" parameter is set to "archive". + if (show === "archive") { + // If it is then check if the calling player is a GM or not. + if (playerIsGM(msg.playerid)) { + // If they are then add a row for the Archive title.. + text += "Archive"; + // Loop through the map displaying an api button for each one. + for (var key in state.MapChange.archiveMaps) { + if (state.MapChange.archiveMaps.hasOwnProperty(key)) { + // Add a tag to open start a row on the table. + text += ""; + // Generate an api button with the map name that will teleport the user to that map. + // If the map name is longer than 20 characters then trim it and add an elipse. + text += "" + ((key.length > displayLength) ? key.substr(0, displayLength) + "..." : key) + ""; + // Add a button to teleport all players to the chosen map. + text += "All"; + // Add a button to teleport a differnet player to the chosen map. + text += "Other"; + + // Add a closing tag to finish the row in the table. + text += ""; + } + } + } + } + else { + // Check if the "show" parameter is set to "all". + if (show === "all") { + // If it is then check if the calling player is a GM or not. + if (playerIsGM(msg.playerid)) { + // If they are then add a row for the Private title.. + text += "Archive"; + // Add a row with a placeholder button for the archived maps. + text += "List All Archive Maps"; + } + } + } + // Check if the "show" parameter is set to "hidden". + if (show === "hidden") { + // If it is then check if the calling player is a GM or not. + if (playerIsGM(msg.playerid)) { + // If they are then add a row for the Hidden title. + text += "Hidden"; + // Loop through the map displaying an api button for each one. + for (var key in state.MapChange.hiddenMaps) { + if (state.MapChange.hiddenMaps.hasOwnProperty(key)) { + // Add a tag to open start a row on the table. + text += ""; + // Generate an api button with the map name that will teleport the user to that map. + // If the map name is longer than 20 characters then trim it and add an elipse. + text += "" + ((key.length > displayLength) ? key.substr(0, displayLength) + "..." : key) + ""; + // Add a button to teleport all players to the chosen map. + text += "All"; + // Add a button to teleport a differnet player to the chosen map. + text += "Other"; + + // Add a closing tag to finish the row in the table. + text += ""; + } + } + } + } + else { + // Check if the "show" parameter is set to "all". + if (show === "all") { + // If it is then check if the calling player is a GM or not. + if (playerIsGM(msg.playerid)) { + // If they are then add a row for the Private title.. + text += "Hidden"; + // Add a row with a placeholder button for the archived maps. + text += "List All Hidden Maps"; + } + } + } + // Check to see if the text is currently empty. + if (text !== "") { + // If it isn't then wrap the text within a set of table tags. + text = "" + text + "
"; + } + // Check to see if the filter is set to display all. + if (show === "all" || show === "archive" || show === "hidden") { + // Add in a blank line to seperate the menus. + text += "
"; + } + // Check if the "show" paramter is set to either "all" or "utilities"/"utils". + if (show === "all" || show === "utilities" || show === "utils" || show === "archive" || show === "hidden") { + // Add a table to start a new table. + text += ""; + // Add in the title for the utilities section. + text += ""; + // Add a tag to start a new row for the utility commands. + text += ""; + // Add an api button for the rejoin command. + text += ""; + // Check if the caller is a GM or not. + if (playerIsGM(msg.playerid)) { + // Add an api button for the GM to force all players to rejoin the bookmark. + text += "" + // Add an api button for the GM to force rejoin another player to the bookmark. + text += ""; + // If they are then add an api button for the map refresh command. + text += ""; + } + // Check if the caller is a GM or not. + if (!playerIsGM(msg.playerid)) { + // Add an api button for the help command. + text += ""; + } + // Add the closing tag of the last row. + text += ""; + // Check if the caller is a GM or not. + if (playerIsGM(msg.playerid)) { + // Add the opening tag for a new row. + text += ""; + // Add an api button for toggling the block on a player. + text += ""; + // Add an api button for the help command. + text += ""; + } + // Add a tag to close the table. + text += "
Utilities:
RejoinAllOtherRefreshHelp
Toggle BlockHelp
"; + } + // Debug + if (state.MapChange.config.debug) { + log(show); + log(text); + } + // Send the assembled menu text to the chat to be displayed. + chat("/w", msg.who, text); + }; + + // Refreshes the maps without needing to restart the script. + var refresh = function(msg) { + log("Refreshing Maps..."); + // Clear out the public maps. + state.MapChange.publicMaps = {}; + // Clear out the private maps. + state.MapChange.privateMaps = {}; + // Clear out the archive maps. + state.MapChange.archiveMaps = {}; + // Clear out the hidden maps. + state.MapChange.hiddenMaps = {}; + // Debug + if (state.MapChange.config.debug) { + log("Clear Public:"); + log(state.MapChange.publicMaps); + log("Clear Private:"); + log(state.MapChange.privateMaps); + log("Clear Archived:"); + log(state.MapChange.archiveMaps); + log("Clear Hidden:"); + log(state.MapChange.hiddenMaps); + } + // Reassemble the maps. + constructMaps(); + log("Refresh Complete"); + // Check if the GM should be notified. + if (state.MapChange.config.gmNotify) { + // If they should then send them a message. + chat("/w", "gm", "Map Refresh Complete"); + } + // Debug + if (state.MapChange.config.debug) { + log("Rebuilt Public:"); + log(state.MapChange.publicMaps); + log("Rebuilt Private:"); + log(state.MapChange.privateMaps); + log("Rebuilt Archived:"); + log(state.MapChange.archiveMaps); + log("Rebuilt Hidden:"); + log(state.MapChange.hiddenMaps); + } + }; + + // Moves a player to the specified map. + var move = function(msg, sender, target) { + var pages = findObjs({_type: 'page'}); + var playerPages = Campaign().get("playerspecificpages"); + var differentSender = false; + + if (msg.playerid != sender) { + differentSender = true; + } + + if (playerPages === false) { + playerPages = {}; + } + + if (target in state.MapChange.publicMaps) { + // Move player. + if (sender in playerPages) { + delete playerPages[sender]; + } + playerPages[sender] = state.MapChange.publicMaps[target]; + + if (state.MapChange.config.gmNotify) { + var playerAddition = ((differentSender) ? getDisplayNameFromPlayerId(sender) + " " : ""); + chat("/w", "gm", msg.who.replace("(GM)", "") + " has moved " + playerAddition + "to " + target); + } + } + else if (target in state.MapChange.privateMaps) { + if (playerIsGM(msg.playerid)) { + // Move player. + if (sender in playerPages) { + delete playerPages[sender]; + } + playerPages[sender] = state.MapChange.privateMaps[target]; + + if (state.MapChange.config.gmNotify) { + var playerAddition = ((differentSender) ? getDisplayNameFromPlayerId(sender) + " " : ""); + chat("/w", "gm", msg.who.replace("(GM)", "") + " has moved " + playerAddition + "to " + target); + } + } + } + else if (target in state.MapChange.archiveMaps) { + if (playerIsGM(msg.playerid)) { + // Move player. + if (sender in playerPages) { + delete playerPages[sender]; + } + playerPages[sender] = state.MapChange.archiveMaps[target]; + + if (state.MapChange.config.gmNotify) { + var playerAddition = ((differentSender) ? getDisplayNameFromPlayerId(sender) + " " : ""); + chat("/w", "gm", msg.who.replace("(GM)", "") + " has moved " + playerAddition + "to " + target); + } + } + } + else if (target in state.MapChange.hiddenMaps) { + if (playerIsGM(msg.playerid)) { + // Move player. + if (sender in playerPages) { + delete playerPages[sender]; + } + playerPages[sender] = state.MapChange.hiddenMaps[target]; + + if (state.MapChange.config.gmNotify) { + var playerAddition = ((differentSender) ? getDisplayNameFromPlayerId(sender) + " " : ""); + chat("/w", "gm", msg.who.replace("(GM)", "") + " has moved " + playerAddition + "to " + target); + } + } + } + else { + // Report Map not found. + chat("/w", msg.who, "Map " + target + " not found"); + } + + Campaign().set("playerspecificpages", false); + Campaign().set("playerspecificpages", playerPages); + }; + + var rejoin = function(msg, sender) { + var playerPages = Campaign().get("playerspecificpages"); + var differentSender = false; + + if (msg.playerid != sender) { + differentSender = true; + } + + if (playerPages !== false) { + if (sender in playerPages) { + delete playerPages[sender]; + Campaign().set("playerspecificpages", false); + } + } + if (_.isEmpty(playerPages)) { + playerPages = false; + } + Campaign().set("playerspecificpages", playerPages); + + if (state.MapChange.config.gmNotify) { + if (differentSender) { + chat("/w", "gm", msg.who.replace("(GM)", "") + " has rejoined " + getDisplayNameFromPlayerId(sender) + " with the bookmark") + } + else { + chat("/w", "gm", msg.who.replace("(GM)", "") + " has rejoined the bookmark"); + } + } + }; + + var rejoinall = function(msg) { + if (playerIsGM(msg.playerid)) { + Campaign().set("playerspecificpages", false); + + if (state.MapChange.config.gmNotify) { + chat("/w", "gm", "All players have rejoined the bookmark"); + } + } + }; + + // Add teh archive maps move in here + var moveall = function(msg, target) { + if (playerIsGM(msg.playerid)) { + var bookmarkPage = Campaign().get("playerpageid"); + if (target in state.MapChange.publicMaps) { + Campaign().set("playerspecificpages", false); + bookmarkPage = state.MapChange.publicMaps[target]; + + if (state.MapChange.config.gmNotify) { + chat("/w", "gm", "All players have moved to " + target); + } + } + else if (target in state.MapChange.privateMaps) { + Campaign().set("playerspecificpages", false); + bookmarkPage = state.MapChange.privateMaps[target]; + + if (state.MapChange.config.gmNotify) { + chat("/w", "gm", "All players have moved to " + target); + } + } + else if (target in state.MapChange.archiveMaps) { + Campaign().set("playerspecificpages", false); + bookmarkPage = state.MapChange.archiveMaps[target]; + + if (state.MapChange.config.gmNotify) { + chat("/w", "gm", "All players have moved to " + target); + } + } + else if (target in state.MapChange.hiddenMaps) { + Campaign().set("playerspecificpages", false); + bookmarkPage = state.MapChange.hiddenMaps[target]; + + if (state.MapChange.config.gmNotify) { + chat("/w", "gm", "All players have moved to " + target); + } + } + else { + chat("/w", msg.who, "Map " + target + " not found"); + } + + Campaign().set("playerpageid", bookmarkPage); + } + }; + + var block = function(msg, player) { + // Check if the sender is a GM. + if (playerIsGM(msg.playerid)) { + // Check if the block players list contains the provided player. + if (_.contains(state.MapChange.blockedPlayers, player)) { + // Find the index of the player in the list. + var index = _.indexOf(state.MapChange.blockedPlayers, player); + // Remove the player from the list. + state.MapChange.blockedPlayers = state.MapChange.blockedPlayers.splice(index - 1, index); + // Check if the GM should be notified. + if (state.MapChange.config.gmNotify) { + // Send a message to the GM to tell them that a player has been unblocked. + chat("/w", "gm", "Unblocked " + getDisplayNameFromPlayerId(player) + " from using commands."); + } + } + else { + // Add the player to the blocked player list. + state.MapChange.blockedPlayers.push(player); + // Check if the GM should be notified. + if (state.MapChange.config.gmNotify) { + // Send a message to the GM to tell them that a player has been unblocked. + chat("/w", "gm", "Blocked " + getDisplayNameFromPlayerId(player) + " from using commands."); + } + } + } + // Debug + if (state.MapChange.config.debug) { + log(state.MapChange.blockedPlayers); + } + }; + + var chat = function(type, who, message) { + who = who.split(" ")[0].replace(" (GM)", ""); + sendChat("MapChange", type + " " + who + " " + message, {noarchive:true}); + }; + + var navigation = function(prev, next) { + // Create a varaible to hold the total colspan for the title bar. + var colspan = 1; + // Check if prev is not empty. + if (prev !== "") { + // If it is then increment colspan. + colspan += 1; + } + // Check if next is not empty. + if (next !== "") { + // If it is then increment colspan. + colspan += 1; + } + // Add in a blank line to seperate the information from the back button. + var text = "
"; + // Add in a new table for the back button. + text += ""; + // Add in the title row for the navigation bar. + text += ""; + // Add the opening tag for the row. + text += ""; + // Check if prev is not empty. + if (prev !== "") { + // If it is then add a table element in for the previous button. + text += ""; + } + // Add in the row for the back button. + text += ""; + // Check if next is not empty. + if (next !== "") { + // If it is then add a table element in for the next button. + text += ""; + } + // Add the closing tag for the table. + text += "
Navigation
PreviousBackNext
"; + // Return the assembled text. + return text; + }; + + var registerEventHandlers = function() { + on('chat:message', handleInput); + }; + + return { + ConstructMaps: constructMaps, + RegisterEventHandlers: registerEventHandlers, + CheckInstall: checkInstall + }; +}()); + +on("ready", function() { + 'use strict'; + log("globalconfig: ") + log(globalconfig) + // Load in the global config settings. + MapChange.CheckInstall(); + // If it is then log out the map construction. + log("Map Change Started"); + log("Blocked Players"); + log(state.MapChange.blockedPlayers); + MapChange.ConstructMaps(); + log("Maps Constructed"); + MapChange.RegisterEventHandlers(); + log("Map Change Ready"); + // If it is then send a message to the GM to tell them the script is ready. + //Commented this out, since it produces a string of chat spam. - keithcurtis + //sendChat("Map Change", "/w gm Map Change Ready"); +}); diff --git a/MapChange/MapChange.js b/MapChange/MapChange.js index a6bdad36f..1a633b8ef 100644 --- a/MapChange/MapChange.js +++ b/MapChange/MapChange.js @@ -6,11 +6,11 @@ var MapChange = MapChange || (function() { 'use strict'; // Defaults. // Date last modified in unix timestamp format. - var lastModified = "1717196400"; + var lastModified = "1717542000"; // Name of the person who last modified the script. var modifiedBy = "TheWhiteWolves, keithcurtis"; // Local version of the script. - var version = "1.6"; + var version = "1.7"; // Set to true to use built in debug statements var debug = false; // Set to false to turn off notifing the GM when a player moves. @@ -79,15 +79,15 @@ var MapChange = MapChange || (function() { // Get the last saved time. st.gcUpdated = gc.lastsaved; // Get the debug setting from the global config. - st.config.debug = gc['Debug Mode'] === true; + st.config.debug = gc['Debug Mode'] == true; // Get the gmNotify setting from the global config. - st.config.gmNotify = gc['GM Notification'] === true; + st.config.gmNotify = gc['GM Notification'] == true; // Get the marker setting from the global config. st.config.marker = gc['Marker'] || "[GM]"; // Get the hide marker setting from the global config. st.config.hideMarker = gc['Hide Marker'] || "[Hide]"; // Get the invertedMarker setting from the global config. - st.config.invertedMarker = gc['Inverted Marker'] === true; + st.config.invertedMarker = gc['Inverted Marker'] == true; } // Debug if (st.config.debug) { diff --git a/MapChange/script.json b/MapChange/script.json index a4c8eef6b..111eae07c 100644 --- a/MapChange/script.json +++ b/MapChange/script.json @@ -1,7 +1,7 @@ { "name": "MapChange", "script": "MapChange.js", - "version": "1.6", + "version": "1.7", "description": "Provides a way for users to move between maps in a campaign without the GM needing to move them.\n\nFor full information on how to use the script, use the command '!mc help' in game.", "authors": "TheWhiteWolves", "roll20userid": "1043", @@ -9,13 +9,15 @@ "useroptions": [{ "name": "Debug Mode", "type": "checkbox", - "description": "Set to true to use built in debug statements", - "value": "false" + "value": "true", + "checked": "unchecked", + "description": "Set to true to use built in debug statements" }, { "name": "GM Notification", "type": "checkbox", - "description": "Set to true to turn on notifing the GM when events happen.", - "value": "false" + "value": "true", + "checked": "unchecked", + "description": "Set to true to turn on notifing the GM when events happen." }, { "name": "Marker", "type": "text", @@ -29,8 +31,9 @@ }, { "name": "Inverted Marker", "type": "checkbox", - "description": "When true this places the pages with name containing the marker into the public list.\nUse this if you want maps to be private by default instead of public by default.", - "value": "false" + "value": "true", + "checked": "unchecked", + "description": "When true this places the pages with name containing the marker into the public list.\nUse this if you want maps to be private by default instead of public by default." }], "dependencies": [], "modifies": { @@ -44,5 +47,5 @@ "state.MapChange": "read,write" }, "conflicts": [], - "previousversions": ["1.0","1.1","1.2","1.3","1.4",1.5] + "previousversions": ["1.0","1.1","1.2","1.3","1.4","1.5","1.6"] }