diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm
index 66f5960d98..8c8b360497 100644
--- a/code/__DEFINES/subsystems.dm
+++ b/code/__DEFINES/subsystems.dm
@@ -41,6 +41,7 @@
// Subsystem init_order, from highest priority to lowest priority
// The numbers just define the ordering, they are meaningless otherwise.
+#define INIT_ORDER_SERVER_MAINT 16
#define INIT_ORDER_JOBS 15
#define INIT_ORDER_EVENTS 14
#define INIT_ORDER_TICKER 13
diff --git a/code/_globalvars/game_modes.dm b/code/_globalvars/game_modes.dm
index 12fb1bae48..96ddad5f3d 100644
--- a/code/_globalvars/game_modes.dm
+++ b/code/_globalvars/game_modes.dm
@@ -1,4 +1,4 @@
-GLOBAL_VAR_INIT(master_mode, "traitor") //"extended"
+GLOBAL_VAR_INIT(master_mode, "traitor") //"extended"
GLOBAL_VAR_INIT(secret_force_mode, "secret") // if this is anything but "secret", the secret rotation will forceably choose this mode
GLOBAL_VAR_INIT(wavesecret, 0) // meteor mode, delays wave progression, terrible name
diff --git a/code/_globalvars/misc.dm b/code/_globalvars/misc.dm
index 285746cbd7..fbe9584262 100644
--- a/code/_globalvars/misc.dm
+++ b/code/_globalvars/misc.dm
@@ -8,7 +8,7 @@ GLOBAL_VAR_INIT(fileaccess_timer, 0)
GLOBAL_VAR_INIT(TAB, " ")
-GLOBAL_DATUM(data_core, /datum/datacore)
+GLOBAL_DATUM_INIT(data_core, /datum/datacore, new)
GLOBAL_VAR_INIT(CELLRATE, 0.002) // multiplier for watts per tick <> cell storage (eg: .002 means if there is a load of 1000 watts, 20 units will be taken from a cell per second)
GLOBAL_VAR_INIT(CHARGELEVEL, 0.001) // Cap for how fast cells charge, as a percentage-per-tick (.001 means cellcharge is capped to 1% per second)
diff --git a/code/controllers/master.dm b/code/controllers/master.dm
index a220c2530c..773f334b67 100644
--- a/code/controllers/master.dm
+++ b/code/controllers/master.dm
@@ -83,6 +83,7 @@ GLOBAL_VAR_INIT(CURRENT_TICKLIMIT, TICK_LIMIT_RUNNING)
sortTim(subsystems, /proc/cmp_subsystem_init)
reverseRange(subsystems)
for(var/datum/controller/subsystem/ss in subsystems)
+ testing("Shutdown [ss.name] subsystem")
ss.Shutdown()
// Returns 1 if we created a new mc, 0 if we couldn't due to a recent restart,
diff --git a/code/controllers/subsystem/blackbox.dm b/code/controllers/subsystem/blackbox.dm
index a807202c9e..334ca50441 100644
--- a/code/controllers/subsystem/blackbox.dm
+++ b/code/controllers/subsystem/blackbox.dm
@@ -55,6 +55,8 @@ SUBSYSTEM_DEF(blackbox)
return FALSE
/datum/controller/subsystem/blackbox/Shutdown()
+ set_val("ahelp_unresolved", GLOB.ahelp_tickets.active_tickets.len)
+
var/pda_msg_amt = 0
var/rc_msg_amt = 0
diff --git a/code/controllers/subsystem/server_maint.dm b/code/controllers/subsystem/server_maint.dm
index a0ca6a7b67..3c54bf2f3b 100644
--- a/code/controllers/subsystem/server_maint.dm
+++ b/code/controllers/subsystem/server_maint.dm
@@ -37,4 +37,11 @@ SUBSYSTEM_DEF(server_maint)
if (MC_TICK_CHECK) //one day, when ss13 has 1000 people per server, you guys are gonna be glad I added this tick check
return
+/datum/controller/subsystem/server_maint/Shutdown()
+ kick_clients_in_lobby("The round came to an end with you in the lobby.", TRUE) //second parameter ensures only afk clients are kicked
+ for(var/thing in GLOB.clients)
+ var/client/C = thing
+ if(C && config.server) //if you set a server location in config.txt, it sends you there instead of trying to reconnect to the same world address. -- NeoFite
+ C << link("byond://[config.server]")
+
#undef PING_BUFFER_TIME
diff --git a/code/controllers/subsystem/server_maint.dm.rej b/code/controllers/subsystem/server_maint.dm.rej
new file mode 100644
index 0000000000..e40a53a808
--- /dev/null
+++ b/code/controllers/subsystem/server_maint.dm.rej
@@ -0,0 +1,9 @@
+diff a/code/controllers/subsystem/server_maint.dm b/code/controllers/subsystem/server_maint.dm (rejected hunks)
+@@ -5,6 +5,7 @@ SUBSYSTEM_DEF(server_maint)
+ wait = 6
+ flags = SS_POST_FIRE_TIMING|SS_FIRE_IN_LOBBY
+ priority = 10
++ init_order = INIT_ORDER_SERVER_MAINT
+ var/list/currentrun
+
+ /datum/controller/subsystem/server_maint/Initialize(timeofday)
diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm
index 7694eb8daf..6e8fa56666 100644
--- a/code/controllers/subsystem/ticker.dm
+++ b/code/controllers/subsystem/ticker.dm
@@ -20,6 +20,7 @@ SUBSYSTEM_DEF(ticker)
var/login_music //music played in pregame lobby
var/round_end_sound //music/jingle played when the world reboots
+ var/round_end_sound_sent = TRUE //If all clients have loaded it
var/list/datum/mind/minds = list() //The characters in the game. Used for objective tracking.
@@ -619,9 +620,9 @@ SUBSYSTEM_DEF(ticker)
sleep(50)
if(mode.station_was_nuked)
- world.Reboot("Station destroyed by Nuclear Device.", "end_proper", "nuke")
+ Reboot("Station destroyed by Nuclear Device.", "end_proper", "nuke")
else
- world.Reboot("Round ended.", "end_proper", "proper completion")
+ Reboot("Round ended.", "end_proper", "proper completion")
/datum/controller/subsystem/ticker/proc/send_tip_of_the_round()
var/m
@@ -788,3 +789,68 @@ SUBSYSTEM_DEF(ticker)
start_at = world.time + newtime
else
timeLeft = newtime
+
+/datum/controller/subsystem/ticker/proc/load_mode()
+ var/list/Lines = world.file2list("data/mode.txt")
+ if(Lines.len)
+ if(Lines[1])
+ GLOB.master_mode = Lines[1]
+ GLOB.world_game_log << "Saved mode is '[GLOB.master_mode]'"
+
+/datum/controller/subsystem/ticker/proc/save_mode(the_mode)
+ var/F = file("data/mode.txt")
+ fdel(F)
+ F << the_mode
+
+/datum/controller/subsystem/ticker/proc/SetRoundEndSound(the_sound)
+ set waitfor = FALSE
+ round_end_sound_sent = FALSE
+ round_end_sound = fcopy_rsc(the_sound)
+ for(var/thing in GLOB.clients)
+ var/client/C = thing
+ if (!C)
+ continue
+ C.Export("##action=load_rsc", round_end_sound)
+ round_end_sound_sent = TRUE
+
+/datum/controller/subsystem/ticker/proc/Reboot(reason, feedback_c, feedback_r, delay)
+ set waitfor = FALSE
+ if(usr && !check_rights(R_SERVER, TRUE))
+ return
+
+ if(!delay)
+ delay = config.round_end_countdown * 10
+
+ if(delay_end)
+ to_chat(world, "An admin has delayed the round end.")
+ return
+
+ to_chat(world, "Rebooting World in [delay/10] [(delay >= 10 && delay < 20) ? "second" : "seconds"]. [reason]")
+
+ var/start_wait = world.time
+ UNTIL(round_end_sound_sent && (world.time - start_wait) > (delay * 2)) //don't wait forever
+ sleep(delay - (world.time - start_wait))
+
+ if(delay_end)
+ to_chat(world, "Reboot was cancelled by an admin.")
+ return
+
+ SSblackbox.set_details("[feedback_c]","[feedback_r]")
+
+ log_game("Rebooting World. [reason]")
+
+ world.Reboot()
+
+/datum/controller/subsystem/ticker/Shutdown()
+ if(!round_end_sound)
+ round_end_sound = pick(\
+ 'sound/roundend/newroundsexy.ogg',
+ 'sound/roundend/apcdestroyed.ogg',
+ 'sound/roundend/bangindonk.ogg',
+ 'sound/roundend/leavingtg.ogg',
+ 'sound/roundend/its_only_game.ogg',
+ 'sound/roundend/yeehaw.ogg',
+ 'sound/roundend/disappointed.ogg'\
+ )
+
+ world << sound(round_end_sound)
\ No newline at end of file
diff --git a/code/controllers/subsystem/ticker.dm.rej b/code/controllers/subsystem/ticker.dm.rej
new file mode 100644
index 0000000000..3f3c042f65
--- /dev/null
+++ b/code/controllers/subsystem/ticker.dm.rej
@@ -0,0 +1,9 @@
+diff a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm (rejected hunks)
+@@ -60,6 +60,7 @@ SUBSYSTEM_DEF(ticker)
+ var/list/round_start_events
+
+ /datum/controller/subsystem/ticker/Initialize(timeofday)
++ load_mode()
+ var/list/music = world.file2list(ROUND_START_MUSIC_LIST, "\n")
+ login_music = pick(music)
+
diff --git a/code/controllers/subsystem/title.dm b/code/controllers/subsystem/title.dm
index d9f4ef9938..6c5466d8d9 100644
--- a/code/controllers/subsystem/title.dm
+++ b/code/controllers/subsystem/title.dm
@@ -55,6 +55,12 @@ SUBSYSTEM_DEF(title)
var/F = file("data/previous_title.dat")
F << file_path
+ for(var/thing in GLOB.clients)
+ if(!thing)
+ continue
+ var/obj/screen/splash/S = new(thing, FALSE)
+ S.Fade(FALSE,FALSE)
+
/datum/controller/subsystem/title/Recover()
icon = SStitle.icon
splash_turf = SStitle.splash_turf
diff --git a/code/controllers/subsystem/vote.dm b/code/controllers/subsystem/vote.dm
index 99234b05ed..ecba3f076c 100644
--- a/code/controllers/subsystem/vote.dm
+++ b/code/controllers/subsystem/vote.dm
@@ -137,7 +137,7 @@ SUBSYSTEM_DEF(vote)
active_admins = 1
break
if(!active_admins)
- world.Reboot("Restart vote successful.", "end_error", "restart vote")
+ SSticker.Reboot("Restart vote successful.", "end_error", "restart vote")
else
to_chat(world, "Notice:Restart vote will not restart the server automatically because there are active admins on.")
message_admins("A restart vote has passed, but there are active admins on with +server, so it has been canceled. If you wish, you may restart the server.")
diff --git a/code/controllers/subsystem/vote.dm.rej b/code/controllers/subsystem/vote.dm.rej
new file mode 100644
index 0000000000..a7334b0b4c
--- /dev/null
+++ b/code/controllers/subsystem/vote.dm.rej
@@ -0,0 +1,10 @@
+diff a/code/controllers/subsystem/vote.dm b/code/controllers/subsystem/vote.dm (rejected hunks)
+@@ -116,7 +116,7 @@ SUBSYSTEM_DEF(vote)
+ restart = 1
+ if("gamemode")
+ if(GLOB.master_mode != .)
+- world.save_mode(.)
++ SSticker.save_mode(.)
+ if(SSticker.HasRoundStarted())
+ restart = 1
+ else
diff --git a/code/game/gamemodes/nuclear/nuclearbomb.dm b/code/game/gamemodes/nuclear/nuclearbomb.dm
index 1f01b38860..5964983b09 100644
--- a/code/game/gamemodes/nuclear/nuclearbomb.dm
+++ b/code/game/gamemodes/nuclear/nuclearbomb.dm
@@ -459,8 +459,7 @@
var/datum/game_mode/nuclear/NM = SSticker.mode
NM.nukes_left --
if(!SSticker.mode.check_finished())//If the mode does not deal with the nuke going off so just reboot because everyone is stuck as is
- spawn()
- world.Reboot("Station destroyed by Nuclear Device.", "end_error", "nuke - unhandled ending")
+ SSticker.Reboot("Station destroyed by Nuclear Device.", "end_error", "nuke - unhandled ending")
/*
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index ac8162bc08..f8329b1f69 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -419,13 +419,18 @@
set desc="Restarts the world immediately"
if (!usr.client.holder)
return
- var/confirm = alert("Restart the game world?", "Restart", "Yes", "Cancel")
- if(confirm == "Cancel")
- return
- if(confirm == "Yes")
- SSticker.delay_end = 0
+
+ var/list/options = list("Regular Restart", "Hard Restart (No Delay/Feeback Reason)", "Hardest Restart (No actions, just reboot)")
+ 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!
- world.Reboot("Initiated by [usr.client.holder.fakekey ? "Admin" : usr.key].", "end_error", "admin reboot - by [usr.key] [usr.client.holder.fakekey ? "(stealth)" : ""]", 10)
+ switch(result)
+ if("Regular Restart")
+ SSticker.Reboot("Initiated by [usr.client.holder.fakekey ? "Admin" : usr.key].", "end_error", "admin reboot - by [usr.key] [usr.client.holder.fakekey ? "(stealth)" : ""]", 10)
+ if("Hard Restart (No Delay, No Feeback Reason)")
+ world.Reboot()
+ if("Hardest Restart (No actions, just reboot)")
+ world.Reboot(fast_track = TRUE)
/datum/admins/proc/end_round()
set category = "Server"
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index c1780e4a34..ebc95436c0 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -1206,7 +1206,7 @@
message_admins("[key_name_admin(usr)] set the mode as [GLOB.master_mode].")
to_chat(world, "The mode is now: [GLOB.master_mode]")
Game() // updates the main game menu
- world.save_mode(GLOB.master_mode)
+ SSticker.save_mode(GLOB.master_mode)
.(href, list("c_mode"=1))
else if(href_list["f_secret2"])
diff --git a/code/modules/admin/verbs/playsound.dm b/code/modules/admin/verbs/playsound.dm
index 2397f6c4a5..f359085941 100644
--- a/code/modules/admin/verbs/playsound.dm
+++ b/code/modules/admin/verbs/playsound.dm
@@ -52,10 +52,7 @@
if(!check_rights(R_SOUNDS))
return
- if(SSticker)
- SSticker.round_end_sound = fcopy_rsc(S)
- else
- return
+ SSticker.SetRoundEndSound(S)
log_admin("[key_name(src)] set the round end sound to [S]")
message_admins("[key_name_admin(src)] set the round end sound to [S]")
diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm
index 0d5e93fba7..d236558ddf 100644
--- a/code/modules/client/client_procs.dm
+++ b/code/modules/client/client_procs.dm
@@ -672,3 +672,7 @@ GLOBAL_LIST(external_rsc_urls)
CRASH("change_view called without argument.")
view = new_size
+
+/client/proc/AnnouncePR(announcement)
+ if(prefs && prefs.chat_toggles & CHAT_PULLR)
+ to_chat(src, announcement)
\ No newline at end of file
diff --git a/code/world.dm b/code/world.dm
index 39d34dfd53..3f274938ec 100644
--- a/code/world.dm
+++ b/code/world.dm
@@ -16,6 +16,32 @@
/world/New()
log_world("World loaded at [time_stamp()]")
+ SetupExternalRSC()
+
+ GLOB.config_error_log = file("data/logs/config_error.log") //temporary file used to record errors with loading config, moved to log directory once logging is set bl
+
+ make_datum_references_lists() //initialises global lists for referencing frequently used datums (so that we only ever do it once)
+
+ config = new
+
+ SetRoundID()
+
+ SetupLogs()
+
+ GLOB.revdata.DownloadPRDetails()
+
+ load_motd()
+ load_admins()
+ load_menu()
+ if(config.usewhitelist)
+ load_whitelist()
+ LoadBans()
+
+ GLOB.timezoneOffset = text2num(time2text(0,"hh")) * 36000
+
+ Master.Initialize(10, FALSE)
+
+/world/proc/SetupExternalRSC()
#if (PRELOAD_RSC == 0)
external_rsc_urls = world.file2list("config/external_rsc_urls.txt","\n")
var/i=1
@@ -25,10 +51,8 @@
else
external_rsc_urls.Cut(i,i+1)
#endif
- GLOB.config_error_log = file("data/logs/config_error.log") //temporary file used to record errors with loading config, moved to log directory once logging is set bl
- make_datum_references_lists() //initialises global lists for referencing frequently used datums (so that we only ever do it once)
- config = new
- GLOB.log_directory = "data/logs/[time2text(world.realtime, "YYYY/MM/DD")]/round-"
+
+/world/proc/SetRoundID()
if(config.sql_enabled)
if(SSdbcore.Connect())
log_world("Database connection established.")
@@ -38,10 +62,14 @@
query_feedback_max_id.Execute()
if(query_feedback_max_id.NextRow())
GLOB.round_id = query_feedback_max_id.item[1]
- GLOB.log_directory += "[GLOB.round_id]"
else
log_world("Your server failed to establish a connection with the database.")
- if(!GLOB.round_id)
+
+/world/proc/SetupLogs()
+ GLOB.log_directory = "data/logs/[time2text(world.realtime, "YYYY/MM/DD")]/round-"
+ if(GLOB.round_id)
+ GLOB.log_directory += "[GLOB.round_id]"
+ else
GLOB.log_directory += "[replacetext(time_stamp(), ":", ".")]"
GLOB.world_game_log = file("[GLOB.log_directory]/game.log")
GLOB.world_attack_log = file("[GLOB.log_directory]/attack.log")
@@ -241,17 +269,10 @@
#undef WORLD_REBOOT
/world/proc/OnReboot(reason, feedback_c, feedback_r, round_end_sound_sent)
- SSblackbox.set_details("[feedback_c]","[feedback_r]")
log_game("Rebooting World. [reason]")
- SSblackbox.set_val("ahelp_unresolved", GLOB.ahelp_tickets.active_tickets.len)
- Master.Shutdown() //run SS shutdowns
- RoundEndAnimation(round_end_sound_sent)
- kick_clients_in_lobby("The round came to an end with you in the lobby.", 1) //second parameter ensures only afk clients are kicked
to_chat(world, "Rebooting world...")
- for(var/thing in GLOB.clients)
- var/client/C = thing
- if(C && config.server) //if you set a server location in config.txt, it sends you there instead of trying to reconnect to the same world address. -- NeoFite
- C << link("byond://[config.server]")
+ RoundEndAnimation(round_end_sound_sent)
+ Master.Shutdown() //run SS shutdowns
/world/proc/RoundEndAnimation(round_end_sound_sent)
set waitfor = FALSE
diff --git a/code/world.dm.rej b/code/world.dm.rej
new file mode 100644
index 0000000000..b192dd6aa4
--- /dev/null
+++ b/code/world.dm.rej
@@ -0,0 +1,12 @@
+diff a/code/world.dm b/code/world.dm (rejected hunks)
+@@ -215,8 +215,8 @@
+ for(var/client/C in GLOB.clients)
+ C.AnnouncePR(final_composed)
+
+-/world/Reboot(reason)
+- if (reason) //special reboot, do none of the normal stuff
++/world/Reboot(reason = 0, fast_track = FALSE)
++ 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")
+ message_admins("[key_name_admin(usr)] Has requested an immediate world restart via client side debugging tools")