diff --git a/README.md b/README.md
index d33438f606..3bd9c03c61 100644
--- a/README.md
+++ b/README.md
@@ -26,6 +26,7 @@ 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
+<<<<<<< HEAD
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.)
@@ -145,3 +146,131 @@ 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.
+>>>>>>> 62f788f... Server tools API v3.1 (#31000)
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 a20cad1cf2..e483357e57 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -1380,63 +1380,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
new file mode 100644
index 0000000000..ed09ca4a9c
--- /dev/null
+++ b/code/controllers/configuration/entries/config.dm
@@ -0,0 +1,384 @@
+#define CURRENT_RESIDENT_FILE "config.txt"
+
+CONFIG_DEF(flag/autoadmin) // if autoadmin is enabled
+ protection = CONFIG_ENTRY_LOCKED
+
+CONFIG_DEF(string/autoadmin_rank) // the rank for autoadmins
+ value = "Game Master"
+ protection = CONFIG_ENTRY_LOCKED
+
+CONFIG_DEF(string/servername) // server name (the name of the game window)
+ protection = CONFIG_ENTRY_LOCKED
+
+CONFIG_DEF(string/serversqlname) // short form server name used for the DB
+ protection = CONFIG_ENTRY_LOCKED
+
+CONFIG_DEF(string/stationname) // station name (the name of the station in-game)
+
+CONFIG_DEF(number/lobby_countdown) // In between round countdown.
+ value = 120
+ min_val = 0
+
+CONFIG_DEF(number/round_end_countdown) // Post round murder death kill countdown
+ value = 25
+ min_val = 0
+
+CONFIG_DEF(flag/hub) // if the game appears on the hub or not
+ protection = CONFIG_ENTRY_LOCKED
+
+CONFIG_DEF(flag/log_ooc) // log OOC channel
+ protection = CONFIG_ENTRY_LOCKED
+
+CONFIG_DEF(flag/log_access) // log login/logout
+ protection = CONFIG_ENTRY_LOCKED
+
+CONFIG_DEF(flag/log_say) // log client say
+ protection = CONFIG_ENTRY_LOCKED
+
+CONFIG_DEF(flag/log_admin) // log admin actions
+ protection = CONFIG_ENTRY_LOCKED
+
+CONFIG_DEF(flag/log_prayer) // log prayers
+ protection = CONFIG_ENTRY_LOCKED
+
+CONFIG_DEF(flag/log_law) // log lawchanges
+ protection = CONFIG_ENTRY_LOCKED
+
+CONFIG_DEF(flag/log_game) // log game events
+ protection = CONFIG_ENTRY_LOCKED
+
+CONFIG_DEF(flag/log_vote) // log voting
+ protection = CONFIG_ENTRY_LOCKED
+
+CONFIG_DEF(flag/log_whisper) // log client whisper
+ protection = CONFIG_ENTRY_LOCKED
+
+CONFIG_DEF(flag/log_attack) // log attack messages
+ protection = CONFIG_ENTRY_LOCKED
+
+CONFIG_DEF(flag/log_emote) // log emotes
+ protection = CONFIG_ENTRY_LOCKED
+
+CONFIG_DEF(flag/log_adminchat) // log admin chat messages
+ protection = CONFIG_ENTRY_LOCKED
+
+CONFIG_DEF(flag/log_pda) // log pda messages
+ protection = CONFIG_ENTRY_LOCKED
+
+CONFIG_DEF(flag/log_twitter) // log certain expliotable parrots and other such fun things in a JSON file of twitter valid phrases.
+ protection = CONFIG_ENTRY_LOCKED
+
+CONFIG_DEF(flag/log_world_topic) // log all world.Topic() calls
+ protection = CONFIG_ENTRY_LOCKED
+
+CONFIG_DEF(flag/allow_admin_ooccolor) // Allows admins with relevant permissions to have their own ooc colour
+ protection = CONFIG_ENTRY_LOCKED
+
+CONFIG_DEF(flag/allow_vote_restart) // allow votes to restart
+
+CONFIG_DEF(flag/allow_vote_mode) // allow votes to change mode
+
+CONFIG_DEF(number/vote_delay) // minimum time between voting sessions (deciseconds, 10 minute default)
+ value = 6000
+ min_val = 0
+
+CONFIG_DEF(number/vote_period) // length of voting period (deciseconds, default 1 minute)
+ value = 600
+ min_val = 0
+
+CONFIG_DEF(flag/default_no_vote) // vote does not default to nochange/norestart
+ protection = CONFIG_ENTRY_LOCKED
+
+CONFIG_DEF(flag/no_dead_vote) // dead people can't vote
+
+CONFIG_DEF(flag/allow_metadata) // Metadata is supported.
+ protection = CONFIG_ENTRY_LOCKED
+
+CONFIG_DEF(flag/popup_admin_pm) // adminPMs to non-admins show in a pop-up 'reply' window when set
+
+CONFIG_DEF(number/fps)
+ value = 20
+ min_val = 1
+ max_val = 100 //byond will start crapping out at 50, so this is just ridic
+ var/sync_validate = FALSE
+
+/datum/config_entry/number/fps/ValidateAndSet(str_val)
+ . = ..()
+ if(.)
+ sync_validate = TRUE
+ var/datum/config_entry/number/ticklag/TL = config.entries_by_type[/datum/config_entry/number/ticklag]
+ if(!TL.sync_validate)
+ TL.ValidateAndSet(10 / value)
+ sync_validate = FALSE
+
+CONFIG_DEF(number/ticklag)
+ integer = FALSE
+ var/sync_validate = FALSE
+
+/datum/config_entry/number/ticklag/New() //ticklag weirdly just mirrors fps
+ var/datum/config_entry/CE = /datum/config_entry/number/fps
+ value = 10 / initial(CE.value)
+ ..()
+
+/datum/config_entry/number/ticklag/ValidateAndSet(str_val)
+ . = text2num(str_val) > 0 && ..()
+ if(.)
+ sync_validate = TRUE
+ var/datum/config_entry/number/fps/FPS = config.entries_by_type[/datum/config_entry/number/fps]
+ if(!FPS.sync_validate)
+ FPS.ValidateAndSet(10 / value)
+ sync_validate = FALSE
+
+CONFIG_DEF(flag/allow_holidays)
+
+CONFIG_DEF(number/tick_limit_mc_init) //SSinitialization throttling
+ value = TICK_LIMIT_MC_INIT_DEFAULT
+ min_val = 0 //oranges warned us
+ integer = FALSE
+
+CONFIG_DEF(flag/admin_legacy_system) //Defines whether the server uses the legacy admin system with admins.txt or the SQL system
+ protection = CONFIG_ENTRY_LOCKED
+
+CONFIG_DEF(string/hostedby)
+ protection = CONFIG_ENTRY_LOCKED
+
+CONFIG_DEF(flag/norespawn)
+
+CONFIG_DEF(flag/guest_jobban)
+
+CONFIG_DEF(flag/usewhitelist)
+ protection = CONFIG_ENTRY_LOCKED
+
+CONFIG_DEF(flag/ban_legacy_system) //Defines whether the server uses the legacy banning system with the files in /data or the SQL system.
+ protection = CONFIG_ENTRY_LOCKED
+
+CONFIG_DEF(flag/use_age_restriction_for_jobs) //Do jobs use account age restrictions? --requires database
+
+CONFIG_DEF(flag/use_account_age_for_jobs) //Uses the time they made the account for the job restriction stuff. New player joining alerts should be unaffected.
+
+CONFIG_DEF(flag/use_exp_tracking)
+
+CONFIG_DEF(flag/use_exp_restrictions_heads)
+
+CONFIG_DEF(number/use_exp_restrictions_heads_hours)
+ value = 0
+ min_val = 0
+
+CONFIG_DEF(flag/use_exp_restrictions_heads_department)
+
+CONFIG_DEF(flag/use_exp_restrictions_other)
+
+CONFIG_DEF(flag/use_exp_restrictions_admin_bypass)
+
+CONFIG_DEF(string/server)
+ protection = CONFIG_ENTRY_LOCKED
+
+CONFIG_DEF(string/banappeals)
+ protection = CONFIG_ENTRY_LOCKED
+
+CONFIG_DEF(string/wikiurl)
+ protection = CONFIG_ENTRY_LOCKED
+ value = "http://www.tgstation13.org/wiki"
+
+CONFIG_DEF(string/forumurl)
+ protection = CONFIG_ENTRY_LOCKED
+ value = "http://tgstation13.org/phpBB/index.php"
+
+CONFIG_DEF(string/rulesurl)
+ protection = CONFIG_ENTRY_LOCKED
+ value = "http://www.tgstation13.org/wiki/Rules"
+
+CONFIG_DEF(string/githuburl)
+ protection = CONFIG_ENTRY_LOCKED
+ value = "https://www.github.com/tgstation/-tg-station"
+
+CONFIG_DEF(number/githubrepoid)
+ protection = CONFIG_ENTRY_LOCKED
+ value = null
+ min_val = 0
+
+CONFIG_DEF(flag/guest_ban)
+
+CONFIG_DEF(number/id_console_jobslot_delay)
+ value = 30
+ min_val = 0
+
+CONFIG_DEF(number/inactivity_period) //time in ds until a player is considered inactive)
+ value = 3000
+ min_val = 0
+
+/datum/config_entry/number/inactivity_period/ValidateAndSet(str_val)
+ . = ..()
+ if(.)
+ value *= 10 //documented as seconds in config.txt
+
+CONFIG_DEF(number/afk_period) //time in ds until a player is considered inactive)
+ value = 3000
+ min_val = 0
+
+/datum/config_entry/number/afk_period/ValidateAndSet(str_val)
+ . = ..()
+ if(.)
+ value *= 10 //documented as seconds in config.txt
+
+CONFIG_DEF(flag/kick_inactive) //force disconnect for inactive players
+
+CONFIG_DEF(flag/load_jobs_from_txt)
+
+CONFIG_DEF(flag/forbid_singulo_possession)
+
+CONFIG_DEF(flag/automute_on) //enables automuting/spam prevention
+
+CONFIG_DEF(string/panic_server_name)
+ protection = CONFIG_ENTRY_LOCKED
+
+/datum/config_entry/string/panic_server_name/ValidateAndSet(str_val)
+ return str_val != "\[Put the name here\]" && ..()
+
+CONFIG_DEF(string/panic_address) //Reconnect a player this linked server if this server isn't accepting new players
+
+/datum/config_entry/string/panic_address/ValidateAndSet(str_val)
+ return str_val != "byond://address:port" && ..()
+
+CONFIG_DEF(string/invoke_youtubedl)
+ protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN
+
+CONFIG_DEF(flag/show_irc_name)
+
+CONFIG_DEF(flag/see_own_notes) //Can players see their own admin notes (read-only)?
+
+CONFIG_DEF(number/note_fresh_days)
+ value = null
+ min_val = 0
+ integer = FALSE
+
+CONFIG_DEF(number/note_stale_days)
+ value = null
+ min_val = 0
+ integer = FALSE
+
+CONFIG_DEF(flag/maprotation)
+
+CONFIG_DEF(number/maprotatechancedelta)
+ value = 0.75
+ min_val = 0
+ max_val = 1
+ integer = FALSE
+
+CONFIG_DEF(number/soft_popcap)
+ value = null
+ min_val = 0
+
+CONFIG_DEF(number/hard_popcap)
+ value = null
+ min_val = 0
+
+CONFIG_DEF(number/extreme_popcap)
+ value = null
+ min_val = 0
+
+CONFIG_DEF(string/soft_popcap_message)
+ value = "Be warned that the server is currently serving a high number of users, consider using alternative game servers."
+
+CONFIG_DEF(string/hard_popcap_message)
+ value = "The server is currently serving a high number of users, You cannot currently join. You may wait for the number of living crew to decline, observe, or find alternative servers."
+
+CONFIG_DEF(string/extreme_popcap_message)
+ value = "The server is currently serving a high number of users, find alternative servers."
+
+CONFIG_DEF(flag/panic_bunker) // prevents people the server hasn't seen before from connecting
+ protection = CONFIG_ENTRY_LOCKED
+
+CONFIG_DEF(number/notify_new_player_age) // how long do we notify admins of a new player
+ min_val = -1
+
+CONFIG_DEF(number/notify_new_player_account_age) // how long do we notify admins of a new byond account
+ min_val = 0
+
+CONFIG_DEF(flag/irc_first_connection_alert) // do we notify the irc channel when somebody is connecting for the first time?
+
+CONFIG_DEF(flag/check_randomizer)
+
+CONFIG_DEF(string/ipintel_email)
+ protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN
+
+/datum/config_entry/string/ipintel_email/ValidateAndSet(str_val)
+ return str_val != "ch@nge.me" && ..()
+
+CONFIG_DEF(number/ipintel_rating_bad)
+ value = 1
+ integer = FALSE
+ min_val = 0
+ max_val = 1
+
+CONFIG_DEF(number/ipintel_save_good)
+ protection = CONFIG_ENTRY_LOCKED
+ value = 12
+ min_val = 0
+
+CONFIG_DEF(number/ipintel_save_bad)
+ protection = CONFIG_ENTRY_LOCKED
+ value = 1
+ min_val = 0
+
+CONFIG_DEF(string/ipintel_domain)
+ protection = CONFIG_ENTRY_LOCKED
+ value = "check.getipintel.net"
+
+CONFIG_DEF(flag/aggressive_changelog)
+
+CONFIG_DEF(flag/autoconvert_notes) //if all connecting player's notes should attempt to be converted to the database
+ protection = CONFIG_ENTRY_LOCKED
+
+CONFIG_DEF(flag/allow_webclient)
+
+CONFIG_DEF(flag/webclient_only_byond_members)
+
+CONFIG_DEF(flag/announce_admin_logout)
+
+CONFIG_DEF(flag/announce_admin_login)
+
+CONFIG_DEF(flag/allow_map_voting)
+
+CONFIG_DEF(flag/generate_minimaps)
+
+CONFIG_DEF(number/client_warn_version)
+ value = null
+ min_val = 500
+ max_val = DM_VERSION - 1
+
+CONFIG_DEF(string/client_warn_message)
+ value = "Your version of byond may have issues or be blocked from accessing this server in the future."
+
+CONFIG_DEF(number/client_error_version)
+ value = null
+ min_val = 500
+ max_val = DM_VERSION - 1
+
+CONFIG_DEF(string/client_error_message)
+ value = "Your version of byond is too old, may have issues, and is blocked from accessing this server."
+
+CONFIG_DEF(number/minute_topic_limit)
+ value = null
+ min_val = 0
+
+CONFIG_DEF(number/second_topic_limit)
+ value = null
+ min_val = 0
+
+CONFIG_DEF(number/error_cooldown) // The "cooldown" time for each occurrence of a unique error)
+ value = 600
+ min_val = 0
+
+CONFIG_DEF(number/error_limit) // How many occurrences before the next will silence them
+ value = 50
+
+CONFIG_DEF(number/error_silence_time) // How long a unique error will be silenced for
+ value = 6000
+
+CONFIG_DEF(number/error_msg_delay) // How long to wait between messaging admins about occurrences of a unique error
+ value = 50
+
+CONFIG_DEF(flag/irc_announce_new_game)
+
+CONFIG_DEF(flag/debug_admin_hrefs)
\ No newline at end of file
diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm
index 3932eada60..3e66c2459c 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 c6958fa997..18db706757 100644
--- a/code/datums/helper_datums/getrev.dm
+++ b/code/datums/helper_datums/getrev.dm
@@ -2,10 +2,10 @@
var/originmastercommit
var/commit
var/list/testmerge = list()
- var/has_pr_details = FALSE //tgs2 support
var/date
/datum/getrev/New()
+<<<<<<< HEAD
if(world.RunningService())
var/file_name
if(ServiceVersion()) //will return null for versions < 3.0.91.0
@@ -21,6 +21,9 @@
if(I)
testmerge |= I
#endif
+=======
+ testmerge = SERVER_TOOLS_PR_LIST
+>>>>>>> 62f788f... Server tools API v3.1 (#31000)
log_world("Running /tg/ revision:")
var/list/logs = world.file2list(".git/logs/HEAD")
if(logs)
@@ -36,16 +39,13 @@
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)
+<<<<<<< HEAD
/datum/getrev/proc/DownloadPRDetails()
if(!config.githubrepoid)
if(testmerge.len)
@@ -73,18 +73,16 @@
CHECK_TICK
log_world("PR details successfully downloaded")
has_pr_details = TRUE
+=======
+>>>>>>> 62f788f... Server tools API v3.1 (#31000)
/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 3763a408ac..05b6b442ef 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,9 +35,12 @@ GLOBAL_PROTECT(security_mode)
Master.Initialize(10, FALSE)
+<<<<<<< HEAD
if(config.irc_announce_new_game)
IRCBroadcast("New round starting on [SSmapping.config.map_name]!")
+=======
+>>>>>>> 62f788f... Server tools API v3.1 (#31000)
/world/proc/SetupExternalRSC()
#if (PRELOAD_RSC == 0)
external_rsc_urls = world.file2list("config/external_rsc_urls.txt","\n")
@@ -125,9 +130,16 @@ GLOBAL_PROTECT(security_mode)
if(!pinging && !playing && config && config.log_world_topic)
WRITE_FILE(GLOB.world_game_log, "TOPIC: \"[T]\", from:[addr], master:[master], key:[key]")
+<<<<<<< HEAD
if(input[SERVICE_CMD_PARAM_KEY])
return ServiceCommand(input)
var/key_valid = (global.comms_allowed && input["key"] == global.comms_key)
+=======
+ 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)
+>>>>>>> 62f788f... Server tools API v3.1 (#31000)
if(pinging)
var/x = 1
@@ -142,17 +154,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
@@ -210,24 +211,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"])
@@ -246,7 +229,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 b56caa4e9a..6fbdec01d2 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 7922a89c80..f974ebda62 100644
--- a/code/modules/admin/verbs/adminhelp.dm
+++ b/code/modules/admin/verbs/adminhelp.dm
@@ -587,10 +587,15 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
/proc/send2irc(msg,msg2)
+<<<<<<< HEAD
if(world.RunningService())
world.ExportService("[SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE] [msg] | [msg2]")
else if(config.useircbot)
shell("python nudge.py [msg] [msg2]")
+=======
+ if(SERVER_TOOLS_PRESENT)
+ SERVER_TOOLS_RELAY_BROADCAST("[msg] | [msg2]")
+>>>>>>> 62f788f... Server tools API v3.1 (#31000)
/proc/send2otherserver(source,msg,type = "Ahelp")
if(config.cross_allowed)
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 525e30ca66..de37ad4848 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -1054,6 +1054,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"
@@ -2128,7 +2129,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"