#define RESTART_COUNTER_PATH "data/round_counter.txt" /// Load byond-tracy. If USE_BYOND_TRACY is defined, then this is ignored and byond-tracy is always loaded. #define USE_TRACY_PARAMETER "tracy" /// Force the log directory to be something specific in the data/logs folder #define OVERRIDE_LOG_DIRECTORY_PARAMETER "log-directory" /// Prevent the master controller from starting automatically #define NO_INIT_PARAMETER "no-init" GLOBAL_VAR(restart_counter) GLOBAL_VAR(tracy_log) /** * WORLD INITIALIZATION * THIS IS THE INIT ORDER: * * BYOND => * - (secret init native) => * - world.Genesis() => * - world.init_byond_tracy() * - (Start native profiling) * - world.init_debugger() * - Master => * - config *unloaded * - (all subsystems) PreInit() * - GLOB => * - make_datum_reference_lists() * - (/static variable inits, reverse declaration order) * - (all pre-mapped atoms) /atom/New() * - world.New() => * - config.Load() * - world.InitTgs() => * - TgsNew() *may sleep * - GLOB.rev_data.load_tgs_info() * - world.ConfigLoaded() => * - SSdbcore.InitializeRound() * - world.SetupLogs() * - load_admins() * - ... * - Master.Initialize() => * - (all subsystems) Initialize() * - Master.StartProcessing() => * - Master.Loop() => * - Failsafe * - world.RunUnattendedFunctions() * * Now listen up because I want to make something clear: * If something is not in this list it should almost definitely be handled by a subsystem Initialize()ing * If whatever it is that needs doing doesn't fit in a subsystem you probably aren't trying hard enough tbhfam * * GOT IT MEMORIZED? * - Dominion/Cyberboss * * Where to put init shit quick guide: * If you need it to happen before the mc is created: world/Genesis. * If you need it to happen last: world/New(), * Otherwise, in a subsystem preinit or init. Subsystems can set an init priority. */ /** * THIS !!!SINGLE!!! PROC IS WHERE ANY FORM OF INIITIALIZATION THAT CAN'T BE PERFORMED IN SUBSYSTEMS OR WORLD/NEW IS DONE * NOWHERE THE FUCK ELSE * I DON'T CARE HOW MANY LAYERS OF DEBUG/PROFILE/TRACE WE HAVE, YOU JUST HAVE TO DEAL WITH THIS PROC EXISTING * I'M NOT EVEN GOING TO TELL YOU WHERE IT'S CALLED FROM BECAUSE I'M DECLARING THAT FORBIDDEN KNOWLEDGE * SO HELP ME GOD IF I FIND ABSTRACTION LAYERS OVER THIS! */ /world/proc/Genesis(tracy_initialized = FALSE) RETURN_TYPE(/datum/controller/master) #ifdef USE_BYOND_TRACY #warn USE_BYOND_TRACY is enabled if(!tracy_initialized) #else if(!tracy_initialized && (USE_TRACY_PARAMETER in params)) #endif GLOB.tracy_log = init_byond_tracy() Genesis(tracy_initialized = TRUE) return Profile(PROFILE_RESTART) Profile(PROFILE_RESTART, type = "sendmaps") // Write everything to this log file until we get to SetupLogs() later _initialize_log_files("data/logs/config_error.[GUID()].log") // Init the debugger first so we can debug Master init_debugger() // Create the logger logger = new // THAT'S IT, WE'RE DONE, THE. FUCKING. END. Master = new /** * World creation * * Here is where a round itself is actually begun and setup. * * db connection setup * * config loaded from files * * loads admins * * Sets up the dynamic menu system * * and most importantly, calls initialize on the master subsystem, starting the game loop that causes the rest of the game to begin processing and setting up * * * Nothing happens until something moves. ~Albert Einstein * * For clarity, this proc gets triggered later in the initialization pipeline, it is not the first thing to happen, as it might seem. * * Initialization Pipeline: * Global vars are new()'ed, (including config, glob, and the master controller will also new and preinit all subsystems when it gets new()ed) * Compiled in maps are loaded (mainly centcom). all areas/turfs/objs/mobs(ATOMs) in these maps will be new()ed * world/New() (You are here) * Once world/New() returns, client's can connect. * 1 second sleep * Master Controller initialization. * Subsystem initialization. * Non-compiled-in maps are maploaded, all atoms are new()ed * All atoms in both compiled and uncompiled maps are initialized() */ /world/New() log_world("World loaded at [time_stamp()]!") // From a really fucking old commit (91d7150) // I wanted to move it but I think this needs to be after /world/New is called but before any sleeps? // - Dominion/Cyberboss GLOB.timezoneOffset = world.timezone * 36000 // First possible sleep() InitTgs() config.Load(params[OVERRIDE_CONFIG_DIRECTORY_PARAMETER]) ConfigLoaded() if(NO_INIT_PARAMETER in params) return Master.Initialize(10, FALSE, TRUE) RunUnattendedFunctions() /// Initializes TGS and loads the returned revising info into GLOB.revdata /world/proc/InitTgs() TgsNew(new /datum/tgs_event_handler/impl, TGS_SECURITY_TRUSTED) GLOB.revdata.load_tgs_info() /// Runs after config is loaded but before Master is initialized /world/proc/ConfigLoaded() // Everything in here is prioritized in a very specific way. // If you need to add to it, ask yourself hard if what your adding is in the right spot // (i.e. basically nothing should be added before load_admins() in here) // Try to set round ID SSdbcore.InitializeRound() SetupLogs() load_admins() load_poll_data() LoadVerbs(/datum/verbs/menu) if(fexists(RESTART_COUNTER_PATH)) GLOB.restart_counter = text2num(trim(file2text(RESTART_COUNTER_PATH))) fdel(RESTART_COUNTER_PATH) /// Runs after the call to Master.Initialize, but before the delay kicks in. Used to turn the world execution into some single function then exit /world/proc/RunUnattendedFunctions() #ifdef UNIT_TESTS HandleTestRun() #endif #ifdef AUTOWIKI setup_autowiki() #endif /world/proc/HandleTestRun() //trigger things to run the whole process Master.sleep_offline_after_initializations = FALSE SSticker.start_immediately = TRUE CONFIG_SET(number/round_end_countdown, 0) var/datum/callback/cb #ifdef UNIT_TESTS cb = CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(RunUnitTests)) #else cb = VARSET_CALLBACK(SSticker, force_ending, ADMIN_FORCE_END_ROUND) #endif SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(_addtimer), cb, 10 SECONDS)) /// Returns a list of data about the world state, don't clutter /world/proc/get_world_state_for_logging() var/data = list() data["tick_usage"] = world.tick_usage data["tick_lag"] = world.tick_lag data["time"] = world.time data["timestamp"] = rustg_unix_timestamp() return data /world/proc/SetupLogs() var/override_dir = params[OVERRIDE_LOG_DIRECTORY_PARAMETER] if(!override_dir) var/realtime = world.realtime var/texttime = time2text(realtime, "YYYY/MM/DD") GLOB.log_directory = "data/logs/[texttime]/round-" GLOB.picture_logging_prefix = "L_[time2text(realtime, "YYYYMMDD")]_" GLOB.picture_log_directory = "data/picture_logs/[texttime]/round-" if(GLOB.round_id) GLOB.log_directory += "[GLOB.round_id]" GLOB.picture_logging_prefix += "R_[GLOB.round_id]_" GLOB.picture_log_directory += "[GLOB.round_id]" else var/timestamp = replacetext(time_stamp(), ":", ".") GLOB.log_directory += "[timestamp]" GLOB.picture_log_directory += "[timestamp]" GLOB.picture_logging_prefix += "T_[timestamp]_" else GLOB.log_directory = "data/logs/[override_dir]" GLOB.picture_logging_prefix = "O_[override_dir]_" GLOB.picture_log_directory = "data/picture_logs/[override_dir]" logger.init_logging() if(GLOB.tracy_log) rustg_file_write("[GLOB.tracy_log]", "[GLOB.log_directory]/tracy.loc") var/latest_changelog = file("[global.config.directory]/../html/changelogs/archive/" + time2text(world.timeofday, "YYYY-MM") + ".yml") GLOB.changelog_hash = fexists(latest_changelog) ? md5(latest_changelog) : 0 //for telling if the changelog has changed recently if(GLOB.round_id) log_game("Round ID: [GLOB.round_id]") // This was printed early in startup to the world log and config_error.log, // but those are both private, so let's put the commit info in the runtime // log which is ultimately public. log_runtime(GLOB.revdata.get_log_message()) #ifndef USE_CUSTOM_ERROR_HANDLER world.log = file("[GLOB.log_directory]/dd.log") #else if (TgsAvailable()) // why world.log = file("[GLOB.log_directory]/dd.log") //not all runtimes trigger world/Error, so this is the only way to ensure we can see all of them. #endif /world/Topic(T, addr, master, key) TGS_TOPIC //redirect to server tools if necessary var/static/list/topic_handlers = TopicHandlers() var/list/input = params2list(T) var/datum/world_topic/handler for(var/I in topic_handlers) if(I in input) handler = topic_handlers[I] break if((!handler || initial(handler.log)) && config && CONFIG_GET(flag/log_world_topic)) log_topic("\"[T]\", from:[addr], master:[master], key:[key]") if(!handler) return handler = new handler() return handler.TryRun(input) /world/proc/AnnouncePR(announcement, list/payload) var/static/list/PRcounts = list() //PR id -> number of times announced this round var/id = "[payload["pull_request"]["id"]]" if(!PRcounts[id]) PRcounts[id] = 1 else ++PRcounts[id] if(PRcounts[id] > CONFIG_GET(number/pr_announcements_per_round)) return var/final_composed = span_announce("PR: [announcement]") for(var/client/C in GLOB.clients) C.AnnouncePR(final_composed) /world/proc/FinishTestRun() set waitfor = FALSE var/list/fail_reasons if(GLOB) if(GLOB.total_runtimes != 0) fail_reasons = list("Total runtimes: [GLOB.total_runtimes]") #ifdef UNIT_TESTS if(GLOB.failed_any_test) LAZYADD(fail_reasons, "Unit Tests failed!") #endif if(!GLOB.log_directory) LAZYADD(fail_reasons, "Missing GLOB.log_directory!") else fail_reasons = list("Missing GLOB!") if(!fail_reasons) text2file("Success!", "[GLOB.log_directory]/clean_run.lk") else log_world("Test run failed!\n[fail_reasons.Join("\n")]") sleep(0) //yes, 0, this'll let Reboot finish and prevent byond memes qdel(src) //shut it down /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") to_chat(world, span_boldannounce("Rebooting World immediately due to host request.")) else to_chat(world, span_boldannounce("Rebooting world...")) Master.Shutdown() //run SS shutdowns #ifdef UNIT_TESTS FinishTestRun() return #else if(TgsAvailable()) var/do_hard_reboot // check the hard reboot counter var/ruhr = CONFIG_GET(number/rounds_until_hard_restart) switch(ruhr) if(-1) do_hard_reboot = FALSE if(0) do_hard_reboot = TRUE else if(GLOB.restart_counter >= ruhr) do_hard_reboot = TRUE else text2file("[++GLOB.restart_counter]", RESTART_COUNTER_PATH) do_hard_reboot = FALSE if(do_hard_reboot) log_world("World hard rebooted at [time_stamp()]") shutdown_logging() // See comment below. auxcleanup() TgsEndProcess() return ..() log_world("World rebooted at [time_stamp()]") shutdown_logging() // Past this point, no logging procs can be used, at risk of data loss. auxcleanup() TgsReboot() // TGS can decide to kill us right here, so it's important to do it last ..() #endif /world/proc/auxcleanup() var/debug_server = world.GetConfig("env", "AUXTOOLS_DEBUG_DLL") if (debug_server) call_ext(debug_server, "auxtools_shutdown")() /world/Del() auxcleanup() . = ..() /world/proc/update_status() var/list/features = list() if(LAZYACCESS(SSlag_switch.measures, DISABLE_NON_OBSJOBS)) features += "closed" var/new_status = "" var/hostedby if(config) var/server_name = CONFIG_GET(string/servername) if (server_name) new_status += "[server_name] " if(CONFIG_GET(flag/allow_respawn)) features += "respawn" // show "respawn" regardless of "respawn as char" or "free respawn" if(!CONFIG_GET(flag/allow_ai)) features += "AI disabled" hostedby = CONFIG_GET(string/hostedby) if (CONFIG_GET(flag/station_name_in_hub_entry)) new_status += " — [station_name()]" var/players = GLOB.clients.len game_state = (CONFIG_GET(number/extreme_popcap) && players >= CONFIG_GET(number/extreme_popcap)) //tells the hub if we are full if (!host && hostedby) features += "hosted by [hostedby]" if(length(features)) new_status += ": [jointext(features, ", ")]" if(!SSticker || SSticker?.current_state == GAME_STATE_STARTUP) new_status += "
STARTING" else if(SSticker) if(SSticker.current_state == GAME_STATE_PREGAME && SSticker.GetTimeLeft() > 0) new_status += "
Starting: [round((SSticker.GetTimeLeft())/10)]" else if(SSticker.current_state == GAME_STATE_SETTING_UP) new_status += "
Starting: Now" else if(SSticker.IsRoundInProgress()) new_status += "
Time: [time2text(STATION_TIME_PASSED(), "hh:mm", 0)]" if(SSshuttle?.emergency && SSshuttle?.emergency?.mode != (SHUTTLE_IDLE || SHUTTLE_ENDGAME)) new_status += " | Shuttle: [SSshuttle.emergency.getModeStr()] [SSshuttle.emergency.getTimerStr()]" else if(SSticker.current_state == GAME_STATE_FINISHED) new_status += "
RESTARTING" if(SSmapping.config) new_status += "
Map: [SSmapping.config.map_path == CUSTOM_MAP_PATH ? "Uncharted Territory" : SSmapping.config.map_name]" if(SSmapping.next_map_config) new_status += "[SSmapping.config ? " | " : "
"]Next: [SSmapping.next_map_config.map_path == CUSTOM_MAP_PATH ? "Uncharted Territory" : SSmapping.next_map_config.map_name]" status = new_status /world/proc/update_hub_visibility(new_visibility) if(new_visibility == GLOB.hub_visibility) return GLOB.hub_visibility = new_visibility if(GLOB.hub_visibility) hub_password = "kMZy3U5jJHSiBQjr" else hub_password = "SORRYNOPASSWORD" /** * Handles increasing the world's maxx var and initializing the new turfs and assigning them to the global area. * If map_load_z_cutoff is passed in, it will only load turfs up to that z level, inclusive. * This is because maploading will handle the turfs it loads itself. */ /world/proc/increase_max_x(new_maxx, map_load_z_cutoff = maxz) if(new_maxx <= maxx) return var/old_max = world.maxx maxx = new_maxx if(!map_load_z_cutoff) return var/area/global_area = GLOB.areas_by_type[world.area] // We're guaranteed to be touching the global area, so we'll just do this LISTASSERTLEN(global_area.turfs_by_zlevel, map_load_z_cutoff, list()) for (var/zlevel in 1 to map_load_z_cutoff) var/list/to_add = block( locate(old_max + 1, 1, zlevel), locate(maxx, maxy, zlevel)) global_area.turfs_by_zlevel[zlevel] += to_add /world/proc/increase_max_y(new_maxy, map_load_z_cutoff = maxz) if(new_maxy <= maxy) return var/old_maxy = maxy maxy = new_maxy if(!map_load_z_cutoff) return var/area/global_area = GLOB.areas_by_type[world.area] // We're guaranteed to be touching the global area, so we'll just do this LISTASSERTLEN(global_area.turfs_by_zlevel, map_load_z_cutoff, list()) for (var/zlevel in 1 to map_load_z_cutoff) var/list/to_add = block( locate(1, old_maxy + 1, 1), locate(maxx, maxy, map_load_z_cutoff)) global_area.turfs_by_zlevel[zlevel] += to_add /world/proc/incrementMaxZ() maxz++ SSmobs.MaxZChanged() SSai_controllers.on_max_z_changed() /world/proc/change_fps(new_value = 20) if(new_value <= 0) CRASH("change_fps() called with [new_value] new_value.") if(fps == new_value) return //No change required. fps = new_value on_tickrate_change() /world/proc/change_tick_lag(new_value = 0.5) if(new_value <= 0) CRASH("change_tick_lag() called with [new_value] new_value.") if(tick_lag == new_value) return //No change required. tick_lag = new_value on_tickrate_change() /world/proc/on_tickrate_change() SStimer?.reset_buckets() DREAMLUAU_SET_EXECUTION_LIMIT_MILLIS(tick_lag * 100) /world/proc/init_byond_tracy() var/library switch (system_type) if (MS_WINDOWS) library = "prof.dll" if (UNIX) library = "libprof.so" else CRASH("Unsupported platform: [system_type]") var/init_result = call_ext(library, "init")("block") if(length(init_result) != 0 && init_result[1] == ".") // if first character is ., then it returned the output filename return init_result else if(init_result != "0") CRASH("Error initializing byond-tracy: [init_result]") /world/proc/init_debugger() var/dll = GetConfig("env", "AUXTOOLS_DEBUG_DLL") if (dll) call_ext(dll, "auxtools_init")() enable_debugging() /world/Profile(command, type, format) if((command & PROFILE_STOP) || !global.config?.loaded || !CONFIG_GET(flag/forbid_all_profiling)) . = ..() #undef NO_INIT_PARAMETER #undef OVERRIDE_LOG_DIRECTORY_PARAMETER #undef USE_TRACY_PARAMETER #undef RESTART_COUNTER_PATH