diff --git a/README.md b/README.md
index d33438f606..0ce4d8a094 100644
--- a/README.md
+++ b/README.md
@@ -1,147 +1,154 @@
-
-##Citadel Station 13
-Based and maintained from /tg/station.
-
-[](https://travis-ci.org/Citadel-Station-13/Citadel-Station-13) [](http://www.krihelinator.xyz)
-
-[](http://isitmaintained.com/project/Citadel-Station-13/Citadel-Station-13 "Percentage of issues still open") [](http://isitmaintained.com/project/Citadel-Station-13/Citadel-Station-13 "Average time to resolve an issue")
-
-**Upstream Information**
-**Website:** http://www.tgstation13.org
-**Code:** https://github.com/tgstation/tgstation
-**Wiki** http://tgstation13.org/wiki/Main_Page
-**IRC:** irc://irc.rizon.net/coderbus or if you dont have an IRC client, you can click [here](https://kiwiirc.com/client/irc.rizon.net:6667/?&theme=cli#coderbus).
-
-**Citadel Station Information**
-**Forums:** http://citadel-station.net/forum/
-**Ban Appeals:** http://citadel-station.net/forum/forumdisplay.php?fid=8
-**Code:** https://github.com/Citadel-Station-13/Citadel-Station-13
-**Discord:** [Here](https://discord.gg/3gJ9pnM).
-
-## DOWNLOADING
-
-There are a number of ways to download the source code. Some are described here, an alternative all-inclusive guide is also located at http://www.tgstation13.org/wiki/Downloading_the_source_code
-
-Option 1:
-Follow this: http://www.tgstation13.org/wiki/Setting_up_git
-
+
+##Citadel Station 13
+Based and maintained from /tg/station.
+
+[](https://travis-ci.org/Citadel-Station-13/Citadel-Station-13) [](http://www.krihelinator.xyz)
+
+[](http://isitmaintained.com/project/Citadel-Station-13/Citadel-Station-13 "Percentage of issues still open") [](http://isitmaintained.com/project/Citadel-Station-13/Citadel-Station-13 "Average time to resolve an issue")
+
+**Upstream Information**
+**Website:** http://www.tgstation13.org
+**Code:** https://github.com/tgstation/tgstation
+**Wiki** http://tgstation13.org/wiki/Main_Page
+**IRC:** irc://irc.rizon.net/coderbus or if you dont have an IRC client, you can click [here](https://kiwiirc.com/client/irc.rizon.net:6667/?&theme=cli#coderbus).
+
+**Citadel Station Information**
+**Forums:** http://citadel-station.net/forum/
+**Ban Appeals:** http://citadel-station.net/forum/forumdisplay.php?fid=8
+**Code:** https://github.com/Citadel-Station-13/Citadel-Station-13
+**Discord:** [Here](https://discord.gg/3gJ9pnM).
+
+## DOWNLOADING
+
+There are a number of ways to download the source code. Some are described here, an alternative all-inclusive guide is also located at http://www.tgstation13.org/wiki/Downloading_the_source_code
+
+Option 1:
+Follow this: http://www.tgstation13.org/wiki/Setting_up_git
+
Option 2: Download the source code as a zip by clicking the ZIP button in the
-code tab of https://github.com/tgstation/tgstation
-(note: this will use a lot of bandwidth if you wish to update and is a lot of
-hassle if you want to make any changes at all, so it's not recommended.)
-
-## INSTALLATION
-
-First-time installation should be fairly straightforward. First, you'll need
-BYOND installed. You can get it from http://www.byond.com/. Once you've done
-that, extract the game files to wherever you want to keep them. This is a
-sourcecode-only release, so the next step is to compile the server files.
-Open tgstation.dme by double-clicking it, open the Build menu, and click
-compile. This'll take a little while, and if everything's done right you'll get
-a message like this:
-
-```
-saving tgstation.dmb (DEBUG mode)
-tgstation.dmb - 0 errors, 0 warnings
-```
-
-If you see any errors or warnings, something has gone wrong - possibly a corrupt
-download or the files extracted wrong. If problems persist, ask for assistance
-in irc://irc.rizon.net/coderbus
-
-Once that's done, open up the config folder. You'll want to edit config.txt to
-set the probabilities for different gamemodes in Secret and to set your server
-location so that all your players don't get disconnected at the end of each
-round. It's recommended you don't turn on the gamemodes with probability 0,
-except Extended, as they have various issues and aren't currently being tested,
-so they may have unknown and bizarre bugs. Extended is essentially no mode, and
-isn't in the Secret rotation by default as it's just not very fun.
-
-You'll also want to edit config/admins.txt to remove the default admins and add
-your own. "Game Master" is the highest level of access, and probably the one
-you'll want to use for now. You can set up your own ranks and find out more in
-config/admin_ranks.txt
-
-The format is
-
-```
-byondkey = Rank
-((i.e. spessmans = Host ))
-```
-
-where the admin rank must be properly capitalised.
-
-Finally, to start the server, run Dream Daemon and enter the path to your
-compiled tgstation.dmb file. Make sure to set the port to the one you
-specified in the config.txt, and set the Security box to 'Safe'. Then press GO
-and the server should start up and be ready to join. It is also recommended that
-you set up the SQL backend (see below).
-
-## UPDATING
-
-To update an existing installation, first back up your /config and /data folders
-as these store your server configuration, player preferences and banlist.
-
-Then, extract the new files (preferably into a clean directory, but updating in
-place should work fine), copy your /config and /data folders back into the new
-install, overwriting when prompted except if we've specified otherwise, and
-recompile the game. Once you start the server up again, you should be running
-the new version.
-
-## MAPS
-
-Citadel Station maintains their own map, but frequently uses /tg/station's currently maintained maps as well.
-
-* [tgstation2 (default)](http://tgstation13.org/wiki/Boxstation)
-* [MetaStation](https://tgstation13.org/wiki/MetaStation)
-* [DeltaStation](https://tgstation13.org/wiki/DeltaStation)
-* [OmegaStation](https://tgstation13.org/wiki/OmegaStation)
-* [PubbyStation](https://tgstation13.org/wiki/PubbyStation)
-
-
-All maps have their own code file that is in the base of the _maps directory. Maps are loaded dynamically when the game starts. Follow this guideline when adding your own map, to your fork, for easy compatibility.
-
-The map that will be loaded for the upcoming round is determined by reading data/next_map.json, which is a copy of the json files found in the _maps tree. If this file does not exist, the default map from config/maps.txt will be loaded. Failing that, tgstation2 will be loaded. If you want to set a specific map to load next round you can use the Change Map verb in game before restarting the server or copy a json from _maps to data/next_map.json before starting the server. Also, for debugging purposes, ticking a corresponding map's code file in Dream Maker will force that map to load every round.
-
-If you are hosting a server, and want randomly picked maps to be played each round, you can enable map rotation in [config.txt](config/config.txt) and then set the maps to be picked in the [maps.txt](config/maps.txt) file.
-
-Anytime you want to make changes to a map it's imperative you use the [Map Merging tools](http://tgstation13.org/wiki/Map_Merger)
-
-## AWAY MISSIONS
-
-/tg/station supports loading away missions however they are disabled by default.
-
-Map files for away missions are located in the _maps/RandomZLevels directory. Each away mission includes it's own code definitions located in /code/modules/awaymissions/mission_code. These files must be included and compiled with the server beforehand otherwise the server will crash upon trying to load away missions that lack their code.
-
-To enable an away mission open `config/awaymissionconfig.txt` and uncomment one of the .dmm lines by removing the #. If more than one away mission is uncommented then the away mission loader will randomly select one the enabled ones to load.
-
-## SQL SETUP
-
-The SQL backend requires a MySQL server. SQL is required for the library, stats tracking, admin notes, and job-only bans, among other features, mostly related to server administration. Your server details go in /config/dbconfig.txt, and the SQL schema is in /SQL/tgstation_schema.sql and /SQL/tgstation_schema_prefix.sql depending on if you want table prefixes. More detailed setup instructions are located here: http://www.tgstation13.org/wiki/Downloading_the_source_code#Setting_up_the_database
-
-## IRC BOT SETUP
-
-Included in the repository is a python3 compatible IRC bot capable of relaying adminhelps to a specified
-IRC channel/server, see the /bot folder for more
-
-## CONTRIBUTING
-
-Please see [CONTRIBUTING.md](.github/CONTRIBUTING.md)
-
-## LICENSE
-
-All code after [commit 333c566b88108de218d882840e61928a9b759d8f on 2014/31/12 at 4:38 PM PST](https://github.com/tgstation/tgstation/commit/333c566b88108de218d882840e61928a9b759d8f) is licensed under [GNU AGPL v3](http://www.gnu.org/licenses/agpl-3.0.html).
-
-All code before [commit 333c566b88108de218d882840e61928a9b759d8f on 2014/31/12 at 4:38 PM PST](https://github.com/tgstation/tgstation/commit/333c566b88108de218d882840e61928a9b759d8f) is licensed under [GNU GPL v3](https://www.gnu.org/licenses/gpl-3.0.html).
-(Including tools unless their readme specifies otherwise.)
-
-See LICENSE-AGPLv3.txt and LICENSE-GPLv3.txt for more details.
-
-tgui clientside is licensed as a subproject under the MIT license.
-Font Awesome font files, used by tgui, are licensed under the SIL Open Font License v1.1
-tgui assets are licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/).
-
-See tgui/LICENSE.md for the MIT license.
-See tgui/assets/fonts/SIL-OFL-1.1-LICENSE.md for the SIL Open Font License.
-
-All assets including icons and sound are under a [Creative Commons 3.0 BY-SA license](http://creativecommons.org/licenses/by-sa/3.0/) unless otherwise indicated.
+code tab of https://github.com/tgstation/tgstation
+(note: this will use a lot of bandwidth if you wish to update and is a lot of
+hassle if you want to make any changes at all, so it's not recommended.)
+
+## INSTALLATION
+
+First-time installation should be fairly straightforward. First, you'll need
+BYOND installed. You can get it from http://www.byond.com/. Once you've done
+that, extract the game files to wherever you want to keep them. This is a
+sourcecode-only release, so the next step is to compile the server files.
+Open tgstation.dme by double-clicking it, open the Build menu, and click
+compile. This'll take a little while, and if everything's done right you'll get
+a message like this:
+
+```
+saving tgstation.dmb (DEBUG mode)
+tgstation.dmb - 0 errors, 0 warnings
+```
+
+If you see any errors or warnings, something has gone wrong - possibly a corrupt
+download or the files extracted wrong. If problems persist, ask for assistance
+in irc://irc.rizon.net/coderbus
+
+Once that's done, open up the config folder. You'll want to edit config.txt to
+set the probabilities for different gamemodes in Secret and to set your server
+location so that all your players don't get disconnected at the end of each
+round. It's recommended you don't turn on the gamemodes with probability 0,
+except Extended, as they have various issues and aren't currently being tested,
+so they may have unknown and bizarre bugs. Extended is essentially no mode, and
+isn't in the Secret rotation by default as it's just not very fun.
+
+You'll also want to edit config/admins.txt to remove the default admins and add
+your own. "Game Master" is the highest level of access, and probably the one
+you'll want to use for now. You can set up your own ranks and find out more in
+config/admin_ranks.txt
+
+The format is
+
+```
+byondkey = Rank
+```
+
+where the admin rank must be properly capitalised.
+
+Finally, to start the server, run Dream Daemon and enter the path to your
+compiled tgstation.dmb file. Make sure to set the port to the one you
+specified in the config.txt, and set the Security box to 'Safe'. Then press GO
+and the server should start up and be ready to join. It is also recommended that
+you set up the SQL backend (see below).
+
+## UPDATING
+
+To update an existing installation, first back up your /config and /data folders
+as these store your server configuration, player preferences and banlist.
+
+Then, extract the new files (preferably into a clean directory, but updating in
+place should work fine), copy your /config and /data folders back into the new
+install, overwriting when prompted except if we've specified otherwise, and
+recompile the game. Once you start the server up again, you should be running
+the new version.
+
+## HOSTING
+
+If you'd like a more robust server hosting option for tgstation and its
+derivatives. Check out our server tools suite at
+https://github.com/tgstation/tgstation-server
+
+## MAPS
+
+/tg/station currently comes equipped with five maps.
+
+* [BoxStation (default)](http://tgstation13.org/wiki/Boxstation)
+* [MetaStation](https://tgstation13.org/wiki/MetaStation)
+* [DeltaStation](https://tgstation13.org/wiki/DeltaStation)
+* [OmegaStation](https://tgstation13.org/wiki/OmegaStation)
+* [PubbyStation](https://tgstation13.org/wiki/PubbyStation)
+
+
+All maps have their own code file that is in the base of the _maps directory. Maps are loaded dynamically when the game starts. Follow this guideline when adding your own map, to your fork, for easy compatibility.
+
+The map that will be loaded for the upcoming round is determined by reading data/next_map.json, which is a copy of the json files found in the _maps tree. If this file does not exist, the default map from config/maps.txt will be loaded. Failing that, tgstation2 will be loaded. If you want to set a specific map to load next round you can use the Change Map verb in game before restarting the server or copy a json from _maps to data/next_map.json before starting the server. Also, for debugging purposes, ticking a corresponding map's code file in Dream Maker will force that map to load every round.
+
+If you are hosting a server, and want randomly picked maps to be played each round, you can enable map rotation in [config.txt](config/config.txt) and then set the maps to be picked in the [maps.txt](config/maps.txt) file.
+
+Anytime you want to make changes to a map it's imperative you use the [Map Merging tools](http://tgstation13.org/wiki/Map_Merger)
+
+## AWAY MISSIONS
+
+/tg/station supports loading away missions however they are disabled by default.
+
+Map files for away missions are located in the _maps/RandomZLevels directory. Each away mission includes it's own code definitions located in /code/modules/awaymissions/mission_code. These files must be included and compiled with the server beforehand otherwise the server will crash upon trying to load away missions that lack their code.
+
+To enable an away mission open `config/awaymissionconfig.txt` and uncomment one of the .dmm lines by removing the #. If more than one away mission is uncommented then the away mission loader will randomly select one the enabled ones to load.
+
+## SQL SETUP
+
+The SQL backend requires a MySQL server. SQL is required for the library, stats tracking, admin notes, and job-only bans, among other features, mostly related to server administration. Your server details go in /config/dbconfig.txt, and the SQL schema is in /SQL/tgstation_schema.sql and /SQL/tgstation_schema_prefix.sql depending on if you want table prefixes. More detailed setup instructions are located here: http://www.tgstation13.org/wiki/Downloading_the_source_code#Setting_up_the_database
+
+## IRC BOT SETUP
+
+Included in the repository is a python3 compatible IRC bot capable of relaying adminhelps to a specified
+IRC channel/server, see the /tools/minibot folder for more
+
+## CONTRIBUTING
+
+Please see [CONTRIBUTING.md](.github/CONTRIBUTING.md)
+
+## LICENSE
+
+All code after [commit 333c566b88108de218d882840e61928a9b759d8f on 2014/31/12 at 4:38 PM PST](https://github.com/tgstation/tgstation/commit/333c566b88108de218d882840e61928a9b759d8f) is licensed under [GNU AGPL v3](http://www.gnu.org/licenses/agpl-3.0.html).
+
+All code before [commit 333c566b88108de218d882840e61928a9b759d8f on 2014/31/12 at 4:38 PM PST](https://github.com/tgstation/tgstation/commit/333c566b88108de218d882840e61928a9b759d8f) is licensed under [GNU GPL v3](https://www.gnu.org/licenses/gpl-3.0.html).
+(Including tools unless their readme specifies otherwise.)
+
+See LICENSE-AGPLv3.txt and LICENSE-GPLv3.txt for more details.
+
+tgui clientside is licensed as a subproject under the MIT license.
+Font Awesome font files, used by tgui, are licensed under the SIL Open Font License v1.1
+tgui assets are licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/).
+The TGS3 API is licensed as a subproject under the MIT license.
+
+See tgui/LICENSE.md for the MIT license.
+See tgui/assets/fonts/SIL-OFL-1.1-LICENSE.md for the SIL Open Font License.
+See the footers of code/\_\_DEFINES/server\_tools.dm, code/modules/server\_tools/st\_commands.dm, and code/modules/server\_tools/st\_inteface.dm for the MIT license.
+
+All assets including icons and sound are under a [Creative Commons 3.0 BY-SA license](http://creativecommons.org/licenses/by-sa/3.0/) unless otherwise indicated.
diff --git a/TGS3.json b/TGS3.json
new file mode 100644
index 0000000000..4fd41ab9f1
--- /dev/null
+++ b/TGS3.json
@@ -0,0 +1,22 @@
+{
+ "documentation": "/tg/station server 3 configuration file",
+ "changelog": {
+ "script": "tools/ss13_genchangelog.py",
+ "arguments": "html/changelog.html html/changelogs",
+ "pip_dependancies": [
+ "PyYaml",
+ "beautifulsoup4"
+ ]
+ },
+ "synchronize_paths": [
+ "html/changelog.html",
+ "html/changelogs/*"
+ ],
+ "static_directories": [
+ "config",
+ "data"
+ ],
+ "dlls": [
+ "libmysql.dll"
+ ]
+ }
\ No newline at end of file
diff --git a/code/__DEFINES/server_tools.dm b/code/__DEFINES/server_tools.dm
index d2be3e578e..b86a54959f 100644
--- a/code/__DEFINES/server_tools.dm
+++ b/code/__DEFINES/server_tools.dm
@@ -1,34 +1,123 @@
+// /tg/station 13 server tools API v3.1
+
+//CONFIGURATION
+//use this define if you want to do configuration outside of this file
+#ifndef SERVER_TOOLS_EXTERNAL_CONFIGURATION
+//Comment this out once you've filled in the below
+//#error /tg/station server tools interface unconfigured
+
+//Required interfaces (fill in with your codebase equivalent):
+
+//create a global variable named `Name` and set it to `Value`
+//These globals must not be modifiable from anywhere outside of the server tools
+#define SERVER_TOOLS_DEFINE_AND_SET_GLOBAL(Name, Value) GLOBAL_VAR_INIT(##Name, ##Value); GLOBAL_PROTECT(##Name)
+//Read the value in the global variable `Name`
+#define SERVER_TOOLS_READ_GLOBAL(Name) GLOB.##Name
+//Set the value in the global variable `Name` to `Value`
+#define SERVER_TOOLS_WRITE_GLOBAL(Name, Value) GLOB.##Name = ##Value
+//display an announcement `message` from the server to all players
+#define SERVER_TOOLS_WORLD_ANNOUNCE(message) to_chat(world, "[html_encode(##message)]")
+//Write a string `message` to a server log
+#define SERVER_TOOLS_LOG(message) log_world("SERVICE: [##message]")
+//Notify current in-game administrators of a string `event`
+#define SERVER_TOOLS_NOTIFY_ADMINS(event) message_admins(##event)
+#endif
+
+//Required hooks:
+
+//Put this somewhere in /world/New() that is always run
+#define SERVER_TOOLS_ON_NEW ServiceInit()
+//Put this somewhere in /world/Topic(T, Addr, Master, Keys) that is always run before T is modified
+#define SERVER_TOOLS_ON_TOPIC var/service_topic_return = ServiceCommand(params2list(T)); if(service_topic_return) return service_topic_return
+//Put at the beginning of world/Reboot(reason)
+#define SERVER_TOOLS_ON_REBOOT ServiceReboot()
+
+//Optional callable functions:
+
+//Returns the string version of the API
+#define SERVER_TOOLS_API_VERSION ServiceAPIVersion()
+//Returns TRUE if the world was launched under the server tools and the API matches, FALSE otherwise
+//No function below this succeed if this is FALSE
+#define SERVER_TOOLS_PRESENT RunningService()
+//Gets the current version of the service running the server
+#define SERVER_TOOLS_VERSION ServiceVersion()
+//Forces a hard reboot of BYOND by ending the process
+//unlike del(world) clients will try to reconnect
+//If the service has not requested a shutdown, the world will reboot shortly after
+#define SERVER_TOOLS_REBOOT_BYOND world.ServiceEndProcess()
+/*
+ Gets the list of any testmerged github pull requests
+
+ "[PR Number]" => list(
+ "title" -> PR title
+ "commit" -> Full hash of commit merged
+ "author" -> Github username of the author of the PR
+ )
+*/
+#define SERVER_TOOLS_PR_LIST GetTestMerges()
+//Sends a message to connected game chats
+#define SERVER_TOOLS_CHAT_BROADCAST(message) world.ChatBroadcast(message)
+//Sends a message to connected admin chats
+#define SERVER_TOOLS_RELAY_BROADCAST(message) world.AdminBroadcast(message)
+
+//IMPLEMENTATION
+
+#define SERVICE_API_VERSION_STRING "3.1.0.0"
+
#define REBOOT_MODE_NORMAL 0
#define REBOOT_MODE_HARD 1
#define REBOOT_MODE_SHUTDOWN 2
-#define IRC_STATUS_THROTTLE 5
-
-#define PR_ANNOUNCEMENTS_PER_ROUND 5 //The number of unique PR announcements allowed per round
- //This makes sure that a single person can only spam 3 reopens and 3 closes before being ignored
-
-//keep these in sync with TGS3
#define SERVICE_WORLD_PARAM "server_service"
#define SERVICE_VERSION_PARAM "server_service_version"
#define SERVICE_PR_TEST_JSON "prtestjob.json"
-#define SERVICE_PR_TEST_JSON_OLD "..\\..\\[SERVICE_PR_TEST_JSON]"
+#define SERVICE_INTERFACE_DLL "TGServiceInterface.dll"
+#define SERVICE_INTERFACE_FUNCTION "DDEntryPoint"
#define SERVICE_CMD_HARD_REBOOT "hard_reboot"
#define SERVICE_CMD_GRACEFUL_SHUTDOWN "graceful_shutdown"
#define SERVICE_CMD_WORLD_ANNOUNCE "world_announce"
-#define SERVICE_CMD_IRC_CHECK "irc_check"
-#define SERVICE_CMD_IRC_STATUS "irc_status"
-#define SERVICE_CMD_ADMIN_MSG "adminmsg"
-#define SERVICE_CMD_NAME_CHECK "namecheck"
-#define SERVICE_CMD_ADMIN_WHO "adminwho"
+#define SERVICE_CMD_LIST_CUSTOM "list_custom_commands"
#define SERVICE_CMD_PARAM_KEY "serviceCommsKey"
#define SERVICE_CMD_PARAM_COMMAND "command"
-#define SERVICE_CMD_PARAM_MESSAGE "message"
-#define SERVICE_CMD_PARAM_TARGET "target"
#define SERVICE_CMD_PARAM_SENDER "sender"
+#define SERVICE_CMD_PARAM_CUSTOM "custom"
+#define SERVICE_CMD_API_COMPATIBLE "api_compat"
+
+#define SERVICE_JSON_PARAM_HELPTEXT "help_text"
+#define SERVICE_JSON_PARAM_ADMINONLY "admin_only"
+#define SERVICE_JSON_PARAM_REQUIREDPARAMETERS "required_parameters"
#define SERVICE_REQUEST_KILL_PROCESS "killme"
#define SERVICE_REQUEST_IRC_BROADCAST "irc"
#define SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE "send2irc"
#define SERVICE_REQUEST_WORLD_REBOOT "worldreboot"
+#define SERVICE_REQUEST_API_VERSION "api_ver"
+
+/*
+The MIT License
+
+Copyright (c) 2011 Dominic Tarr
+
+Permission is hereby granted, free of charge,
+to any person obtaining a copy of this software and
+associated documentation files (the "Software"), to
+deal in the Software without restriction, including
+without limitation the rights to use, copy, modify,
+merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom
+the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm
index 905babe848..5e190ab709 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -1364,63 +1364,6 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
return FALSE
return TRUE
-//WHATEVER YOU USE THIS FOR MUST BE SANITIZED TO SHIT, IT USES SHELL
-//It also sleeps
-
-//Set this to TRUE before calling
-//This prevents RCEs from badmins
-//kevinz000 if you touch this I will hunt you down
-GLOBAL_VAR_INIT(valid_HTTPSGet, FALSE)
-GLOBAL_PROTECT(valid_HTTPSGet)
-/proc/HTTPSGet(url) //tgs2 support
- if(findtext(url, "\""))
- GLOB.valid_HTTPSGet = FALSE
-
- if(!GLOB.valid_HTTPSGet)
- if(usr)
- CRASH("[usr.ckey]([usr]) just attempted an invalid HTTPSGet on: [url]!")
- else
- CRASH("Invalid HTTPSGet call on: [url]")
- GLOB.valid_HTTPSGet = FALSE
-
- //"This has got to be the ugliest hack I have ever done"
- //warning, here be dragons
- /*
- | @___oo
- /\ /\ / (__,,,,|
- ) /^\) ^\/ _)
- ) /^\/ _)
- ) _ / / _)
- /\ )/\/ || | )_)
- < > |(,,) )__)
- || / \)___)\
- | \____( )___) )___
- \______(_______;;; __;;;
- */
- var/temp_file = "data/HTTPSGetOutput.txt"
- var/command
- if(world.system_type == MS_WINDOWS)
- command = "powershell -Command \"wget [url] -OutFile [temp_file]\""
- else if(world.system_type == UNIX)
- command = "wget -O [temp_file] [url]"
- else
- CRASH("Invalid world.system_type ([world.system_type])? Yell at Lummox.")
-
- log_world("HTTPSGet: [url]")
- var/result = shell(command)
- if(result != 0)
- log_world("Download failed: shell exited with code: [result]")
- return
-
- var/f = file(temp_file)
- if(!f)
- log_world("Download failed: Temp file not found")
- return
-
- . = file2text(f)
- f = null
- fdel(temp_file)
-
#define UNTIL(X) while(!(X)) stoplag()
/proc/pass()
diff --git a/code/controllers/configuration/entries/config.dm b/code/controllers/configuration/entries/config.dm
index cabb6dca27..62919afd06 100644
--- a/code/controllers/configuration/entries/config.dm
+++ b/code/controllers/configuration/entries/config.dm
@@ -229,7 +229,7 @@ CONFIG_DEF(flag/forbid_singulo_possession)
CONFIG_DEF(flag/useircbot) //tgs2 support
protection = CONFIG_ENTRY_LOCKED
-
+
CONFIG_DEF(flag/automute_on) //enables automuting/spam prevention
CONFIG_DEF(string/panic_server_name)
diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm
index 2a43d7a844..e41b848134 100755
--- a/code/controllers/subsystem/ticker.dm
+++ b/code/controllers/subsystem/ticker.dm
@@ -89,6 +89,8 @@ SUBSYSTEM_DEF(ticker)
for(var/client/C in GLOB.clients)
window_flash(C, ignorepref = TRUE) //let them know lobby has opened up.
to_chat(world, "Welcome to [station_name()]!")
+ if(CONFIG_GET(flag/irc_announce_new_game))
+ SERVER_TOOLS_CHAT_BROADCAST("New round starting on [SSmapping.config.map_name]!")
current_state = GAME_STATE_PREGAME
//Everyone who wants to be an observer is now spawned
create_observers()
diff --git a/code/datums/helper_datums/getrev.dm b/code/datums/helper_datums/getrev.dm
index b3dc454ecc..7b60070fc7 100644
--- a/code/datums/helper_datums/getrev.dm
+++ b/code/datums/helper_datums/getrev.dm
@@ -2,25 +2,10 @@
var/originmastercommit
var/commit
var/list/testmerge = list()
- var/has_pr_details = FALSE //tgs2 support
var/date
/datum/getrev/New()
- if(world.RunningService())
- var/file_name
- if(ServiceVersion()) //will return null for versions < 3.0.91.0
- file_name = SERVICE_PR_TEST_JSON_OLD
- else
- file_name = SERVICE_PR_TEST_JSON
- if(fexists(file_name))
- testmerge = json_decode(file2text(file_name))
-#ifdef SERVERTOOLS
- else if(!world.RunningService() && fexists("../prtestjob.lk")) //tgs2 support
- var/list/tmp = world.file2list("..\\prtestjob.lk")
- for(var/I in tmp)
- if(I)
- testmerge |= I
-#endif
+ testmerge = SERVER_TOOLS_PR_LIST
log_world("Running /tg/ revision:")
var/list/logs = world.file2list(".git/logs/HEAD")
if(logs)
@@ -36,53 +21,21 @@
log_world(commit)
for(var/line in testmerge)
if(line)
- if(world.RunningService())
- var/tmcommit = testmerge[line]["commit"]
- log_world("Test merge active of PR #[line] commit [tmcommit]")
- SSblackbox.add_details("testmerged_prs","[line]|[tmcommit]")
- else //tgs2 support
- log_world("Test merge active of PR #[line]")
- SSblackbox.add_details("testmerged_prs","[line]")
+ var/tmcommit = testmerge[line]["commit"]
+ log_world("Test merge active of PR #[line] commit [tmcommit]")
+ SSblackbox.add_details("testmerged_prs","[line]|[tmcommit]")
log_world("Based off origin/master commit [originmastercommit]")
else
log_world(originmastercommit)
-/datum/getrev/proc/DownloadPRDetails()
- var/repo_id = CONFIG_GET(number/githubrepoid)
- if(!repo_id)
- if(testmerge.len)
- log_world("PR details download failed: No github repo config set")
- return
- for(var/line in testmerge)
- if(!isnum(text2num(line)))
- log_world("PR details download failed: Invalid PR number: [line]")
- return
- var/url = "https://api.github.com/repositories/[repo_id]/pulls/[line].json"
- GLOB.valid_HTTPSGet = TRUE
- var/json = HTTPSGet(url)
- if(!json)
- return
-
- testmerge[line] = json_decode(json)
-
- if(!testmerge[line])
- log_world("PR details download failed: null details returned")
- return
- CHECK_TICK
- log_world("PR details successfully downloaded")
- has_pr_details = TRUE
/datum/getrev/proc/GetTestMergeInfo(header = TRUE)
if(!testmerge.len)
return ""
. = header ? "The following pull requests are currently test merged:
" : ""
for(var/line in testmerge)
- var/details
- if(world.RunningService())
- var/cm = testmerge[line]["commit"]
- details = ": '" + html_encode(testmerge[line]["title"]) + "' by " + html_encode(testmerge[line]["author"]) + " at commit " + html_encode(copytext(cm, 1, min(length(cm), 7)))
- else if(has_pr_details) //tgs2 support
- details = ": '" + html_encode(testmerge[line]["title"]) + "' by " + html_encode(testmerge[line]["user"]["login"])
+ var/cm = testmerge[line]["commit"]
+ var/details = ": '" + html_encode(testmerge[line]["title"]) + "' by " + html_encode(testmerge[line]["author"]) + " at commit " + html_encode(copytext(cm, 1, min(length(cm), 7)))
if(details && findtext(details, "\[s\]") && (!usr || !usr.client.holder))
continue
. += "#[line][details]
"
diff --git a/code/game/world.dm b/code/game/world.dm
index 316ed70961..2f54467c95 100644
--- a/code/game/world.dm
+++ b/code/game/world.dm
@@ -1,3 +1,6 @@
+#define PR_ANNOUNCEMENTS_PER_ROUND 5 //The number of unique PR announcements allowed per round
+ //This makes sure that a single person can only spam 3 reopens and 3 closes before being ignored
+
GLOBAL_VAR(security_mode)
GLOBAL_PROTECT(security_mode)
@@ -19,8 +22,7 @@ GLOBAL_PROTECT(security_mode)
SetupLogs()
- if(!RunningService()) //tgs2 support
- GLOB.revdata.DownloadPRDetails()
+ SERVER_TOOLS_ON_NEW
load_motd()
load_admins()
@@ -33,8 +35,6 @@ GLOBAL_PROTECT(security_mode)
Master.Initialize(10, FALSE)
- if(CONFIG_GET(flag/irc_announce_new_game))
- IRCBroadcast("New round starting on [SSmapping.config.map_name]!")
/world/proc/SetupExternalRSC()
#if (PRELOAD_RSC == 0)
@@ -125,8 +125,8 @@ GLOBAL_PROTECT(security_mode)
if(!pinging && !playing && config && CONFIG_GET(flag/log_world_topic))
WRITE_FILE(GLOB.world_game_log, "TOPIC: \"[T]\", from:[addr], master:[master], key:[key]")
- if(input[SERVICE_CMD_PARAM_KEY])
- return ServiceCommand(input)
+ SERVER_TOOLS_ON_TOPIC //redirect to server tools if necessary
+
var/comms_key = CONFIG_GET(string/comms_key)
var/key_valid = (comms_key && input["key"] == comms_key)
@@ -143,17 +143,6 @@ GLOBAL_PROTECT(security_mode)
n++
return n
- else if("ircstatus" in input) //tgs2 support
- var/static/last_irc_status = 0
- if(world.time - last_irc_status < 50)
- return
- var/list/adm = get_admin_counts()
- var/list/allmins = adm["total"]
- var/status = "Admins: [allmins.len] (Active: [english_list(adm["present"])] AFK: [english_list(adm["afk"])] Stealth: [english_list(adm["stealth"])] Skipped: [english_list(adm["noflags"])]). "
- status += "Players: [GLOB.clients.len] (Active: [get_active_player_count(0,1,0)]). Mode: [SSticker.mode.name]."
- send2irc("Status", status)
- last_irc_status = world.time
-
else if("status" in input)
var/list/s = list()
s["version"] = GLOB.game_version
@@ -211,24 +200,6 @@ GLOBAL_PROTECT(security_mode)
if(input["crossmessage"] == "News_Report")
minor_announce(input["message"], "Breaking Update From [input["message_sender"]]")
- else if("adminmsg" in input) //tgs2 support
- if(!key_valid)
- return "Bad Key"
- else
- return IrcPm(input["adminmsg"],input["msg"],input["sender"])
-
- else if("namecheck" in input) //tgs2 support
- if(!key_valid)
- return "Bad Key"
- else
- log_admin("IRC Name Check: [input["sender"]] on [input["namecheck"]]")
- message_admins("IRC name checking on [input["namecheck"]] from [input["sender"]]")
- return keywords_lookup(input["namecheck"],1)
- else if("adminwho" in input) //tgs2 support
- if(!key_valid)
- return "Bad Key"
- else
- return ircadminwho()
else if("server_hop" in input)
show_server_hop_transfer_screen(input["server_hop"])
@@ -247,7 +218,7 @@ GLOBAL_PROTECT(security_mode)
C.AnnouncePR(final_composed)
/world/Reboot(reason = 0, fast_track = FALSE)
- ServiceReboot() //handles alternative actions if necessary
+ SERVER_TOOLS_ON_REBOOT
if (reason || fast_track) //special reboot, do none of the normal stuff
if (usr)
log_admin("[key_name(usr)] Has requested an immediate world restart via client side debugging tools")
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index 881e59204d..a64cd8f40e 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -421,8 +421,8 @@
return
var/list/options = list("Regular Restart", "Hard Restart (No Delay/Feeback Reason)", "Hardest Restart (No actions, just reboot)")
- if(world.RunningService())
- options += "Service Restart (Force restart DD)";
+ if(SERVER_TOOLS_PRESENT)
+ options += "Server Restart (Kill and restart DD)";
var/rebootconfirm
if(SSticker.admin_delay_notice)
@@ -434,16 +434,19 @@
var/result = input(usr, "Select reboot method", "World Reboot", options[1]) as null|anything in options
if(result)
SSblackbox.add_details("admin_verb","Reboot World") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+ var/init_by = "Initiated by [usr.client.holder.fakekey ? "Admin" : usr.key]."
switch(result)
if("Regular Restart")
- SSticker.Reboot("Initiated by [usr.client.holder.fakekey ? "Admin" : usr.key].", "admin reboot - by [usr.key] [usr.client.holder.fakekey ? "(stealth)" : ""]", 10)
+ SSticker.Reboot(init_by, "admin reboot - by [usr.key] [usr.client.holder.fakekey ? "(stealth)" : ""]", 10)
if("Hard Restart (No Delay, No Feeback Reason)")
+ to_chat(world, "World reboot - [init_by]")
world.Reboot()
if("Hardest Restart (No actions, just reboot)")
+ to_chat(world, "Hard world reboot - [init_by]")
world.Reboot(fast_track = TRUE)
- if("Service Restart (Force restart DD)")
- GLOB.reboot_mode = REBOOT_MODE_HARD
- world.ServiceReboot()
+ if("Server Restart (Kill and restart DD)")
+ to_chat(world, "Server restart - [init_by]")
+ SERVER_TOOLS_REBOOT_BYOND
/datum/admins/proc/end_round()
set category = "Server"
diff --git a/code/modules/admin/chat_commands.dm b/code/modules/admin/chat_commands.dm
new file mode 100644
index 0000000000..e0d7f1315e
--- /dev/null
+++ b/code/modules/admin/chat_commands.dm
@@ -0,0 +1,63 @@
+#define IRC_STATUS_THROTTLE 5
+
+/datum/server_tools_command/ircstatus
+ name = "status"
+ help_text = "Gets the admincount, playercount, gamemode, and true game mode of the server"
+ admin_only = TRUE
+ var/static/last_irc_status = 0
+
+/datum/server_tools_command/ircstatus/Run(sender, params)
+ var/rtod = REALTIMEOFDAY
+ if(rtod - last_irc_status < IRC_STATUS_THROTTLE)
+ return
+ last_irc_status = rtod
+ var/list/adm = get_admin_counts()
+ var/list/allmins = adm["total"]
+ var/status = "Admins: [allmins.len] (Active: [english_list(adm["present"])] AFK: [english_list(adm["afk"])] Stealth: [english_list(adm["stealth"])] Skipped: [english_list(adm["noflags"])]). "
+ status += "Players: [GLOB.clients.len] (Active: [get_active_player_count(0,1,0)]). Mode: [SSticker.mode ? SSticker.mode.name : "Not started"]."
+ return status
+
+/datum/server_tools_command/irccheck
+ name = "check"
+ help_text = "Gets the playercount, gamemode, and address of the server"
+ admin_only = TRUE
+ var/static/last_irc_check = 0
+
+/datum/server_tools_command/irccheck/Run(sender, params)
+ var/rtod = REALTIMEOFDAY
+ if(rtod - last_irc_check < IRC_STATUS_THROTTLE)
+ return
+ last_irc_check = rtod
+ var/server = CONFIG_GET(string/server)
+ return "[GLOB.round_id ? "Round #[GLOB.round_id]: " : ""][GLOB.clients.len] players on [SSmapping.config.map_name], Mode: [GLOB.master_mode]; Round [SSticker.HasRoundStarted() ? (SSticker.IsRoundInProgress() ? "Active" : "Finishing") : "Starting"] -- [server ? server : "[world.internet_address]:[world.port]"]"
+
+/datum/server_tools_command/ahelp
+ name = "ahelp"
+ help_text = " |list>>"
+ required_parameters = 2
+ admin_only = TRUE
+
+/datum/server_tools_command/ahelp/Run(sender, params)
+ var/list/all_params = splittext(params, " ")
+ var/target = all_params[1]
+ all_params.Cut(1, 2)
+ return IrcPm(target, all_params.Join(" "), sender)
+
+/datum/server_tools_command/namecheck
+ name = "namecheck"
+ help_text = "Returns info on the specified target"
+ required_parameters = 1
+ admin_only = TRUE
+
+/datum/server_tools_command/namecheck/Run(sender, params)
+ log_admin("IRC Name Check: [sender] on [params]")
+ message_admins("IRC name checking on [params] from [sender]")
+ return keywords_lookup(params, 1)
+
+/datum/server_tools_command/adminwho
+ name = "adminwho"
+ help_text = "Lists administrators currently on the server"
+ admin_only = TRUE
+
+/datum/server_tools_command/adminwho/Run(sender, params)
+ return ircadminwho()
diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm
index a61de11c30..3a343b7f06 100644
--- a/code/modules/admin/verbs/adminhelp.dm
+++ b/code/modules/admin/verbs/adminhelp.dm
@@ -587,10 +587,8 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
/proc/send2irc(msg,msg2)
- if(world.RunningService())
- world.ExportService("[SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE] [msg] | [msg2]")
- else if(CONFIG_GET(flag/useircbot))
- shell("python nudge.py [msg] [msg2]")
+ if(SERVER_TOOLS_PRESENT)
+ SERVER_TOOLS_RELAY_BROADCAST("[msg] | [msg2]")
/proc/send2otherserver(source,msg,type = "Ahelp")
var/comms_key = CONFIG_GET(string/comms_key)
diff --git a/code/modules/server_tools/st_commands.dm b/code/modules/server_tools/st_commands.dm
new file mode 100644
index 0000000000..9ec87a595c
--- /dev/null
+++ b/code/modules/server_tools/st_commands.dm
@@ -0,0 +1,76 @@
+/datum/server_tools_command
+ var/name = "" //the string to trigger this command on a chat bot. e.g. TGS3_BOT: do_this_command
+ var/help_text = "" //help text for this command
+ var/required_parameters = 0 //number of parameters required for this command
+ var/admin_only = FALSE //set to TRUE if this command should only be usable by registered chat admins
+
+//override to implement command
+//sender is the display name of who sent the command
+//params is the trimmed string following the command name
+/datum/server_tools_command/proc/Run(sender, params)
+ CRASH("[type] has no implementation for Run()")
+
+/world/proc/ListServiceCustomCommands(warnings_only)
+ if(!warnings_only)
+ . = list()
+ var/list/command_name_types = list()
+ var/list/warned_command_names = warnings_only ? list() : null
+ for(var/I in typesof(/datum/server_tools_command) - /datum/server_tools_command)
+ var/datum/server_tools_command/stc = I
+ var/command_name = initial(stc.name)
+ var/static/list/warned_server_tools_names = list()
+ if(!command_name || findtext(command_name, " ") || findtext(command_name, "'") || findtext(command_name, "\""))
+ if(warnings_only && !warned_command_names[command_name])
+ SERVER_TOOLS_LOG("WARNING: Custom command [command_name] can't be used as it is empty or contains illegal characters!")
+ warned_command_names[command_name] = TRUE
+ continue
+
+ if(command_name_types[command_name])
+ if(warnings_only)
+ SERVER_TOOLS_LOG("WARNING: Custom commands [command_name_types[command_name]] and [stc] have the same name, only [command_name_types[command_name]] will be available!")
+ continue
+ command_name_types[stc] = command_name
+
+ if(!warnings_only)
+ .[command_name] = list(SERVICE_JSON_PARAM_HELPTEXT = initial(stc.help_text), SERVICE_JSON_PARAM_ADMINONLY = initial(stc.admin_only), SERVICE_JSON_PARAM_REQUIREDPARAMETERS = initial(stc.required_parameters))
+
+/world/proc/HandleServiceCustomCommand(command, sender, params)
+ var/static/list/cached_custom_server_tools_commands
+ if(!cached_custom_server_tools_commands)
+ cached_custom_server_tools_commands = list()
+ for(var/I in typesof(/datum/server_tools_command) - /datum/server_tools_command)
+ var/datum/server_tools_command/stc = I
+ cached_custom_server_tools_commands[lowertext(initial(stc.name))] = stc
+
+ var/command_type = cached_custom_server_tools_commands[command]
+ if(!command_type)
+ return FALSE
+ var/datum/server_tools_command/stc = new command_type
+ return stc.Run(sender, params) || TRUE
+
+/*
+The MIT License
+
+Copyright (c) 2011 Dominic Tarr
+
+Permission is hereby granted, free of charge,
+to any person obtaining a copy of this software and
+associated documentation files (the "Software"), to
+deal in the Software without restriction, including
+without limitation the rights to use, copy, modify,
+merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom
+the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
diff --git a/code/modules/server_tools/st_interface.dm b/code/modules/server_tools/st_interface.dm
new file mode 100644
index 0000000000..12fb5a85c2
--- /dev/null
+++ b/code/modules/server_tools/st_interface.dm
@@ -0,0 +1,125 @@
+SERVER_TOOLS_DEFINE_AND_SET_GLOBAL(reboot_mode, REBOOT_MODE_NORMAL)
+SERVER_TOOLS_DEFINE_AND_SET_GLOBAL(server_tools_api_compatible, FALSE)
+
+/proc/GetTestMerges()
+ if(RunningService() && fexists(SERVICE_PR_TEST_JSON))
+ . = json_decode(file2text(SERVICE_PR_TEST_JSON))
+ if(.)
+ return
+ return list()
+
+/world/proc/ServiceInit()
+ if(!RunningService(TRUE))
+ return
+ ListServiceCustomCommands(TRUE)
+ ExportService("[SERVICE_REQUEST_API_VERSION] [SERVER_TOOLS_API_VERSION]", TRUE)
+
+/proc/RunningService(skip_compat_check = FALSE)
+ return (skip_compat_check || SERVER_TOOLS_READ_GLOBAL(server_tools_api_compatible)) && world.params[SERVICE_WORLD_PARAM] != null
+
+/proc/ServiceVersion()
+ if(RunningService(TRUE))
+ return world.params[SERVICE_VERSION_PARAM]
+
+/proc/ServiceAPIVersion()
+ return SERVICE_API_VERSION_STRING
+
+/world/proc/ExportService(command, skip_compat_check = FALSE)
+ . = FALSE
+ if(!RunningService(skip_compat_check))
+ return
+ if(skip_compat_check && !fexists(SERVICE_INTERFACE_DLL))
+ CRASH("Service parameter present but no interface DLL detected. This is symptomatic of running a service less than version 3.1! Please upgrade.")
+ call(SERVICE_INTERFACE_DLL, SERVICE_INTERFACE_FUNCTION)(command) //trust no retval
+ return TRUE
+
+/world/proc/ChatBroadcast(message)
+ ExportService("[SERVICE_REQUEST_IRC_BROADCAST] [message]")
+
+/world/proc/AdminBroadcast(message)
+ ExportService("[SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE] [message]")
+
+/world/proc/ServiceEndProcess()
+ SERVER_TOOLS_LOG("Sending shutdown request!");
+ sleep(world.tick_lag) //flush the buffers
+ ExportService(SERVICE_REQUEST_KILL_PROCESS)
+
+//called at the exact moment the world is supposed to reboot
+/world/proc/ServiceReboot()
+ switch(SERVER_TOOLS_READ_GLOBAL(reboot_mode))
+ if(REBOOT_MODE_HARD)
+ SERVER_TOOLS_WORLD_ANNOUNCE("Hard reboot triggered, you will automatically reconnect...")
+ ServiceEndProcess()
+ if(REBOOT_MODE_SHUTDOWN)
+ SERVER_TOOLS_WORLD_ANNOUNCE("The server is shutting down...")
+ ServiceEndProcess()
+ else
+ ExportService(SERVICE_REQUEST_WORLD_REBOOT) //just let em know
+
+/world/proc/ServiceCommand(list/params)
+ var/their_sCK = params[SERVICE_CMD_PARAM_KEY]
+ if(!their_sCK || !RunningService(TRUE))
+ return FALSE //continue world/Topic
+
+ var/sCK = world.params[SERVICE_WORLD_PARAM]
+ if(their_sCK != sCK)
+ return "Invalid comms key!";
+
+ var/command = params[SERVICE_CMD_PARAM_COMMAND]
+ if(!command)
+ return "No command!"
+
+ switch(command)
+ if(SERVICE_CMD_API_COMPATIBLE)
+ SERVER_TOOLS_WRITE_GLOBAL(server_tools_api_compatible, TRUE)
+ return "SUCCESS"
+ if(SERVICE_CMD_HARD_REBOOT)
+ if(SERVER_TOOLS_READ_GLOBAL(reboot_mode) != REBOOT_MODE_HARD)
+ SERVER_TOOLS_WRITE_GLOBAL(reboot_mode, REBOOT_MODE_HARD)
+ SERVER_TOOLS_LOG("Hard reboot requested by service")
+ SERVER_TOOLS_NOTIFY_ADMINS("The world will hard reboot at the end of the game. Requested by service.")
+ if(SERVICE_CMD_GRACEFUL_SHUTDOWN)
+ if(SERVER_TOOLS_READ_GLOBAL(reboot_mode) != REBOOT_MODE_SHUTDOWN)
+ SERVER_TOOLS_WRITE_GLOBAL(reboot_mode, REBOOT_MODE_SHUTDOWN)
+ SERVER_TOOLS_LOG("Shutdown requested by service")
+ message_admins("The world will shutdown at the end of the game. Requested by service.")
+ if(SERVICE_CMD_WORLD_ANNOUNCE)
+ var/msg = params["message"]
+ if(!istext(msg) || !msg)
+ return "No message set!"
+ SERVER_TOOLS_WORLD_ANNOUNCE(msg)
+ return "SUCCESS"
+ if(SERVICE_CMD_LIST_CUSTOM)
+ return json_encode(ListServiceCustomCommands(FALSE))
+ else
+ var/custom_command_result = HandleServiceCustomCommand(lowertext(command), params[SERVICE_CMD_PARAM_SENDER], params[SERVICE_CMD_PARAM_CUSTOM])
+ if(custom_command_result)
+ return istext(custom_command_result) ? custom_command_result : "SUCCESS"
+ return "Unknown command: [command]"
+
+/*
+The MIT License
+
+Copyright (c) 2011 Dominic Tarr
+
+Permission is hereby granted, free of charge,
+to any person obtaining a copy of this software and
+associated documentation files (the "Software"), to
+deal in the Software without restriction, including
+without limitation the rights to use, copy, modify,
+merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom
+the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
diff --git a/tgstation.dme b/tgstation.dme
index 00948c2c45..ac04a28f0f 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -1063,6 +1063,7 @@
#include "code\modules\admin\admin_verbs.dm"
#include "code\modules\admin\adminmenu.dm"
#include "code\modules\admin\banjob.dm"
+#include "code\modules\admin\chat_commands.dm"
#include "code\modules\admin\create_mob.dm"
#include "code\modules\admin\create_object.dm"
#include "code\modules\admin\create_poll.dm"
@@ -2139,7 +2140,8 @@
#include "code\modules\ruins\spaceruin_code\TheDerelict.dm"
#include "code\modules\security_levels\keycard_authentication.dm"
#include "code\modules\security_levels\security_levels.dm"
-#include "code\modules\server_tools\server_tools.dm"
+#include "code\modules\server_tools\st_interface.dm"
+#include "code\modules\server_tools\st_commands.dm"
#include "code\modules\shuttle\arrivals.dm"
#include "code\modules\shuttle\assault_pod.dm"
#include "code\modules\shuttle\computer.dm"