diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm index c5f8ebc1380..44b1593b395 100644 --- a/code/controllers/subsystem/ticker.dm +++ b/code/controllers/subsystem/ticker.dm @@ -59,6 +59,8 @@ SUBSYSTEM_DEF(ticker) var/mode_result = "undefined" /// Server end state (Did we end properly or reboot or nuke or what) var/end_state = "undefined" + /// Time the real reboot kicks in + var/real_reboot_time = 0 /datum/controller/subsystem/ticker/Initialize() login_music = pick(\ @@ -128,9 +130,9 @@ SUBSYSTEM_DEF(ticker) spawn(50) if(mode.station_was_nuked) - world.Reboot("Station destroyed by Nuclear Device.", "nuke") + reboot_helper("Station destroyed by Nuclear Device.", "nuke") else - world.Reboot("Round ended.", "proper completion") + reboot_helper("Round ended.", "proper completion") /datum/controller/subsystem/ticker/proc/setup() cultdat = setupcult() @@ -541,3 +543,38 @@ SUBSYSTEM_DEF(ticker) var/datum/trade_destination/D = new loc_type GLOB.weighted_randomevent_locations[D] = D.viable_random_events.len GLOB.weighted_mundaneevent_locations[D] = D.viable_mundane_events.len + +// Easy handler to make rebooting the world not a massive sleep in world/Reboot() +/datum/controller/subsystem/ticker/proc/reboot_helper(reason, end_string, delay) + // Admins delayed round end. Just alert and dont bother with anything else. + if(delay_end) + to_chat(world, "An admin has delayed the round end.") + return + + if(!isnull(delay)) + // Delay time was present. Use that. + delay = max(0, delay) + else + // Use default restart timeout + delay = restart_timeout + + to_chat(world, "Rebooting world in [delay/10] [delay > 10 ? "seconds" : "second"]. [reason]") + + real_reboot_time = world.time + delay + UNTIL(world.time > real_reboot_time) // Hold it here + + // And if we re-delayed, bail again + if(delay_end) + to_chat(world, "Reboot was cancelled by an admin.") + return + + if(end_string) + end_state = end_string + + // Play a haha funny noise + var/round_end_sound = pick(GLOB.round_end_sounds) + var/sound_length = GLOB.round_end_sounds[round_end_sound] + world << round_end_sound + sleep(sound_length) + + world.Reboot() diff --git a/code/controllers/subsystem/vote.dm b/code/controllers/subsystem/vote.dm index b5928e09859..a60f4835316 100644 --- a/code/controllers/subsystem/vote.dm +++ b/code/controllers/subsystem/vote.dm @@ -180,7 +180,7 @@ SUBSYSTEM_DEF(vote) if(restart) - world.Reboot("Restart vote successful.", "restart vote") + SSticker.reboot_helper("Restart vote successful.", "restart vote") return . diff --git a/code/game/gamemodes/nuclear/nuclearbomb.dm b/code/game/gamemodes/nuclear/nuclearbomb.dm index 58a197c3302..99e9dfaeb35 100644 --- a/code/game/gamemodes/nuclear/nuclearbomb.dm +++ b/code/game/gamemodes/nuclear/nuclearbomb.dm @@ -382,7 +382,7 @@ GLOBAL_VAR(bomb_set) //kinda shit but I couldn't get permission to do what I wanted to do. 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 - world.Reboot("Station destroyed by Nuclear Device.", "nuke - unhandled ending") + SSticker.reboot_helper("Station destroyed by Nuclear Device.", "nuke - unhandled ending") return return diff --git a/code/game/world.dm b/code/game/world.dm index 9cfb62b1f82..d2fb442c5f9 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -102,9 +102,9 @@ GLOBAL_LIST_EMPTY(world_topic_handlers) wth = new wth() return wth.invoke(input) -/world/Reboot(var/reason, end_string, var/time) +/world/Reboot(reason, fast_track = FALSE) //special reboot, do none of the normal stuff - if(reason == 1) // Do NOT change this to if(reason). You WILL break the entirety of world rebooting + if((reason == 1) || fast_track) // Do NOT change this to if(reason). You WILL break the entirety of world rebooting if(usr) if(!check_rights(R_SERVER)) message_admins("[key_name_admin(usr)] attempted to restart the server via the Profiler, without access.") @@ -112,71 +112,53 @@ GLOBAL_LIST_EMPTY(world_topic_handlers) return message_admins("[key_name_admin(usr)] has requested an immediate world restart via client side debugging tools") log_admin("[key_name(usr)] has requested an immediate world restart via client side debugging tools") - spawn(0) to_chat(world, "Rebooting world immediately due to host request") rustg_log_close_all() // Past this point, no logging procs can be used, at risk of data loss. - TgsReboot() + // Now handle a reboot if(config && config.shutdown_on_reboot) sleep(0) if(GLOB.shutdown_shell_command) shell(GLOB.shutdown_shell_command) del(world) + TgsEndProcess() // We want to shutdown on reboot. That means kill our TGS process "gracefully", instead of the watchdog crying return else + TgsReboot() // Tell TGS we did a reboot return ..(1) - var/delay - if(!isnull(time)) - delay = max(0,time) - else - delay = SSticker.restart_timeout - if(SSticker.delay_end) - to_chat(world, "An admin has delayed the round end.") - return - to_chat(world, "Rebooting world in [delay/10] [delay > 10 ? "seconds" : "second"]. [reason]") - - var/round_end_sound = pick(GLOB.round_end_sounds) - var/sound_length = GLOB.round_end_sounds[round_end_sound] - if(delay > sound_length) // If there's time, play the round-end sound before rebooting - spawn(delay - sound_length) - if(!SSticker.delay_end) - world << round_end_sound - sleep(delay) - if(SSticker.delay_end) - to_chat(world, "Reboot was cancelled by an admin.") - return - log_game("Rebooting world. [reason]") - //kick_clients_in_lobby("The round came to an end with you in the lobby.", 1) - - if(end_string) - SSticker.end_state = end_string - - Master.Shutdown() //run SS shutdowns - rustg_log_close_all() // Past this point, no logging procs can be used, at risk of data loss. - TgsReboot() + // If we got here, we are in a "normal" reboot + Master.Shutdown() // Shutdown subsystems + // If we were running unit tests, finish that run #ifdef UNIT_TESTS FinishTestRun() return #endif + // If we had an update or pending TM, set a 60 second timeout var/secs_before_auto_reconnect = 10 if(GLOB.pending_server_update) secs_before_auto_reconnect = 60 to_chat(world, "Reboot will take a little longer, due to pending updates.") + // Send the reboot banner to all players for(var/client/C in GLOB.clients) C << output(list2params(list(secs_before_auto_reconnect)), "browseroutput:reboot") - if(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 + if(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]") + // And begin the real shutdown + rustg_log_close_all() // Past this point, no logging procs can be used, at risk of data loss. if(config && config.shutdown_on_reboot) sleep(0) if(GLOB.shutdown_shell_command) shell(GLOB.shutdown_shell_command) + rustg_log_close_all() // Past this point, no logging procs can be used, at risk of data loss. del(world) + TgsEndProcess() // We want to shutdown on reboot. That means kill our TGS process "gracefully", instead of the watchdog crying return else + TgsReboot() // We did a normal reboot. Tell TGS we did a normal reboot. ..(0) /world/proc/load_mode() diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index a91eb296413..1d63a00874f 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -363,16 +363,47 @@ GLOBAL_VAR_INIT(nologevent, 0) if(!check_rights(R_SERVER)) return - var/delay = input("What delay should the restart have (in seconds)?", "Restart Delay", 5) as num|null - if(isnull(delay)) - return - else - delay = delay * 10 - message_admins("[key_name_admin(usr)] has initiated a server restart with a delay of [delay/10] seconds") - log_admin("[key_name(usr)] has initiated a server restart with a delay of [delay/10] seconds") - SSticker.delay_end = 0 - SSblackbox.record_feedback("tally", "admin_verb", 1, "Reboot Server") //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].", "admin reboot - by [usr.key] [usr.client.holder.fakekey ? "(stealth)" : ""]", delay) + // Give an extra popup if they are rebooting a live server + var/is_live_server = TRUE + if(usr.client.is_connecting_from_localhost()) + is_live_server = FALSE + + var/list/options = list("Regular Restart", "Hard Restart") + if(world.TgsAvailable()) // TGS lets you kill the process entirely + options += "Terminate Process (Kill and restart DD)" + + var/result = input(usr, "Select reboot method", "World Reboot", options[1]) as null|anything in options + + if(is_live_server) + if(alert(usr, "WARNING: THIS IS A LIVE SERVER, NOT A LOCAL TEST SERVER. DO YOU STILL WANT TO RESTART","This server is live","Restart","Cancel") != "Restart") + return FALSE + + if(result) + SSblackbox.record_feedback("tally", "admin_verb", 1, "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") + var/delay = input("What delay should the restart have (in seconds)?", "Restart Delay", 5) as num|null + if(!delay) + return FALSE + + + // These are pasted each time so that they dont false send if reboot is cancelled + message_admins("[key_name_admin(usr)] has initiated a server restart of type [result]") + log_admin("[key_name(usr)] has initiated a server restart of type [result]") + SSticker.delay_end = FALSE // We arent delayed anymore + SSticker.reboot_helper(init_by, "admin reboot - by [usr.key] [usr.client.holder.fakekey ? "(stealth)" : ""]", delay * 10) + + if("Hard Restart") + message_admins("[key_name_admin(usr)] has initiated a server restart of type [result]") + log_admin("[key_name(usr)] has initiated a server restart of type [result]") + world.Reboot(fast_track = TRUE) + + if("Terminate Process (Kill and restart DD)") + message_admins("[key_name_admin(usr)] has initiated a server restart of type [result]") + log_admin("[key_name(usr)] has initiated a server restart of type [result]") + world.TgsEndProcess() // Just nuke the entire process if we are royally fucked /datum/admins/proc/end_round() set category = "Server" @@ -576,6 +607,8 @@ GLOBAL_VAR_INIT(nologevent, 0) SSticker.delay_end = !SSticker.delay_end log_admin("[key_name(usr)] [SSticker.delay_end ? "delayed the round end" : "has made the round end normally"].") message_admins("[key_name(usr)] [SSticker.delay_end ? "delayed the round end" : "has made the round end normally"].", 1) + if(SSticker.delay_end) + SSticker.real_reboot_time = 0 // Immediately show the "Admin delayed round end" message return //alert("Round end delayed", null, null, null, null, null) if(SSticker.ticker_going) SSticker.ticker_going = FALSE @@ -883,3 +916,4 @@ GLOBAL_VAR_INIT(gamma_ship_location, 1) // 0 = station , 1 = space continue result[1]++ return result + diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index ae72433bd77..fb21ec0380b 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -317,6 +317,8 @@ SSticker.delay_end = !SSticker.delay_end log_admin("[key_name(usr)] [SSticker.delay_end ? "delayed the round end" : "has made the round end normally"].") message_admins("[key_name_admin(usr)] [SSticker.delay_end ? "delayed the round end" : "has made the round end normally"].", 1) + if(SSticker.delay_end) + SSticker.real_reboot_time = 0 // If they set this at round end, show the "Reboot was cancelled by an admin" message instantly href_list["secretsadmin"] = "check_antagonist" else if(href_list["simplemake"]) diff --git a/code/modules/unit_tests/unit_test.dm b/code/modules/unit_tests/unit_test.dm index 036ee996781..8fd15ce81cc 100644 --- a/code/modules/unit_tests/unit_test.dm +++ b/code/modules/unit_tests/unit_test.dm @@ -70,7 +70,7 @@ GLOBAL_VAR(test_log) CHECK_TICK - world.Reboot("Unit Test Reboot", "tests ended", 0) + SSticker.reboot_helper("Unit Test Reboot", "tests ended", 0) // OTHER MISC PROCS RELATED TO UNIT TESTS //