"))
+ for(var/mob/Player in GLOB.player_list)
+ if(Player.mind && !isnewplayer(Player))
+ if(Player.stat != DEAD)
+ var/turf/playerTurf = get_turf(Player)
+ if(emergency_shuttle.departed && emergency_shuttle.evac)
+ if(isNotAdminLevel(playerTurf.z))
+ to_chat(Player, span_filter_system(span_blue(span_bold("You survived the round, but remained on [station_name()] as [Player.real_name]."))))
+ else
+ to_chat(Player, span_filter_system(span_green(span_bold("You managed to survive the events on [station_name()] as [Player.real_name]."))))
+ else if(isAdminLevel(playerTurf.z))
+ to_chat(Player, span_filter_system(span_green(span_bold("You successfully underwent crew transfer after events on [station_name()] as [Player.real_name]."))))
+ else if(issilicon(Player))
+ to_chat(Player, span_filter_system(span_green(span_bold("You remain operational after the events on [station_name()] as [Player.real_name]."))))
+ else
+ to_chat(Player, span_filter_system(span_blue(span_bold("You missed the crew transfer after the events on [station_name()] as [Player.real_name]."))))
+ else
+ if(isobserver(Player))
+ var/mob/observer/dead/O = Player
+ if(!O.started_as_observer)
+ to_chat(Player, span_filter_system(span_red(span_bold("You did not survive the events on [station_name()]..."))))
+ else
+ to_chat(Player, span_filter_system(span_red(span_bold("You did not survive the events on [station_name()]..."))))
+ to_world(span_filter_system(" "))
+
+ for (var/mob/living/silicon/ai/aiPlayer in GLOB.mob_list)
+ if (aiPlayer.stat != 2)
+ to_world(span_filter_system(span_bold("[aiPlayer.name]'s laws at the end of the round were:"))) // VOREStation edit
+ else
+ to_world(span_filter_system(span_bold("[aiPlayer.name]'s laws when it was deactivated were:"))) // VOREStation edit
+ aiPlayer.show_laws(1)
+
+ if (aiPlayer.connected_robots.len)
+ var/robolist = span_bold("The AI's loyal minions were:") + " "
+ for(var/mob/living/silicon/robot/robo in aiPlayer.connected_robots)
+ robolist += "[robo.name][robo.stat?" (Deactivated), ":", "]" // VOREStation edit
+ to_world(span_filter_system("[robolist]"))
+
+ var/dronecount = 0
+
+ for (var/mob/living/silicon/robot/robo in GLOB.mob_list)
+
+ if(istype(robo, /mob/living/silicon/robot/platform))
+ var/mob/living/silicon/robot/platform/tank = robo
+ if(!tank.has_had_player)
+ continue
+
+ if(istype(robo,/mob/living/silicon/robot/drone) && !istype(robo,/mob/living/silicon/robot/drone/swarm))
+ dronecount++
+ continue
+
+ if (!robo.connected_ai)
+ if (robo.stat != 2)
+ to_world(span_filter_system(span_bold("[robo.name] survived as an AI-less stationbound synthetic! Its laws were:"))) // VOREStation edit
+ else
+ to_world(span_filter_system(span_bold("[robo.name] was unable to survive the rigors of being a stationbound synthetic without an AI. Its laws were:"))) // VOREStation edit
+
+ if(robo) //How the hell do we lose robo between here and the world messages directly above this?
+ robo.laws.show_laws(world)
+
+ if(dronecount)
+ to_world(span_filter_system(span_bold("There [dronecount>1 ? "were" : "was"] [dronecount] industrious maintenance [dronecount>1 ? "drones" : "drone"] at the end of this round.")))
+
+ mode.declare_completion()//To declare normal completion.
+
+ //Ask the event manager to print round end information
+ SSevents.RoundEnd()
+
+ //Print a list of antagonists to the server log
+ var/list/total_antagonists = list()
+ //Look into all mobs in world, dead or alive
+ for(var/datum/mind/Mind in minds)
+ var/temprole = Mind.special_role
+ if(temprole) //if they are an antagonist of some sort.
+ if(temprole in total_antagonists) //If the role exists already, add the name to it
+ total_antagonists[temprole] += ", [Mind.name]([Mind.key])"
+ else
+ total_antagonists.Add(temprole) //If the role doesnt exist in the list, create it and add the mob
+ total_antagonists[temprole] += ": [Mind.name]([Mind.key])"
+
+ //Now print them all into the log!
+ log_game("Antagonists at round end were...")
+ for(var/i in total_antagonists)
+ log_game("[i]s[total_antagonists[i]].")
+
+ SSdbcore.SetRoundEnd()
+
+ sleep(5 SECONDS)
+ ready_for_reboot = TRUE
+ standard_reboot()
+
+/datum/controller/subsystem/ticker/proc/standard_reboot()
+ if(ready_for_reboot)
+ if(mode.station_was_nuked)
+ Reboot("Station destroyed by Nuclear Device.", "nuke")
+ else
+ Reboot("Round ended.", "proper completion")
+ else
+ CRASH("Attempted standard reboot without ticker roundend completion")
diff --git a/code/_helpers/sorts/comparators.dm b/code/_helpers/sorts/comparators.dm
index c2d3ffb506..8358330704 100644
--- a/code/_helpers/sorts/comparators.dm
+++ b/code/_helpers/sorts/comparators.dm
@@ -13,7 +13,10 @@
// Sorts subsystems by init_order
/proc/cmp_subsystem_init(datum/controller/subsystem/a, datum/controller/subsystem/b)
- return initial(b.init_order) - initial(a.init_order) //uses initial() so it can be used on types
+ return a.init_order - b.init_order
+
+/proc/cmp_subsystem_init_stage(datum/controller/subsystem/a, datum/controller/subsystem/b)
+ return initial(a.init_stage) - initial(b.init_stage)
// Sorts subsystems by priority
/proc/cmp_subsystem_priority(datum/controller/subsystem/a, datum/controller/subsystem/b)
diff --git a/code/_helpers/time.dm b/code/_helpers/time.dm
index b1d372f435..99c0e61409 100644
--- a/code/_helpers/time.dm
+++ b/code/_helpers/time.dm
@@ -121,24 +121,30 @@ GLOBAL_VAR_INIT(round_start_time, 0)
rollovercheck_last_timeofday = world.timeofday
return midnight_rollovers
-//Increases delay as the server gets more overloaded,
-//as sleeps aren't cheap and sleeping only to wake up and sleep again is wasteful
+///Increases delay as the server gets more overloaded, as sleeps aren't cheap and sleeping only to wake up and sleep again is wasteful
#define DELTA_CALC max(((max(TICK_USAGE, world.cpu) / 100) * max(Master.sleep_delta-1,1)), 1)
-//returns the number of ticks slept
+///returns the number of ticks slept
/proc/stoplag(initial_delay)
- if (!Master || !(Master.current_runlevel & RUNLEVELS_DEFAULT))
+ if (!Master || Master.init_stage_completed < INITSTAGE_MAX)
sleep(world.tick_lag)
return 1
if (!initial_delay)
initial_delay = world.tick_lag
+// Unit tests are not the normal environemnt. The mc can get absolutely thigh crushed, and sleeping procs running for ages is much more common
+// We don't want spurious hard deletes off this, so let's only sleep for the requested period of time here yeah?
+#ifdef UNIT_TESTS
+ sleep(initial_delay)
+ return CEILING(DS2TICKS(initial_delay), 1)
+#else
. = 0
var/i = DS2TICKS(initial_delay)
do
- . += CEILING(i*DELTA_CALC, 1)
- sleep(i*world.tick_lag*DELTA_CALC)
+ . += CEILING(i * DELTA_CALC, 1)
+ sleep(i * world.tick_lag * DELTA_CALC)
i *= 2
while (TICK_USAGE > min(TICK_LIMIT_TO_RUN, Master.current_ticklimit))
+#endif
#undef DELTA_CALC
diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm
index dd9f2fa50a..6d82e50d67 100644
--- a/code/_onclick/click.dm
+++ b/code/_onclick/click.dm
@@ -194,7 +194,7 @@
if(is_incorporeal())
return 0
- if(!ticker)
+ if(!SSticker)
to_chat(src, "You cannot attack people before the game has started.")
return 0
diff --git a/code/_onclick/hud/fullscreen.dm b/code/_onclick/hud/fullscreen.dm
index c6d8a916b7..2a0b8191d9 100644
--- a/code/_onclick/hud/fullscreen.dm
+++ b/code/_onclick/hud/fullscreen.dm
@@ -143,6 +143,15 @@
show_when_dead = TRUE
plane = PLANE_PLAYER_HUD_ABOVE
+/obj/screen/fullscreen/cinematic_backdrop
+ icon = 'icons/hud/screen_gen.dmi'
+ screen_loc = "WEST,SOUTH to EAST,NORTH"
+ icon_state = "flash"
+ plane = SPLASHSCREEN_PLANE
+ layer = CINEMATIC_LAYER
+ color = COLOR_BLACK
+ show_when_dead = TRUE
+
/obj/screen/fullscreen/lighting_backdrop
icon = 'icons/mob/screen_gen.dmi'
icon_state = "flash"
diff --git a/code/controllers/admin.dm b/code/controllers/admin.dm
index 69ec6de5cb..e74cfbed04 100644
--- a/code/controllers/admin.dm
+++ b/code/controllers/admin.dm
@@ -79,7 +79,7 @@ ADMIN_VERB(debug_controller, R_DEBUG, "Debug Controller", "Debug the various per
//Goon PS stuff, and other yet-to-be-subsystem things.
options["LEGACY: master_controller"] = master_controller
options["LEGACY: job_master"] = job_master
- options["LEGACY: radio_controller"] = radio_controller
+ options["LEGACY: SSradio"] = SSradio
options["LEGACY: emergency_shuttle"] = emergency_shuttle
options["LEGACY: paiController"] = paiController
options["LEGACY: cameranet"] = cameranet
diff --git a/code/controllers/communications.dm b/code/controllers/communications.dm
deleted file mode 100644
index 3bccb4b944..0000000000
--- a/code/controllers/communications.dm
+++ /dev/null
@@ -1,394 +0,0 @@
-/*
- HOW IT WORKS
-
- The radio_controller is a global object maintaining all radio transmissions, think about it as about "ether".
- Note that walkie-talkie, intercoms and headsets handle transmission using nonstandard way.
- procs:
-
- add_object(obj/device as obj, var/new_frequency as num, var/radio_filter as text|null = null)
- Adds listening object.
- parameters:
- device - device receiving signals, must have proc receive_signal (see description below).
- one device may listen several frequencies, but not same frequency twice.
- new_frequency - see possibly frequencies below;
- radio_filter - thing for optimization. Optional, but recommended.
- All filters should be consolidated in this file, see defines later.
- Device without listening filter will receive all signals (on specified frequency).
- Device with filter will receive any signals sent without filter.
- Device with filter will not receive any signals sent with different filter.
- returns:
- Reference to frequency object.
-
- remove_object (obj/device, old_frequency)
- Obliviously, after calling this proc, device will not receive any signals on old_frequency.
- Other frequencies will left unaffected.
-
- return_frequency(var/frequency as num)
- returns:
- Reference to frequency object. Use it if you need to send and do not need to listen.
-
- radio_frequency is a global object maintaining list of devices that listening specific frequency.
- procs:
-
- post_signal(obj/source as obj|null, datum/signal/signal, var/radio_filter as text|null = null, var/range as num|null = null)
- Sends signal to all devices that wants such signal.
- parameters:
- source - object, emitted signal. Usually, devices will not receive their own signals.
- signal - see description below.
- radio_filter - described above.
- range - radius of regular byond's square circle on that z-level. null means everywhere, on all z-levels.
-
- obj/proc/receive_signal(datum/signal/signal, var/receive_method as num, var/receive_param)
- Handler from received signals. By default does nothing. Define your own for your object.
- Avoid of sending signals directly from this proc, use spawn(-1). DO NOT use sleep() here or call procs that sleep please. If you must, use spawn()
- parameters:
- signal - see description below. Extract all needed data from the signal before doing sleep(), spawn() or return!
- receive_method - may be TRANSMISSION_WIRE or TRANSMISSION_RADIO.
- TRANSMISSION_WIRE is currently unused.
- receive_param - for TRANSMISSION_RADIO here comes frequency.
-
- datum/signal
- vars:
- source
- an object that emitted signal. Used for debug and bearing.
- data
- list with transmitting data. Usual use pattern:
- data["msg"] = "hello world"
- encryption
- Some number symbolizing "encryption key".
- Note that game actually do not use any cryptography here.
- If receiving object don't know right key, it must ignore encrypted signal in its receive_signal.
-
-*/
-
-/*
-Frequency range: 1200 to 1600
-Radiochat range: 1441 to 1489 (most devices refuse to be tune to other frequency, even during mapmaking)
-
-Radio:
-1459 - standard radio chat
-1351 - Science
-1353 - Command
-1355 - Medical
-1357 - Engineering
-1359 - Security
-1341 - deathsquad
-1443 - Confession Intercom
-1347 - Cargo techs
-1349 - Service people
-
-Devices:
-1451 - tracking implant
-1457 - RSD default
-
-On the map:
-1311 for prison shuttle console (in fact, it is not used)
-1433 for engine components
-1435 for status displays
-1437 for atmospherics/fire alerts
-1439 for air pumps, air scrubbers, atmo control
-1441 for atmospherics - supply tanks
-1443 for atmospherics - distribution loop/mixed air tank
-1445 for bot nav beacons
-1447 for mulebot, secbot and ed209 control
-1449 for airlock controls, electropack, magnets
-1451 for toxin lab access
-1453 for engineering access
-1455 for AI access
-*/
-
-var/const/RADIO_LOW_FREQ = 1200
-var/const/PUBLIC_LOW_FREQ = 1441
-var/const/PUBLIC_HIGH_FREQ = 1489
-var/const/RADIO_HIGH_FREQ = 1600
-
-var/const/BOT_FREQ = 1447
-var/const/COMM_FREQ = 1353
-var/const/ERT_FREQ = 1345
-var/const/AI_FREQ = 1343
-var/const/DTH_FREQ = 1341
-var/const/SYND_FREQ = 1213
-var/const/RAID_FREQ = 1277
-var/const/ENT_FREQ = 1461 //entertainment frequency. This is not a diona exclusive frequency.
-
-// department channels
-var/const/PUB_FREQ = 1459
-var/const/SEC_FREQ = 1359
-var/const/ENG_FREQ = 1357
-var/const/MED_FREQ = 1355
-var/const/SCI_FREQ = 1351
-var/const/SRV_FREQ = 1349
-var/const/SUP_FREQ = 1347
-var/const/EXP_FREQ = 1361
-
-// internal department channels
-var/const/MED_I_FREQ = 1485
-var/const/SEC_I_FREQ = 1475
-
-var/const/TALON_FREQ = 1363 //VOREStation Add
-var/const/CSN_FREQ = 1365 //VOREStation Add
-
-var/list/radiochannels = list(
- CHANNEL_COMMON = PUB_FREQ,
- CHANNEL_SCIENCE = SCI_FREQ,
- CHANNEL_COMMAND = COMM_FREQ,
- CHANNEL_MEDICAL = MED_FREQ,
- CHANNEL_ENGINEERING = ENG_FREQ,
- CHANNEL_SECURITY = SEC_FREQ,
- CHANNEL_RESPONSE_TEAM = ERT_FREQ,
- CHANNEL_SPECIAL_OPS = DTH_FREQ,
- CHANNEL_MERCENARY = SYND_FREQ,
- CHANNEL_RAIDER = RAID_FREQ,
- CHANNEL_SUPPLY = SUP_FREQ,
- CHANNEL_SERVICE = SRV_FREQ,
- CHANNEL_EXPLORATION = EXP_FREQ,
- CHANNEL_AI_PRIVATE = AI_FREQ,
- CHANNEL_ENTERTAINMENT = ENT_FREQ,
- CHANNEL_MEDICAL_1 = MED_I_FREQ,
- CHANNEL_SECURITY_1 = SEC_I_FREQ,
- CHANNEL_TALON = TALON_FREQ, //VOREStation Add
- CHANNEL_CASINO = CSN_FREQ,
-)
-
-// Hey, if anyone ever needs to update tgui/packages/tgui/constants.js with new radio channels
-// I've kept this around just for you.
-/* /client/verb/generate_tgui_radio_constants()
- set name = "Generate TGUI Radio Constants"
- set category = "Generate TGUI Radio Constants"
-
- var/list/channel_info = list()
-
- for(var/i in RADIO_LOW_FREQ to RADIO_HIGH_FREQ)
- for(var/key in radiochannels)
- if(i == radiochannels[key])
- channel_info.Add(list(list("name" = key, "freq" = i, "color" = frequency_span_class(i))))
-
- for(var/list/channel in channel_info)
- switch(channel["color"])
- if("deadsay") channel["color"] = "#530FAD"
- if("radio") channel["color"] = "#008000"
- if("deptradio") channel["color"] = "#ff00ff"
- if("newscaster") channel["color"] = "#750000"
- if("comradio") channel["color"] = "#193A7A"
- if("syndradio") channel["color"] = "#6D3F40"
- if("centradio") channel["color"] = "#5C5C8A"
- if("airadio") channel["color"] = "#FF00FF"
- if("entradio") channel["color"] = "#339966"
- if("secradio") channel["color"] = "#A30000"
- if("engradio") channel["color"] = "#A66300"
- if("medradio") channel["color"] = "#008160"
- if("sciradio") channel["color"] = "#993399"
- if("supradio") channel["color"] = "#5F4519"
- if("srvradio") channel["color"] = "#6eaa2c"
- if("expradio") channel["color"] = "#555555"
-
- to_chat(src, json_encode(channel_info)) */
-
-
-// central command channels, i.e deathsquid & response teams
-var/list/CENT_FREQS = list(ERT_FREQ, DTH_FREQ)
-
-// Antag channels, i.e. Syndicate
-var/list/ANTAG_FREQS = list(SYND_FREQ, RAID_FREQ)
-
-//Department channels, arranged lexically
-var/list/DEPT_FREQS = list(AI_FREQ, COMM_FREQ, ENG_FREQ, ENT_FREQ, MED_FREQ, SEC_FREQ, SCI_FREQ, SRV_FREQ, SUP_FREQ)
-
-var/list/OFFMAP_FREQS = list(TALON_FREQ, CSN_FREQ) //VOREStation Add
-
-/proc/frequency_span_class(var/frequency)
- // Antags!
- if (frequency in ANTAG_FREQS)
- return "syndradio"
- // CentCom channels (deathsquid and ert)
- if(frequency in CENT_FREQS)
- return "centradio"
- // command channel
- if(frequency == COMM_FREQ)
- return "comradio"
- // AI private channel
- if(frequency == AI_FREQ)
- return "airadio"
- // department radio formatting (poorly optimized, ugh)
- if(frequency == SEC_FREQ)
- return "secradio"
- if (frequency == ENG_FREQ)
- return "engradio"
- if(frequency == SCI_FREQ)
- return "sciradio"
- if(frequency == MED_FREQ)
- return "medradio"
- if(frequency == SUP_FREQ) // cargo
- return "supradio"
- if(frequency == SRV_FREQ) // service
- return "srvradio"
- if(frequency == EXP_FREQ) // explorer
- return "expradio"
- if(frequency == ENT_FREQ) // entertainment
- return "entradio"
- if(frequency in DEPT_FREQS)
- return "deptradio"
- //VOREStation Add
- if(frequency in OFFMAP_FREQS)
- return "expradio"
- //VOREStation Add End
- return "radio"
-
-/* filters */
-//When devices register with the radio controller, they might register under a certain filter.
-//Other devices can then choose to send signals to only those devices that belong to a particular filter.
-//This is done for performance, so we don't send signals to lots of machines unnecessarily.
-
-//This filter is special because devices belonging to default also recieve signals sent to any other filter.
-var/const/RADIO_DEFAULT = "radio_default"
-
-var/const/RADIO_TO_AIRALARM = "radio_airalarm" //air alarms
-var/const/RADIO_FROM_AIRALARM = "radio_airalarm_rcvr" //devices interested in recieving signals from air alarms
-var/const/RADIO_CHAT = "radio_telecoms"
-var/const/RADIO_ATMOSIA = "radio_atmos"
-var/const/RADIO_NAVBEACONS = "radio_navbeacon"
-var/const/RADIO_AIRLOCK = "radio_airlock"
-var/const/RADIO_SECBOT = "radio_secbot"
-var/const/RADIO_MULEBOT = "radio_mulebot"
-var/const/RADIO_MAGNETS = "radio_magnet"
-
-var/global/datum/controller/radio/radio_controller
-
-/hook/startup/proc/createRadioController()
- radio_controller = new /datum/controller/radio()
- return 1
-
-//callback used by objects to react to incoming radio signals
-/obj/proc/receive_signal(datum/signal/signal, receive_method, receive_param)
- return null
-
-//The global radio controller
-/datum/controller/radio
- var/list/datum/radio_frequency/frequencies = list()
-
-/datum/controller/radio/proc/add_object(obj/device as obj, var/new_frequency as num, var/radio_filter = null as text|null)
- var/f_text = num2text(new_frequency)
- var/datum/radio_frequency/frequency = frequencies[f_text]
-
- if(!frequency)
- frequency = new
- frequency.frequency = new_frequency
- frequencies[f_text] = frequency
-
- frequency.add_listener(device, radio_filter)
- return frequency
-
-/datum/controller/radio/proc/remove_object(obj/device, old_frequency)
- var/f_text = num2text(old_frequency)
- var/datum/radio_frequency/frequency = frequencies[f_text]
-
- if(frequency)
- frequency.remove_listener(device)
-
- if(frequency.devices.len == 0)
- qdel(frequency)
- frequencies -= f_text
-
- return 1
-
-/datum/controller/radio/proc/return_frequency(var/new_frequency as num)
- var/f_text = num2text(new_frequency)
- var/datum/radio_frequency/frequency = frequencies[f_text]
-
- if(!frequency)
- frequency = new
- frequency.frequency = new_frequency
- frequencies[f_text] = frequency
-
- return frequency
-
-/datum/radio_frequency
- var/frequency as num
- var/list/list/obj/devices = list()
-
-/datum/radio_frequency/proc/post_signal(obj/source as obj|null, datum/signal/signal, var/radio_filter = null as text|null, var/range = null as num|null)
- var/turf/start_point
- if(range)
- start_point = get_turf(source)
- if(!start_point)
- qdel(signal)
- return 0
- if (radio_filter)
- send_to_filter(source, signal, radio_filter, start_point, range)
- send_to_filter(source, signal, RADIO_DEFAULT, start_point, range)
- else
- //Broadcast the signal to everyone!
- for (var/next_filter in devices)
- send_to_filter(source, signal, next_filter, start_point, range)
-
-//Sends a signal to all machines belonging to a given filter. Should be called by post_signal()
-/datum/radio_frequency/proc/send_to_filter(obj/source, datum/signal/signal, var/radio_filter, var/turf/start_point = null, var/range = null)
- if (range && !start_point)
- return
-
- for(var/obj/device in devices[radio_filter])
- if(device == source)
- continue
- if(range)
- var/turf/end_point = get_turf(device)
- if(!end_point)
- continue
- if(start_point.z!=end_point.z || get_dist(start_point, end_point) > range)
- continue
-
- device.receive_signal(signal, TRANSMISSION_RADIO, frequency)
-
-/datum/radio_frequency/proc/add_listener(obj/device as obj, var/radio_filter as text|null)
- if (!radio_filter)
- radio_filter = RADIO_DEFAULT
- //log_admin("add_listener(device=[device],radio_filter=[radio_filter]) frequency=[frequency]")
- var/list/obj/devices_line = devices[radio_filter]
- if (!devices_line)
- devices_line = new
- devices[radio_filter] = devices_line
- devices_line+=device
-// var/list/obj/devices_line___ = devices[filter_str]
-// var/l = devices_line___.len
- //log_admin("DEBUG: devices_line.len=[devices_line.len]")
- //log_admin("DEBUG: devices(filter_str).len=[l]")
-
-/datum/radio_frequency/proc/remove_listener(obj/device)
- for (var/devices_filter in devices)
- var/list/devices_line = devices[devices_filter]
- devices_line-=device
- while (null in devices_line)
- devices_line -= null
- if (devices_line.len==0)
- devices -= devices_filter
-
-/datum/signal
- var/obj/source
-
- var/transmission_method = 0 //unused at the moment
- //0 = wire
- //1 = radio transmission
- //2 = subspace transmission
-
- var/list/data = list()
- var/encryption
-
- var/frequency = 0
-
-/datum/signal/proc/copy_from(datum/signal/model)
- source = model.source
- transmission_method = model.transmission_method
- data = model.data
- encryption = model.encryption
- frequency = model.frequency
-
-/datum/signal/proc/debug_print()
- if (source)
- . = "signal = {source = '[source]' ([source:x],[source:y],[source:z])\n"
- else
- . = "signal = {source = '[source]' ()\n"
- for (var/i in data)
- . += "data\[\"[i]\"\] = \"[data[i]]\"\n"
- if(islist(data[i]))
- var/list/L = data[i]
- for(var/t in L)
- . += "data\[\"[i]\"\] list has: [t]"
diff --git a/code/controllers/configuration/configuration.dm b/code/controllers/configuration/configuration.dm
index 1cba676a0c..a2f5b2c9d1 100644
--- a/code/controllers/configuration/configuration.dm
+++ b/code/controllers/configuration/configuration.dm
@@ -24,6 +24,9 @@
/// If the configuration is loaded
var/loaded = FALSE
+ /// If a reload is in progress
+ var/reload_in_progress = FALSE
+
/// A regex that matches words blocked IC
var/static/regex/ic_filter_regex
@@ -64,13 +67,23 @@
var/static/list/configuration_errors
/datum/controller/configuration/proc/admin_reload()
- if(IsAdminAdvancedProcCall())
+ if(IsAdminAdvancedProcCall() || !PreConfigReload())
return
log_admin("[key_name_admin(usr)] has forcefully reloaded the configuration from disk.")
message_admins("[key_name_admin(usr)] has forcefully reloaded the configuration from disk.")
full_wipe()
Load(world.params[OVERRIDE_CONFIG_DIRECTORY_PARAMETER])
+/datum/controller/configuration/proc/PreConfigReload()
+ if(reload_in_progress)
+ to_chat(usr, span_warning("Another user is already reloading the config!"))
+ return FALSE
+
+ reload_in_progress = TRUE
+ world.TgsTriggerEvent("tg-PreConfigReload", wait_for_completion = TRUE)
+ reload_in_progress = FALSE
+ return TRUE
+
/datum/controller/configuration/proc/Load(_directory)
if(IsAdminAdvancedProcCall()) //If admin proccall is detected down the line it will horribly break everything.
return
diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm
index 64e5058fdb..86f3c78790 100644
--- a/code/controllers/configuration/entries/game_options.dm
+++ b/code/controllers/configuration/entries/game_options.dm
@@ -34,6 +34,8 @@
/datum/config_entry/number/walk_speed
default = 0
+/datum/config_entry/flag/force_random_names
+
///Mob specific modifiers. NOTE: These will affect different mob types in different ways
/datum/config_entry/number/human_delay
default = 0
diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm
index ca15716dbe..7b9ae13596 100644
--- a/code/controllers/configuration/entries/general.dm
+++ b/code/controllers/configuration/entries/general.dm
@@ -1,6 +1,18 @@
/// server name (the name of the game window)
/datum/config_entry/string/servername
+/// Countdown between lobby and the round starting.
+/datum/config_entry/number/lobby_countdown
+ default = 120
+ integer = FALSE
+ min_val = 0
+
+/// Post round murder death kill countdown.
+/datum/config_entry/number/round_end_countdown
+ default = 25
+ integer = FALSE
+ min_val = 0
+
/// generate numeric suffix based on server port
/datum/config_entry/flag/server_suffix
@@ -222,9 +234,6 @@
// var/static/Tickcomp = 0 // FIXME: Unused
-/// use socket_talk to communicate with other processes
-/datum/config_entry/flag/socket_talk
-
// var/static/list/resource_urls = null // FIXME: Unused
/// Ghosts can turn on Antagovision to see a HUD of who is the bad guys this round.
@@ -787,6 +796,60 @@
/datum/config_entry/flag/discord_ahelps_all
default = FALSE
+/datum/config_entry/number/mc_tick_rate/base_mc_tick_rate
+ integer = FALSE
+ default = 1
+
+/datum/config_entry/number/mc_tick_rate/high_pop_mc_tick_rate
+ integer = FALSE
+ default = 1.1
+
+/datum/config_entry/number/mc_tick_rate/high_pop_mc_mode_amount
+ default = 65
+
+/datum/config_entry/number/mc_tick_rate/disable_high_pop_mc_mode_amount
+ default = 60
+
+/datum/config_entry/number/mc_tick_rate
+ abstract_type = /datum/config_entry/number/mc_tick_rate
+
+/datum/config_entry/number/mc_tick_rate/ValidateAndSet(str_val)
+ . = ..()
+ if (.)
+ Master.UpdateTickRate()
+
+/datum/config_entry/flag/resume_after_initializations
+
+/datum/config_entry/flag/resume_after_initializations/ValidateAndSet(str_val)
+ . = ..()
+ if(. && MC_RUNNING())
+ world.sleep_offline = !config_entry_value
+
/datum/config_entry/number/rounds_until_hard_restart
default = -1
min_val = 0
+
+/datum/config_entry/flag/auto_profile
+
+/datum/config_entry/number/profiler_interval
+ default = 300 SECONDS
+
+/datum/config_entry/number/drift_dump_threshold
+ default = 4 SECONDS
+
+/datum/config_entry/number/drift_profile_delay
+ default = 15 SECONDS
+
+/datum/config_entry/flag/forbid_all_profiling
+
+/datum/config_entry/flag/forbid_admin_profiling
+
+/datum/config_entry/flag/toast_notification_on_init
+
+/// If admins with +DEBUG can initialize byond-tracy midround.
+/datum/config_entry/flag/allow_tracy_start
+ protection = CONFIG_ENTRY_LOCKED
+
+/// If admins with +DEBUG can queue byond-tracy to run the next round.
+/datum/config_entry/flag/allow_tracy_queue
+ protection = CONFIG_ENTRY_LOCKED
diff --git a/code/controllers/emergency_shuttle_controller.dm b/code/controllers/emergency_shuttle_controller.dm
index 71e087d6a7..226557bf7e 100644
--- a/code/controllers/emergency_shuttle_controller.dm
+++ b/code/controllers/emergency_shuttle_controller.dm
@@ -161,8 +161,8 @@
/datum/emergency_shuttle_controller/proc/get_shuttle_prep_time()
// During mutiny rounds, the shuttle takes twice as long.
- if(ticker && ticker.mode)
- return SHUTTLE_PREPTIME * ticker.mode.shuttle_delay
+ if(SSticker && SSticker.mode)
+ return SHUTTLE_PREPTIME * SSticker.mode.shuttle_delay
return SHUTTLE_PREPTIME
diff --git a/code/controllers/failsafe.dm b/code/controllers/failsafe.dm
index e92e30079b..f645733804 100644
--- a/code/controllers/failsafe.dm
+++ b/code/controllers/failsafe.dm
@@ -24,6 +24,9 @@ GLOBAL_REAL(Failsafe, /datum/controller/failsafe)
var/running = TRUE
/datum/controller/failsafe/New()
+ // Ensure usr is null, to prevent any potential weirdness resulting from the failsafe having a usr if it's manually restarted.
+ usr = null
+
// Highlander-style: there can only be one! Kill off the old and replace it with the new.
if(Failsafe != src)
if(istype(Failsafe))
@@ -149,7 +152,7 @@ GLOBAL_REAL(Failsafe, /datum/controller/failsafe)
/proc/recover_all_SS_and_recreate_master()
del(Master)
var/list/subsytem_types = subtypesof(/datum/controller/subsystem)
- sortTim(subsytem_types, GLOBAL_PROC_REF(cmp_subsystem_init))
+ sortTim(subsytem_types, GLOBAL_PROC_REF(cmp_subsystem_init_stage))
for(var/I in subsytem_types)
new I
. = Recreate_MC()
diff --git a/code/controllers/globals.dm b/code/controllers/globals.dm
index bf9c7e1e9c..b809fb7bd5 100644
--- a/code/controllers/globals.dm
+++ b/code/controllers/globals.dm
@@ -1,9 +1,10 @@
+// See initialization order in /code/game/world.dm
GLOBAL_REAL(GLOB, /datum/controller/global_vars)
/datum/controller/global_vars
name = "Global Variables"
- var/list/gvars_datum_protected_varlist
+ var/static/list/gvars_datum_protected_varlist
var/list/gvars_datum_in_built_vars
var/list/gvars_datum_init_order
@@ -13,30 +14,24 @@ GLOBAL_REAL(GLOB, /datum/controller/global_vars)
GLOB = src
var/datum/controller/exclude_these = new
- gvars_datum_in_built_vars = exclude_these.vars + list("gvars_datum_protected_varlist", "gvars_datum_in_built_vars", "gvars_datum_init_order")
+ // I know this is dumb but the nested vars list hangs a ref to the datum. This fixes that
+ // I have an issue report open, lummox has not responded. It might be a FeaTuRE
+ // Sooo we gotta be dumb
+ var/list/controller_vars = exclude_these.vars.Copy()
+ controller_vars["vars"] = null
+ gvars_datum_in_built_vars = controller_vars + list(NAMEOF(src, gvars_datum_protected_varlist), NAMEOF(src, gvars_datum_in_built_vars), NAMEOF(src, gvars_datum_init_order))
- log_world("[vars.len - gvars_datum_in_built_vars.len] global variables")
+ QDEL_IN(exclude_these, 0) //signal logging isn't ready
- Initialize(exclude_these)
+ Initialize()
/datum/controller/global_vars/Destroy(force)
- stack_trace("There was an attempt to qdel the global vars holder!")
- if(!force)
- return QDEL_HINT_LETMELIVE
-
- QDEL_NULL(statclick)
- gvars_datum_protected_varlist.Cut()
- gvars_datum_in_built_vars.Cut()
-
- GLOB = null
-
- return ..()
+ // This is done to prevent an exploit where admins can get around protected vars
+ SHOULD_CALL_PARENT(FALSE)
+ return QDEL_HINT_IWILLGC
/datum/controller/global_vars/stat_entry(msg)
- if(!statclick)
- statclick = new/obj/effect/statclick/debug(null, "Initializing...", src)
-
- msg = "GLOB: [statclick.update("Edit")]"
+ msg = "Edit"
return msg
/datum/controller/global_vars/vv_edit_var(var_name, var_value)
@@ -44,6 +39,14 @@ GLOBAL_REAL(GLOB, /datum/controller/global_vars)
return FALSE
return ..()
+/*
+/datum/controller/global_vars/vv_get_var(var_name)
+ switch(var_name)
+ if (NAMEOF(src, vars))
+ return debug_variable(var_name, list(), 0, src)
+ return debug_variable(var_name, vars[var_name], 0, src, display_flags = VV_ALWAYS_CONTRACT_LIST)
+*/
+
/datum/controller/global_vars/Initialize()
gvars_datum_init_order = list()
gvars_datum_protected_varlist = list(NAMEOF(src, gvars_datum_protected_varlist) = TRUE)
@@ -55,8 +58,8 @@ GLOBAL_REAL(GLOB, /datum/controller/global_vars)
var/list/expected_global_procs = vars - gvars_datum_in_built_vars
for(var/I in global_procs)
expected_global_procs -= replacetext("[I]", "InitGlobal", "")
- var/english_missing = expected_global_procs.Join(", ")
- log_world("Missing procs: [english_missing]")
+ log_world("Missing procs: [expected_global_procs.Join(", ")]")
+
for(var/I in global_procs)
var/start_tick = world.time
call(src, I)()
@@ -64,4 +67,5 @@ GLOBAL_REAL(GLOB, /datum/controller/global_vars)
if(end_tick - start_tick)
warning("Global [replacetext("[I]", "InitGlobal", "")] slept during initialization!")
+ // Someone make it so this call isn't necessary
populate_legacy_globals()
diff --git a/code/controllers/master.dm b/code/controllers/master.dm
index 6a562eb319..8730d0c27f 100644
--- a/code/controllers/master.dm
+++ b/code/controllers/master.dm
@@ -8,7 +8,7 @@
**/
// See initialization order in /code/game/world.dm
-GLOBAL_REAL(Master, /datum/controller/master) = new
+GLOBAL_REAL(Master, /datum/controller/master)
/datum/controller/master
name = "Master"
@@ -25,6 +25,9 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
/// List of subsystems to process().
var/list/subsystems
+ ///Most recent init stage to complete init.
+ var/static/init_stage_completed
+
// Vars for keeping track of tick drift.
var/init_timeofday
var/init_time
@@ -41,9 +44,9 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
/// makes the mc main loop runtime
var/make_runtime = FALSE
- var/initializations_finished_with_no_players_logged_in //I wonder what this could be?
+ var/initializations_finished_with_no_players_logged_in //I wonder what this could be?
- // The type of the last subsystem to be process()'d.
+ /// The type of the last subsystem to be fire()'d.
var/last_type_processed
var/datum/controller/subsystem/queue_head //!Start of queue linked list
@@ -53,11 +56,16 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/map_loading = FALSE //!Are we loading in a new map?
var/current_runlevel //!for scheduling different subsystems for different stages of the round
+ var/sleep_offline_after_initializations = TRUE
/// During initialization, will be the instanced subsytem that is currently initializing.
/// Outside of initialization, returns null.
var/current_initializing_subsystem = null
+ /// The last decisecond we force dumped profiling information
+ /// Used to avoid spamming profile reads since they can be expensive (string memes)
+ var/last_profiled = 0
+
var/static/restart_clear = 0
var/static/restart_timeout = 0
var/static/restart_count = 0
@@ -66,13 +74,25 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
///current tick limit, assigned before running a subsystem.
///used by CHECK_TICK as well so that the procs subsystems call can obey that SS's tick limits
- var/static/current_ticklimit
+ var/static/current_ticklimit = TICK_LIMIT_RUNNING
+
+ /// Whether the Overview UI will update as fast as possible for viewers.
+ var/overview_fast_update = FALSE
+ /// Enables rolling usage averaging
+ var/use_rolling_usage = FALSE
+ /// How long to run our rolling usage averaging
+ var/rolling_usage_length = 5 SECONDS
/datum/controller/master/New()
+ // Ensure usr is null, to prevent any potential weirdness resulting from the MC having a usr if it's manually restarted.
+ usr = null
+
+ if(!config)
+ config = new
// Highlander-style: there can only be one! Kill off the old and replace it with the new.
if(!random_seed)
- #ifdef UNIT_TEST
+ #ifdef UNIT_TESTS
random_seed = 29051994 // How about 22475?
#else
random_seed = rand(1, 1e9)
@@ -82,15 +102,29 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/list/_subsystems = list()
subsystems = _subsystems
if (Master != src)
- if (istype(Master))
+ if (istype(Master)) //If there is an existing MC take over his stuff and delete it
Recover()
qdel(Master)
+ Master = src
else
- var/list/subsytem_types = subtypesof(/datum/controller/subsystem)
- sortTim(subsytem_types, GLOBAL_PROC_REF(cmp_subsystem_init))
- for(var/I in subsytem_types)
- _subsystems += new I
- Master = src
+ //Code used for first master on game boot or if existing master got deleted
+ Master = src
+ var/list/subsystem_types = subtypesof(/datum/controller/subsystem)
+ sortTim(subsystem_types, GLOBAL_PROC_REF(cmp_subsystem_init_stage))
+
+ //Find any abandoned subsystem from the previous master (if there was any)
+ var/list/existing_subsystems = list()
+ for(var/global_var in global.vars)
+ if (istype(global.vars[global_var], /datum/controller/subsystem))
+ existing_subsystems += global.vars[global_var]
+
+ //Either init a new SS or if an existing one was found use that
+ for(var/I in subsystem_types)
+ var/ss_idx = existing_subsystems.Find(I)
+ if (ss_idx)
+ _subsystems += existing_subsystems[ss_idx]
+ else
+ _subsystems += new I
if(!GLOB)
new /datum/controller/global_vars
@@ -111,8 +145,109 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
ss.Shutdown()
log_world("Shutdown complete")
+ADMIN_VERB(cmd_controller_view_ui, R_SERVER|R_DEBUG, "Controller Overview", "View the current states of the Subsystem Controllers.", ADMIN_CATEGORY_DEBUG)
+ Master.tgui_interact(user.mob)
+
+/datum/controller/master/tgui_status(mob/user, datum/tgui_state/state)
+ if(!user.client?.holder?.check_for_rights(R_SERVER|R_DEBUG))
+ return STATUS_CLOSE
+ return STATUS_INTERACTIVE
+
+/datum/controller/master/tgui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(isnull(ui))
+ ui = new /datum/tgui(user, src, "ControllerOverview")
+ ui.open()
+ use_rolling_usage = TRUE
+
+/datum/controller/master/tgui_close(mob/user)
+ var/valid_found = FALSE
+ for(var/datum/tgui/open_ui as anything in open_tguis)
+ if(open_ui.user == user)
+ continue
+ valid_found = TRUE
+ if(!valid_found)
+ use_rolling_usage = FALSE
+ return ..()
+
+/datum/controller/master/tgui_data(mob/user)
+ var/list/data = list()
+
+ var/list/subsystem_data = list()
+ for(var/datum/controller/subsystem/subsystem as anything in subsystems)
+ var/list/rolling_usage = subsystem.rolling_usage
+ subsystem.prune_rolling_usage()
+
+ // Then we sum
+ var/sum = 0
+ for(var/i in 2 to length(rolling_usage) step 2)
+ sum += rolling_usage[i]
+ var/average = sum / DS2TICKS(rolling_usage_length)
+
+ subsystem_data += list(list(
+ "name" = subsystem.name,
+ "ref" = REF(subsystem),
+ "init_order" = subsystem.init_order,
+ "last_fire" = subsystem.last_fire,
+ "next_fire" = subsystem.next_fire,
+ "can_fire" = subsystem.can_fire,
+ "doesnt_fire" = !!(subsystem.flags & SS_NO_FIRE),
+ "cost_ms" = subsystem.cost,
+ "tick_usage" = subsystem.tick_usage,
+ "usage_per_tick" = average,
+ "tick_overrun" = subsystem.tick_overrun,
+ "initialized" = subsystem.initialized,
+ "initialization_failure_message" = subsystem.initialization_failure_message,
+ ))
+ data["subsystems"] = subsystem_data
+ data["world_time"] = world.time
+ data["map_cpu"] = world.map_cpu
+ data["fast_update"] = overview_fast_update
+ data["rolling_length"] = rolling_usage_length
+
+ return data
+
+/datum/controller/master/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state)
+ if(..())
+ return TRUE
+
+ switch(action)
+ if("toggle_fast_update")
+ overview_fast_update = !overview_fast_update
+ return TRUE
+
+ if("set_rolling_length")
+ var/length = text2num(params["rolling_length"])
+ if(!length || length < 0)
+ return
+ rolling_usage_length = length SECONDS
+ return TRUE
+
+ if("view_variables")
+ var/datum/controller/subsystem/subsystem = locate(params["ref"]) in subsystems
+ if(isnull(subsystem))
+ to_chat(ui.user, span_warning("Failed to locate subsystem."))
+ return
+ SSadmin_verbs.dynamic_invoke_verb(ui.user, /datum/admin_verb/debug_variables, subsystem)
+ return TRUE
+
+/datum/controller/master/proc/check_and_perform_fast_update()
+ PRIVATE_PROC(TRUE)
+ set waitfor = FALSE
+
+
+ if(!overview_fast_update)
+ return
+
+ var/static/already_updating = FALSE
+ if(already_updating)
+ return
+ already_updating = TRUE
+ SStgui.update_uis(src)
+ already_updating = FALSE
+
// Returns 1 if we created a new mc, 0 if we couldn't due to a recent restart,
-// -1 if we encountered a runtime trying to recreate it
+// -1 if we encountered a runtime trying to recreate it
/proc/Recreate_MC()
. = -1 //so if we runtime, things know we failed
if (world.time < Master.restart_timeout)
@@ -123,7 +258,8 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/delay = 50 * ++Master.restart_count
Master.restart_timeout = world.time + delay
Master.restart_clear = world.time + (delay * 2)
- Master.processing = FALSE //stop ticking this one
+ if (Master) //Can only do this if master hasn't been deleted
+ Master.processing = FALSE //stop ticking this one
try
new/datum/controller/master()
catch
@@ -132,18 +268,23 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
/datum/controller/master/Recover()
- var/msg = "## DEBUG: [time2text(world.timeofday)] MC restarted. Reports:\n"
- for (var/varname in Master.vars)
- switch (varname)
- if("name", "tag", "bestF", "type", "parent_type", "vars", "statclick") // Built-in junk.
- continue
- else
- var/varval = Master.vars[varname]
- if (istype(varval, /datum)) // Check if it has a type var.
- var/datum/D = varval
- msg += "\t [varname] = [D]([D.type])\n"
- else
- msg += "\t [varname] = [varval]\n"
+ var/msg = "## DEBUG: [time2text(world.timeofday, "DDD MMM DD hh:mm:ss YYYY", TIMEZONE_UTC)] MC restarted. Reports:\n"
+ var/list/master_attributes = Master.vars
+ var/list/filtered_variables = list(
+ NAMEOF(src, name),
+ NAMEOF(src, parent_type),
+ NAMEOF(src, statclick),
+ NAMEOF(src, tag),
+ NAMEOF(src, type),
+ NAMEOF(src, vars),
+ )
+ for (var/varname in master_attributes - filtered_variables)
+ var/varval = master_attributes[varname]
+ if (isdatum(varval)) // Check if it has a type var.
+ var/datum/D = varval
+ msg += "\t [varname] = [D]([D.type])\n"
+ else
+ msg += "\t [varname] = [varval]\n"
log_world(msg)
var/datum/controller/subsystem/BadBoy = Master.last_type_processed
@@ -153,88 +294,183 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
LAZYINITLIST(BadBoy.failure_strikes)
switch(++BadBoy.failure_strikes[BadBoy.type])
if(2)
- msg = "MC Notice: The [BadBoy.name] subsystem was the last to fire for 2 controller restarts. It will be recovered now and disabled if it happens again."
+ msg = "The [BadBoy.name] subsystem was the last to fire for 2 controller restarts. It will be recovered now and disabled if it happens again."
FireHim = TRUE
- BadBoy.fail()
if(3)
- msg = "MC Notice: The [BadBoy.name] subsystem seems to be destabilizing the MC and will be offlined."
+ msg = "The [BadBoy.name] subsystem seems to be destabilizing the MC and will be put offline."
BadBoy.flags |= SS_NO_FIRE
- BadBoy.critfail()
if(msg)
- log_game(msg)
- message_admins(span_boldannounce("[msg]"))
+ to_chat(GLOB.admins, span_boldannounce("[msg]"))
log_world(msg)
if (istype(Master.subsystems))
if(FireHim)
- Master.subsystems += new BadBoy.type //NEW_SS_GLOBAL will remove the old one
-
+ Master.subsystems += new BadBoy.type //NEW_SS_GLOBAL will remove the old one
subsystems = Master.subsystems
current_runlevel = Master.current_runlevel
StartProcessing(10)
else
- to_world(span_boldannounce("The Master Controller is having some issues, we will need to re-initialize EVERYTHING"))
- Initialize(20, TRUE)
-
+ to_chat(world, span_boldannounce("The Master Controller is having some issues, we will need to re-initialize EVERYTHING"))
+ Initialize(20, TRUE, FALSE)
// Please don't stuff random bullshit here,
-// Make a subsystem, give it the SS_NO_FIRE flag, and do your work in it's Initialize()
+// Make a subsystem, give it the SS_NO_FIRE flag, and do your work in its Initialize()
/datum/controller/master/Initialize(delay, init_sss, tgs_prime)
set waitfor = 0
if(delay)
sleep(delay)
- if(tgs_prime)
- world.TgsInitializationComplete()
-
if(init_sss)
init_subtypes(/datum/controller/subsystem, subsystems)
- to_chat(world, span_boldannounce("MC: Initializing subsystems..."))
+ init_stage_completed = 0
+ var/mc_started = FALSE
- // Sort subsystems by init_order, so they initialize in the correct order.
- sortTim(subsystems, GLOBAL_PROC_REF(cmp_subsystem_init))
+ to_chat(world, span_boldannounce("Initializing subsystems..."), MESSAGE_TYPE_DEBUG)
+ var/list/stage_sorted_subsystems = new(INITSTAGE_MAX)
+ for (var/i in 1 to INITSTAGE_MAX)
+ stage_sorted_subsystems[i] = list()
+
+ var/list/type_to_subsystem = list()
+ for(var/datum/controller/subsystem/subsystem as anything in subsystems)
+ type_to_subsystem[subsystem.type] = subsystem
+
+ // Allows subsystems to declare other subsystems that must initialize after them.
+ for(var/datum/controller/subsystem/subsystem as anything in subsystems)
+ for(var/dependent_type as anything in subsystem.dependents)
+ if(!ispath(dependent_type, /datum/controller/subsystem))
+ stack_trace("ERROR: MC: subsystem `[subsystem.type]` has an invalid dependent: `[dependent_type]`. Skipping")
+ continue
+ var/datum/controller/subsystem/dependent = type_to_subsystem[dependent_type]
+ dependent.dependencies |= subsystem.type
+ subsystem.dependents = list()
+
+ // Constructs a reverse-dependency graph.
+ for(var/datum/controller/subsystem/subsystem as anything in subsystems)
+ for(var/dependency_type as anything in subsystem.dependencies)
+ if(!ispath(dependency_type, /datum/controller/subsystem))
+ stack_trace("ERROR: MC: subsystem `[subsystem.type]` has an invalid dependency: `[dependency_type]`. Skipping")
+ continue
+ var/datum/controller/subsystem/dependency = type_to_subsystem[dependency_type]
+ // Not a foolproof failsafe, likely to only prevent any immediate issues if this is only triggered once.
+ if(subsystem.init_stage < dependency.init_stage)
+ stack_trace("ERROR: MC: subsystem `[subsystem.type]` has an init_stage before one of its dependencies (Dependency: `[dependency.type]`, [subsystem.init_stage] < [dependency.init_stage])! Setting init_stage to [dependency.init_stage]")
+ subsystem.init_stage = dependency.init_stage
+ dependency.dependents += subsystem
+
+ // Topological sorting algorithm
+ var/list/counts = new(subsystems.len)
+ var/list/unsorted_subsystems = list()
+ var/index = 1
+ for(var/datum/controller/subsystem/subsystem as anything in subsystems)
+ counts[index] = length(subsystem.dependencies)
+ subsystem.ordering_id = index
+ if(counts[index] == 0)
+ unsorted_subsystems += subsystem
+ index += 1
+
+ var/list/sorted_subsystems = list()
+ while(length(unsorted_subsystems) > 0)
+ var/datum/controller/subsystem/sub = unsorted_subsystems[unsorted_subsystems.len]
+ unsorted_subsystems.len--
+ sorted_subsystems += sub
+ for(var/datum/controller/subsystem/dependent as anything in sub.dependents)
+ counts[dependent.ordering_id] -= 1
+ if(counts[dependent.ordering_id] == 0)
+ unsorted_subsystems += dependent
+ // Topological sorting algorithm end
+
+ if(length(subsystems) != length(sorted_subsystems))
+ var/list/circular_dependency = subsystems.Copy() - sorted_subsystems
+ var/list/debug_msg = list()
+ var/list/usr_msg = list()
+ for(var/datum/controller/subsystem/subsystem as anything in circular_dependency)
+ usr_msg += "[subsystem.name]"
+
+ var/list/datum/controller/subsystem/nodes = list(circular_dependency[1])
+ var/list/loop = list()
+ while(length(nodes) > 0)
+ var/datum/controller/subsystem/node = nodes[nodes.len]
+ nodes.len--
+ if(node in loop)
+ loop += node
+ break
+ loop += node
+ for(var/datum/controller/subsystem/connected as anything in node.dependencies)
+ nodes += type_to_subsystem[connected]
+
+ var/loop_position = 0
+ for(var/datum/controller/subsystem/node in loop)
+ if(node == loop[loop.len])
+ break
+ loop_position++
+ if(loop_position != 0)
+ loop.Cut(1, loop_position + 1)
+
+ for(var/datum/controller/subsystem/subsystem as anything in loop)
+ debug_msg += "[subsystem.name]"
+
+ // Can't initialize them if they have circular dependencies, there's no real failsafe here.
+ stack_trace("ERROR: CRITICAL: MC: The following subsystems have circular dependencies: [jointext(debug_msg, " -> ")]")
+ to_chat(world, span_bolddanger("CRITICAL: Failed to initialize [jointext(usr_msg, ", ")]"), MESSAGE_TYPE_DEBUG)
+
+ for (var/datum/controller/subsystem/subsystem as anything in sorted_subsystems)
+ var/subsystem_init_stage = subsystem.init_stage
+ if (!isnum(subsystem_init_stage) || subsystem_init_stage < 1 || subsystem_init_stage > INITSTAGE_MAX || round(subsystem_init_stage) != subsystem_init_stage)
+ stack_trace("ERROR: MC: subsystem `[subsystem.type]` has invalid init_stage: `[subsystem_init_stage]`. Setting to `[INITSTAGE_MAX]`")
+ subsystem_init_stage = subsystem.init_stage = INITSTAGE_MAX
+ stage_sorted_subsystems[subsystem_init_stage] += subsystem
+
+ // Sort subsystems by display setting for easy access.
+ var/evaluated_order = 1
+ sortTim(subsystems, GLOBAL_PROC_REF(cmp_subsystem_display))
var/start_timeofday = REALTIMEOFDAY
- // Initialize subsystems.
- current_ticklimit = CONFIG_GET(number/tick_limit_mc_init)
- for (var/datum/controller/subsystem/SS in subsystems)
- if (SS.flags & SS_NO_INIT)
- continue
- init_subsystem(SS)
- CHECK_TICK
- current_initializing_subsystem = null
- current_ticklimit = TICK_LIMIT_RUNNING
+ for (var/current_init_stage in 1 to INITSTAGE_MAX)
+
+ // Initialize subsystems.
+ for (var/datum/controller/subsystem/subsystem in stage_sorted_subsystems[current_init_stage])
+ subsystem.init_order = evaluated_order
+ evaluated_order++
+ init_subsystem(subsystem)
+
+ CHECK_TICK
+ current_initializing_subsystem = null
+ init_stage_completed = current_init_stage
+ if (!mc_started)
+ mc_started = TRUE
+ if (!current_runlevel)
+ SetRunLevel(1) // Intentionally not using the defines here because the MC doesn't care about them
+ // Loop.
+ Master.StartProcessing(0)
+
var/time = (REALTIMEOFDAY - start_timeofday) / 10
- var/msg = "MC: Initializations complete within [time] second[time == 1 ? "" : "s"]!"
- to_chat(world, span_boldannounce("[msg]"))
+
+
+ var/msg = "Initializations complete within [time] second[time == 1 ? "" : "s"]!"
+ to_chat(world, span_boldannounce("[msg]"), MESSAGE_TYPE_DEBUG)
log_world(msg)
- // FIXME: TGS <-> Discord communication; sending message to a TGS chat channel
- // send2chat("Server Initialization completed! - Took [time] second[time == 1 ? "" : "s"].", "bot announce")
+ if(world.system_type == MS_WINDOWS && CONFIG_GET(flag/toast_notification_on_init) && !length(GLOB.clients))
+ world.shelleo("start /min powershell -ExecutionPolicy Bypass -File tools/initToast/initToast.ps1 -name \"[world.name]\" -icon %CD%\\icons\\virgoicon_16.png -port [world.port]")
- if (!current_runlevel)
- SetRunLevel(RUNLEVEL_LOBBY)
-
- // GLOB.revdata = new // It can load revdata now, from tgs or .git or whatever
-
- // Sort subsystems by display setting for easy access.
- sortTim(subsystems, GLOBAL_PROC_REF(cmp_subsystem_display))
// Set world options.
- #ifdef UNIT_TEST
- world.sleep_offline = 0
- #else
- world.sleep_offline = 1
- #endif
world.change_fps(CONFIG_GET(number/fps))
var/initialized_tod = REALTIMEOFDAY
- sleep(1)
+
+ if(tgs_prime)
+ world.TgsInitializationComplete()
+
+ if(sleep_offline_after_initializations)
+ world.sleep_offline = TRUE
+ sleep(1 TICKS)
+
+ if(sleep_offline_after_initializations && CONFIG_GET(flag/resume_after_initializations))
+ world.sleep_offline = FALSE
initializations_finished_with_no_players_logged_in = initialized_tod < REALTIMEOFDAY - 10
- // Loop.
- Master.StartProcessing(0)
/**
* Initialize a given subsystem and handle the results.
@@ -248,13 +484,11 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
SS_INIT_NONE,
SS_INIT_SUCCESS,
SS_INIT_NO_NEED,
+ SS_INIT_NO_MESSAGE,
)
- if (subsystem.subsystem_initialized) //Don't init if they already are initialized
- return
-
- if (subsystem.flags & SS_NO_INIT)
- subsystem.subsystem_initialized = TRUE
+ if ((subsystem.flags & SS_NO_INIT) || subsystem.initialized) //Don't init SSs with the corresponding flag or if they already are initialized
+ subsystem.initialized = TRUE // set initialized to TRUE, because the value of initialized may still be checked on SS_NO_INIT subsystems as an "is this ready" check
return
current_initializing_subsystem = subsystem
@@ -267,7 +501,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/seconds = round(time / 1000, 0.01)
// Always update the blackbox tally regardless.
- // SSblackbox.record_feedback("tally", "subsystem_initialize", time, subsystem.name)
+ // NOT IMPLEMENTED: SSblackbox.record_feedback("tally", "subsystem_initialize", time, subsystem.name)
feedback_set_details("subsystem_initialize", "[time] [subsystem.name]")
// Gave invalid return value.
@@ -280,12 +514,11 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
if(result != SS_INIT_FAILURE)
// Some form of success, implicit failure, or the SS in unused.
- subsystem.subsystem_initialized = TRUE
-
+ subsystem.initialized = TRUE
SEND_SIGNAL(subsystem, COMSIG_SUBSYSTEM_POST_INITIALIZE)
else
// The subsystem officially reports that it failed to init and wishes to be treated as such.
- subsystem.subsystem_initialized = FALSE
+ subsystem.initialized = FALSE
subsystem.can_fire = FALSE
// The rest of this proc is printing the world log and chat message.
@@ -298,7 +531,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
if(SS_INIT_FAILURE)
message_prefix = "Failed to initialize [subsystem.name] subsystem after"
chat_warning = TRUE
- if(SS_INIT_SUCCESS)
+ if(SS_INIT_SUCCESS, SS_INIT_NO_MESSAGE)
message_prefix = "Initialized [subsystem.name] subsystem within"
if(SS_INIT_NO_NEED)
// This SS is disabled or is otherwise shy.
@@ -311,14 +544,17 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/message = "[message_prefix] [seconds] second[seconds == 1 ? "" : "s"]!"
var/chat_message = chat_warning ? span_boldwarning(message) : span_boldannounce(message)
- to_chat(world, chat_message)
+ if(result != SS_INIT_NO_MESSAGE)
+ to_chat(world, chat_message, MESSAGE_TYPE_DEBUG)
log_world(message)
/datum/controller/master/proc/SetRunLevel(new_runlevel)
- var/old_runlevel = isnull(current_runlevel) ? "NULL" : GLOB.runlevel_flags[current_runlevel]
- testing("MC: Runlevel changed from [old_runlevel] to [new_runlevel]")
- current_runlevel = RUNLEVEL_FLAG_TO_INDEX(new_runlevel)
+ var/old_runlevel = current_runlevel
+
+ testing("MC: Runlevel changed from [isnull(old_runlevel) ? "NULL" : old_runlevel] to [new_runlevel]")
+ current_runlevel = log(2, new_runlevel) + 1
if(current_runlevel < 1)
+ current_runlevel = old_runlevel
CRASH("Attempted to set invalid runlevel: [new_runlevel]")
// Starts the mc, and sticks around to restart it if the loop ever ends.
@@ -326,49 +562,56 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
set waitfor = 0
if(delay)
sleep(delay)
- var/rtn = Loop()
- if (rtn > 0 || processing < 0)
+ testing("Master starting processing")
+ var/started_stage
+ var/rtn = -2
+ do
+ started_stage = init_stage_completed
+ rtn = Loop(started_stage)
+ while (rtn == MC_LOOP_RTN_NEWSTAGES && processing > 0 && started_stage < init_stage_completed)
+
+ if (rtn >= MC_LOOP_RTN_GRACEFUL_EXIT || processing < 0)
return //this was suppose to happen.
//loop ended, restart the mc
- log_and_message_admins("MC Notice: MC crashed or runtimed, self-restarting (\ref[src])")
+ log_game("MC crashed or runtimed, restarting")
+ message_admins("MC crashed or runtimed, restarting")
var/rtn2 = Recreate_MC()
- switch(rtn2)
- if(-1)
- log_and_message_admins("MC Warning: Failed to self-recreate MC (Return code: [rtn2]), it's up to the failsafe now (\ref[src])")
- Failsafe.defcon = 2
- if(0)
- log_and_message_admins("MC Warning: Too soon for MC self-restart (Return code: [rtn2]), going to let failsafe handle it (\ref[src])")
- Failsafe.defcon = 2
- if(1)
- log_and_message_admins("MC Notice: MC self-recreated, old MC departing (Return code: [rtn2]) (\ref[src])")
+ if (rtn2 <= 0)
+ log_game("Failed to recreate MC (Error code: [rtn2]), it's up to the failsafe now")
+ message_admins("Failed to recreate MC (Error code: [rtn2]), it's up to the failsafe now")
+ Failsafe.defcon = 2
// Main loop.
-/datum/controller/master/proc/Loop()
+/datum/controller/master/proc/Loop(init_stage)
. = -1
//Prep the loop (most of this is because we want MC restarts to reset as much state as we can, and because
- // local vars rock
+ // local vars rock
//all this shit is here so that flag edits can be refreshed by restarting the MC. (and for speed)
var/list/tickersubsystems = list()
- var/list/runlevel_sorted_subsystems = list(list()) //ensure we always have at least one runlevel
+ var/list/runlevel_sorted_subsystems = list(list()) //ensure we always have at least one runlevel
var/timer = world.time
- for (var/datum/controller/subsystem/SS as anything in subsystems)
+ for (var/thing in subsystems)
+ var/datum/controller/subsystem/SS = thing
if (SS.flags & SS_NO_FIRE)
continue
+ if (SS.init_stage > init_stage)
+ continue
SS.queued_time = 0
SS.queue_next = null
SS.queue_prev = null
SS.state = SS_IDLE
- if (SS.flags & SS_TICKER)
+ if ((SS.flags & (SS_TICKER|SS_BACKGROUND)) == SS_TICKER)
tickersubsystems += SS
- timer += world.tick_lag * rand(0,1)
+ // Timer subsystems aren't allowed to bunch up, so we offset them a bit
+ timer += world.tick_lag * rand(0, 1)
SS.next_fire = timer
continue
var/ss_runlevels = SS.runlevels
var/added_to_any = FALSE
- for(var/I in 1 to GLOB.runlevel_flags.len)
- if(ss_runlevels & GLOB.runlevel_flags[I])
+ for(var/I in 1 to GLOB.bitflags.len)
+ if(ss_runlevels & GLOB.bitflags[I])
while(runlevel_sorted_subsystems.len < I)
runlevel_sorted_subsystems += list(list())
runlevel_sorted_subsystems[I] += SS
@@ -402,30 +645,41 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
canary.use_variable()
//the actual loop.
while (1)
- tickdrift = max(0, MC_AVERAGE_FAST(tickdrift, (((REALTIMEOFDAY - init_timeofday) - (world.time - init_time)) / world.tick_lag)))
+ var/newdrift = ((REALTIMEOFDAY - init_timeofday) - (world.time - init_time)) / world.tick_lag
+ tickdrift = max(0, MC_AVERAGE_FAST(tickdrift, newdrift))
var/starting_tick_usage = TICK_USAGE
+
+ if(newdrift - olddrift >= CONFIG_GET(number/drift_dump_threshold))
+ AttemptProfileDump(CONFIG_GET(number/drift_profile_delay))
+ olddrift = newdrift
+
+ if (init_stage != init_stage_completed)
+ return MC_LOOP_RTN_NEWSTAGES
if (processing <= 0)
current_ticklimit = TICK_LIMIT_RUNNING
- sleep(10)
+ sleep(1 SECONDS)
continue
//Anti-tick-contention heuristics:
- //if there are mutiple sleeping procs running before us hogging the cpu, we have to run later.
- // (because sleeps are processed in the order received, longer sleeps are more likely to run first)
- if (starting_tick_usage > TICK_LIMIT_MC) //if there isn't enough time to bother doing anything this tick, sleep a bit.
- sleep_delta *= 2
- current_ticklimit = TICK_LIMIT_RUNNING * 0.5
- sleep(world.tick_lag * (processing * sleep_delta))
- continue
+ if (init_stage == INITSTAGE_MAX)
+ //if there are multiple sleeping procs running before us hogging the cpu, we have to run later.
+ // (because sleeps are processed in the order received, longer sleeps are more likely to run first)
+ if (starting_tick_usage > TICK_LIMIT_MC) //if there isn't enough time to bother doing anything this tick, sleep a bit.
+ sleep_delta *= 2
+ current_ticklimit = TICK_LIMIT_RUNNING * 0.5
+ sleep(world.tick_lag * (processing * sleep_delta))
+ continue
- //Byond resumed us late. assume it might have to do the same next tick
- if (last_run + CEILING(world.tick_lag * (processing * sleep_delta), world.tick_lag) < world.time)
- sleep_delta += 1
+ //Byond resumed us late. assume it might have to do the same next tick
+ if (last_run + CEILING(world.tick_lag * (processing * sleep_delta), world.tick_lag) < world.time)
+ sleep_delta += 1
- sleep_delta = MC_AVERAGE_FAST(sleep_delta, 1) //decay sleep_delta
+ sleep_delta = MC_AVERAGE_FAST(sleep_delta, 1) //decay sleep_delta
- if (starting_tick_usage > (TICK_LIMIT_MC*0.75)) //we ran 3/4 of the way into the tick
- sleep_delta += 1
+ if (starting_tick_usage > (TICK_LIMIT_MC*0.75)) //we ran 3/4 of the way into the tick
+ sleep_delta += 1
+ else
+ sleep_delta = 1
//debug
if (make_runtime)
@@ -436,7 +690,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
new/datum/controller/failsafe() // (re)Start the failsafe.
//now do the actual stuff
- if (!queue_head || !(iteration % 3))
+ if (!skip_ticks)
var/checking_runlevel = current_runlevel
if(cached_runlevel != checking_runlevel)
//resechedule subsystems
@@ -455,46 +709,73 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
else
subsystems_to_check = tickersubsystems
- if (CheckQueue(subsystems_to_check) <= 0)
- log_world("MC: CheckQueue(subsystems_to_check) exited uncleanly, SoftReset (error_level=[error_level]")
+ if (CheckQueue(subsystems_to_check) <= 0) //error processing queue
+ stack_trace("MC: CheckQueue failed. Current error_level is [round(error_level, 0.25)]")
if (!SoftReset(tickersubsystems, runlevel_sorted_subsystems))
- log_world("MC: SoftReset() failed, crashing")
- return
- if (!error_level)
+ error_level++
+ CRASH("MC: SoftReset() failed, exiting loop()")
+
+ if (error_level < 2) //except for the first strike, stop incrmenting our iteration so failsafe enters defcon
iteration++
- error_level++
+ else
+ cached_runlevel = null //3 strikes, Lets reset the runlevel lists
current_ticklimit = TICK_LIMIT_RUNNING
- sleep(10)
+ sleep((1 SECONDS) * error_level)
+ error_level++
continue
if (queue_head)
- if (RunQueue() <= 0)
- log_world("MC: RunQueue() exited uncleanly, running SoftReset (error_level=[error_level]")
- if (!SoftReset(tickersubsystems, runlevel_sorted_subsystems))
- log_world("MC: SoftReset() failed, crashing")
- return
- if (!error_level)
- iteration++
+ if (RunQueue() <= 0) //error running queue
+ stack_trace("MC: RunQueue failed. Current error_level is [round(error_level, 0.25)]")
+ if (error_level > 1) //skip the first error,
+ if (!SoftReset(tickersubsystems, runlevel_sorted_subsystems))
+ error_level++
+ CRASH("MC: SoftReset() failed, exiting loop()")
+
+ if (error_level <= 2) //after 3 strikes stop incrmenting our iteration so failsafe enters defcon
+ iteration++
+ else
+ cached_runlevel = null //3 strikes, Lets also reset the runlevel lists
+ current_ticklimit = TICK_LIMIT_RUNNING
+ sleep((1 SECONDS) * error_level)
+ error_level++
+ continue
error_level++
- current_ticklimit = TICK_LIMIT_RUNNING
- sleep(10)
- continue
- error_level--
+ if (error_level > 0)
+ error_level = max(MC_AVERAGE_SLOW(error_level-1, error_level), 0)
if (!queue_head) //reset the counts if the queue is empty, in the off chance they get out of sync
queue_priority_count = 0
queue_priority_count_bg = 0
iteration++
last_run = world.time
+ if (skip_ticks)
+ skip_ticks--
src.sleep_delta = MC_AVERAGE_FAST(src.sleep_delta, sleep_delta)
- current_ticklimit = TICK_LIMIT_RUNNING
- if (processing * sleep_delta <= world.tick_lag)
- current_ticklimit -= (TICK_LIMIT_RUNNING * 0.25) //reserve the tail 1/4 of the next tick for the mc if we plan on running next tick
+
+// Force any verbs into overtime, to test how they perfrom under load
+// For local ONLY
+#ifdef VERB_STRESS_TEST
+ /// Target enough tick usage to only allow time for our maptick estimate and verb processing, and nothing else
+ var/overtime_target = TICK_LIMIT_RUNNING
+// This will leave just enough cpu time for maptick, forcing verbs to run into overtime
+// Use this for testing the worst case scenario, when maptick is spiking and usage is otherwise completely consumed
+#ifdef FORCE_VERB_OVERTIME
+ overtime_target += TICK_BYOND_RESERVE
+#endif
+ CONSUME_UNTIL(overtime_target)
+#endif
+
+ if (init_stage != INITSTAGE_MAX)
+ current_ticklimit = TICK_LIMIT_RUNNING * 2
+ else
+ current_ticklimit = TICK_LIMIT_RUNNING
+ if (processing * sleep_delta <= world.tick_lag)
+ current_ticklimit -= (TICK_LIMIT_RUNNING * 0.25) //reserve the tail 1/4 of the next tick for the mc if we plan on running next tick
+
+ check_and_perform_fast_update()
sleep(world.tick_lag * (processing * sleep_delta))
-
-
-
// This is what decides if something should run.
/datum/controller/master/proc/CheckQueue(list/subsystemstocheck)
. = 0 //so the mc knows if we runtimed
@@ -519,11 +800,16 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
continue
if ((SS_flags & (SS_TICKER|SS_KEEP_TIMING)) == SS_KEEP_TIMING && SS.last_fire + (SS.wait * 0.75) > world.time)
continue
+ if (SS.postponed_fires >= 1)
+ SS.postponed_fires--
+ SS.update_nextfire()
+ continue
SS.enqueue()
. = 1
-// Run thru the queue of subsystems to run, running them while balancing out their allocated tick precentage
+/// RunQueue - Run thru the queue of subsystems to run, running them while balancing out their allocated tick precentage
+/// Returns 0 if runtimed, a negitive number for logic errors, and a positive number if the operation completed without errors
/datum/controller/master/proc/RunQueue()
. = 0
var/datum/controller/subsystem/queue_node
@@ -535,12 +821,11 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/tick_precentage
var/tick_remaining
var/ran = TRUE //this is right
- var/ran_non_ticker = FALSE
var/bg_calc //have we swtiched current_tick_budget to background mode yet?
var/tick_usage
//keep running while we have stuff to run and we haven't gone over a tick
- // this is so subsystems paused eariler can use tick time that later subsystems never used
+ // this is so subsystems paused eariler can use tick time that later subsystems never used
while (ran && queue_head && TICK_USAGE < TICK_LIMIT_MC)
ran = FALSE
bg_calc = FALSE
@@ -549,42 +834,42 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
while (queue_node)
if (ran && TICK_USAGE > TICK_LIMIT_RUNNING)
break
-
queue_node_flags = queue_node.flags
queue_node_priority = queue_node.queued_priority
- //super special case, subsystems where we can't make them pause mid way through
- //if we can't run them this tick (without going over a tick)
- //we bump up their priority and attempt to run them next tick
- //(unless we haven't even ran anything this tick, since its unlikely they will ever be able run
- // in those cases, so we just let them run)
- if (queue_node_flags & SS_NO_TICK_CHECK)
- if (queue_node.tick_usage > TICK_LIMIT_RUNNING - TICK_USAGE && ran_non_ticker)
- queue_node.queued_priority += queue_priority_count * 0.1
- queue_priority_count -= queue_node_priority
- queue_priority_count += queue_node.queued_priority
- current_tick_budget -= queue_node_priority
- queue_node = queue_node.queue_next
- continue
+ if (!(queue_node_flags & SS_TICKER) && skip_ticks)
+ queue_node = queue_node.queue_next
+ continue
+
+ if ((queue_node_flags & SS_BACKGROUND))
+ if (!bg_calc)
+ current_tick_budget = queue_priority_count_bg
+ bg_calc = TRUE
+ else if (bg_calc)
+ //error state, do sane fallback behavior
+ if (. == 0)
+ log_world("MC: Queue logic failure, non-background subsystem queued to run after a background subsystem: [queue_node] queue_prev:[queue_node.queue_prev]")
+ . = -1
+ current_tick_budget = queue_priority_count //this won't even be right, but is the best we have.
+ bg_calc = FALSE
- if ((queue_node_flags & SS_BACKGROUND) && !bg_calc)
- current_tick_budget = queue_priority_count_bg
- bg_calc = TRUE
tick_remaining = TICK_LIMIT_RUNNING - TICK_USAGE
- if (current_tick_budget > 0 && queue_node_priority > 0)
- tick_precentage = tick_remaining / (current_tick_budget / queue_node_priority)
+ if (queue_node_priority >= 0 && current_tick_budget > 0 && current_tick_budget >= queue_node_priority)
+ //Give the subsystem a precentage of the remaining tick based on the remaining priority
+ tick_precentage = tick_remaining * (queue_node_priority / current_tick_budget)
else
- tick_precentage = tick_remaining
+ //error state
+ if (. == 0)
+ log_world("MC: tick_budget sync error. [json_encode(list(current_tick_budget, queue_priority_count, queue_priority_count_bg, bg_calc, queue_node, queue_node_priority))]")
+ . = -1
+ tick_precentage = tick_remaining //just because we lost track of priority calculations doesn't mean we can't try to finish off the run, if the error state persists, we don't want to stop ticks from happening
- // Reduce tick allocation for subsystems that overran on their last tick.
tick_precentage = max(tick_precentage*0.5, tick_precentage-queue_node.tick_overrun)
current_ticklimit = round(TICK_USAGE + tick_precentage)
- if (!(queue_node_flags & SS_TICKER))
- ran_non_ticker = TRUE
ran = TRUE
queue_node_paused = (queue_node.state == SS_PAUSED || queue_node.state == SS_PAUSING)
@@ -592,10 +877,22 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
queue_node.state = SS_RUNNING
+ if(queue_node.profiler_focused)
+ world.Profile(PROFILE_START)
+
tick_usage = TICK_USAGE
var/state = queue_node.ignite(queue_node_paused)
tick_usage = TICK_USAGE - tick_usage
+ if(use_rolling_usage)
+ queue_node.prune_rolling_usage()
+ // Rolling usage is an unrolled list that we know the order off
+ // OPTIMIZATION POSTING
+ queue_node.rolling_usage += list(DS2TICKS(world.time), tick_usage)
+
+ if(queue_node.profiler_focused)
+ world.Profile(PROFILE_STOP)
+
if (state == SS_RUNNING)
state = SS_IDLE
current_tick_budget -= queue_node_priority
@@ -621,7 +918,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
queue_node.paused_ticks = 0
queue_node.paused_tick_usage = 0
- if (queue_node_flags & SS_BACKGROUND) //update our running total
+ if (bg_calc) //update our running total
queue_priority_count_bg -= queue_node_priority
else
queue_priority_count -= queue_node_priority
@@ -629,14 +926,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
queue_node.last_fire = world.time
queue_node.times_fired++
- if (queue_node_flags & SS_TICKER)
- queue_node.next_fire = world.time + (world.tick_lag * queue_node.wait)
- else if (queue_node_flags & SS_POST_FIRE_TIMING)
- queue_node.next_fire = world.time + queue_node.wait + (world.tick_lag * (queue_node.tick_overrun/100))
- else if (queue_node_flags & SS_KEEP_TIMING)
- queue_node.next_fire += queue_node.wait
- else
- queue_node.next_fire = queue_node.queued_time + queue_node.wait + (world.tick_lag * (queue_node.tick_overrun/100))
+ queue_node.update_nextfire()
queue_node.queued_time = 0
@@ -645,21 +935,24 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
queue_node = queue_node.queue_next
- . = 1
+ if (. == 0)
+ . = 1
//resets the queue, and all subsystems, while filtering out the subsystem lists
-// called if any mc's queue procs runtime or exit improperly.
+// called if any mc's queue procs runtime or exit improperly.
/datum/controller/master/proc/SoftReset(list/ticker_SS, list/runlevel_SS)
. = 0
- log_world("MC: SoftReset called, resetting MC queue state.")
+ stack_trace("MC: SoftReset called, resetting MC queue state.")
+
if (!istype(subsystems) || !istype(ticker_SS) || !istype(runlevel_SS))
log_world("MC: SoftReset: Bad list contents: '[subsystems]' '[ticker_SS]' '[runlevel_SS]'")
return
- var/subsystemstocheck = subsystems + ticker_SS
+ var/subsystemstocheck = subsystems | ticker_SS
for(var/I in runlevel_SS)
subsystemstocheck |= I
- for (var/datum/controller/subsystem/SS as anything in subsystemstocheck)
+ for (var/thing in subsystemstocheck)
+ var/datum/controller/subsystem/SS = thing
if (!SS || !istype(SS))
//list(SS) is so if a list makes it in the subsystem list, we remove the list, not the contents
subsystems -= list(SS)
@@ -688,39 +981,51 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
log_world("MC: SoftReset: Finished.")
. = 1
+/// Warns us that the end of tick byond map_update will be laggier then normal, so that we can just skip running subsystems this tick.
+/datum/controller/master/proc/laggy_byond_map_update_incoming()
+ if (!skip_ticks)
+ skip_ticks = 1
/datum/controller/master/stat_entry(msg)
- if(!statclick)
- statclick = new/obj/effect/statclick/debug(null, "Initializing...", src)
-
- msg = "Byond: (FPS:[world.fps]) (TickCount:[world.time/world.tick_lag]) (TickDrift:[round(Master.tickdrift,1)]([round((Master.tickdrift/(world.time/world.tick_lag))*100,0.1)]%))"
- msg += "Master Controller: [statclick.update("(TickRate:[Master.processing]) (Iteration:[Master.iteration])")]"
-
+ msg = "(TickRate:[Master.processing]) (Iteration:[Master.iteration]) (TickLimit: [round(Master.current_ticklimit, 0.1)])"
return msg
-/datum/controller/master/StartLoadingMap(var/quiet = TRUE)
- if(map_loading)
- admin_notice(span_danger("Another map is attempting to be loaded before first map released lock. Delaying."), R_DEBUG)
- else if(!quiet)
- admin_notice(span_danger("Map is now being built. Locking."), R_DEBUG)
+/datum/controller/master/StartLoadingMap()
//disallow more than one map to load at once, multithreading it will just cause race conditions
while(map_loading)
stoplag()
- for(var/datum/controller/subsystem/SS as anything in subsystems)
+ for(var/S in subsystems)
+ var/datum/controller/subsystem/SS = S
SS.StartLoadingMap()
-
map_loading = TRUE
-/datum/controller/master/StopLoadingMap(var/quiet = TRUE)
- if(!quiet)
- admin_notice(span_danger("Map is finished. Unlocking."), R_DEBUG)
+/datum/controller/master/StopLoadingMap(bounds = null)
map_loading = FALSE
- for(var/datum/controller/subsystem/SS as anything in subsystems)
+ for(var/S in subsystems)
+ var/datum/controller/subsystem/SS = S
SS.StopLoadingMap()
+
+/datum/controller/master/proc/UpdateTickRate()
+ if (!processing)
+ return
+ var/client_count = length(GLOB.clients)
+ if (client_count < CONFIG_GET(number/mc_tick_rate/disable_high_pop_mc_mode_amount))
+ processing = CONFIG_GET(number/mc_tick_rate/base_mc_tick_rate)
+ else if (client_count > CONFIG_GET(number/mc_tick_rate/high_pop_mc_mode_amount))
+ processing = CONFIG_GET(number/mc_tick_rate/high_pop_mc_tick_rate)
+
/datum/controller/master/proc/OnConfigLoad()
for (var/thing in subsystems)
var/datum/controller/subsystem/SS = thing
SS.OnConfigLoad()
+
+/// Attempts to dump our current profile info into a file, triggered if the MC thinks shit is going down
+/// Accepts a delay in deciseconds of how long ago our last dump can be, this saves causing performance problems ourselves
+/datum/controller/master/proc/AttemptProfileDump(delay)
+ if(REALTIMEOFDAY - last_profiled <= delay)
+ return FALSE
+ last_profiled = REALTIMEOFDAY
+ SSprofiler.DumpFile(allow_yield = FALSE)
diff --git a/code/controllers/master_controller.dm b/code/controllers/master_controller.dm
index 4f8b17d4cf..6f157b7bcf 100644
--- a/code/controllers/master_controller.dm
+++ b/code/controllers/master_controller.dm
@@ -42,7 +42,7 @@ var/global/last_tick_duration = 0
transfer_controller = new
admin_notice(span_danger("Initializations complete."), R_DEBUG)
-// #if UNIT_TEST
+// #if UNIT_TESTS
// # define CHECK_SLEEP_MASTER // For unit tests we don't care about a smooth lobby screen experience. We care about speed.
// #else
// # define CHECK_SLEEP_MASTER if(++initialized_objects > 500) { initialized_objects=0;sleep(world.tick_lag); }
diff --git a/code/controllers/subsystem.dm b/code/controllers/subsystem.dm
index 3e5e269057..02f45a5415 100644
--- a/code/controllers/subsystem.dm
+++ b/code/controllers/subsystem.dm
@@ -13,27 +13,48 @@
/// Name of the subsystem - you must change this
name = "fire coderbus"
- /// Order of initialization. Higher numbers are initialized first, lower numbers later. Use or create defines such as [INIT_ORDER_DEFAULT] so we can see the order in one file.
- var/init_order = INIT_ORDER_DEFAULT
+ /// Determines which subsystems this subsystem is dependant on to initialize. Will initialize after all specified subsystems.
+ /// If init_stage is earlier than a dependent subsystem, will throw an error and push the init stage forward to that subsystem.
+ /// Usage: Put the typepaths of the subsystems that need to init before this one in this list.
+ var/list/dependencies = list()
+
+ /// The inverse of the dependencies. Can be set manually, but will also get evaluated at runtime. Turns into a list of instances at runtime.
+ /// Usage: Put the typepaths of the subsystems that need to init after this one in this list.
+ var/list/dependents
+
+ /// ID of the subsystem. Set automatically when the dependency graph is evaluated. Used primarily in determining order.
+ var/ordering_id = 0
+
+ /// Do not modify. Automatically set when the dependency graph is evaluated. Similar to ordering_id, but evaluated after init_stage.
+ var/init_order = 0
/// Time to wait (in deciseconds) between each call to fire(). Must be a positive integer.
var/wait = 20
- /// Priority Weight: When mutiple subsystems need to run in the same tick, higher priority subsystems will be given a higher share of the tick before MC_TICK_CHECK triggers a sleep, higher priority subsystems also run before lower priority subsystems.
+ /// Priority Weight: When multiple subsystems need to run in the same tick, higher priority subsystems will be given a higher share of the tick before MC_TICK_CHECK triggers a sleep, higher priority subsystems also run before lower priority subsystems
var/priority = FIRE_PRIORITY_DEFAULT
/// [Subsystem Flags][SS_NO_INIT] to control binary behavior. Flags must be set at compile time or before preinit finishes to take full effect. (You can also restart the mc to force them to process again)
var/flags = NONE
- /// This var is set to TRUE after the subsystem has been initialized.
- var/subsystem_initialized = FALSE
+ /// Which stage does this subsystem init at. Earlier stages can fire while later stages init.
+ var/init_stage = INITSTAGE_MAIN
+
+ /// This var is set to `INITIALIZATION_INNEW_REGULAR` after the subsystem has been initialized.
+ var/initialized = FALSE
/// Set to 0 to prevent fire() calls, mostly for admin use or subsystems that may be resumed later
/// use the [SS_NO_FIRE] flag instead for systems that never fire to keep it from even being added to list that is checked every tick
var/can_fire = TRUE
///Bitmap of what game states can this subsystem fire at. See [RUNLEVELS_DEFAULT] for more details.
- var/runlevels = RUNLEVELS_DEFAULT
+ var/runlevels = RUNLEVELS_DEFAULT //points of the game at which the SS can fire
+
+ /**
+ * boolean set by admins. if TRUE then this subsystem will stop the world profiler after ignite() returns and start it again when called.
+ * used so that you can audit a specific subsystem or group of subsystems' synchronous call chain.
+ */
+ var/profiler_focused = FALSE
/*
* The following variables are managed by the MC and should not be modified directly.
@@ -54,6 +75,9 @@
/// Running average of the amount of tick usage (in percents of a game tick) the subsystem has spent past its allocated time without pausing
var/tick_overrun = 0
+ /// Flat list of usage and time, every odd index is a log time, every even index is a usage
+ var/list/rolling_usage = list()
+
/// How much of a tick (in percents of a tick) were we allocated last fire.
var/tick_allocation_last = 0
@@ -78,6 +102,9 @@
/// Tracks the amount of completed runs for the subsystem
var/times_fired = 0
+ /// How many fires have we been requested to postpone
+ var/postponed_fires = 0
+
/// Time the subsystem entered the queue, (for timing and priority reasons)
var/queued_time = 0
@@ -92,9 +119,13 @@
/// Previous subsystem in the queue of subsystems to run this tick
var/datum/controller/subsystem/queue_prev
+ /// String to store an applicable error message for a subsystem crashing, used to help debug crashes in contexts such as Continuous Integration/Unit Tests
+ var/initialization_failure_message = null
+
//Do not blindly add vars here to the bottom, put it where it goes above
//If your var only has two values, put it in as a flag.
+
//Do not override
///datum/controller/subsystem/New()
@@ -107,7 +138,7 @@
///This is used so the mc knows when the subsystem sleeps. do not override.
/datum/controller/subsystem/proc/ignite(resumed = FALSE)
SHOULD_NOT_OVERRIDE(TRUE)
- set waitfor = 0
+ set waitfor = FALSE
. = SS_IDLE
tick_allocation_last = Master.current_ticklimit-(TICK_USAGE)
@@ -131,7 +162,7 @@
///Sleeping in here prevents future fires until returned.
/datum/controller/subsystem/proc/fire(resumed = FALSE)
flags |= SS_NO_FIRE
- throw EXCEPTION("Subsystem [src]([type]) does not fire() but did not set the SS_NO_FIRE flag. Please add the SS_NO_FIRE flag to any subsystem that doesn't fire so it doesn't get added to the processing list and waste cpu.")
+ CRASH("Subsystem [src]([type]) does not fire() but did not set the SS_NO_FIRE flag. Please add the SS_NO_FIRE flag to any subsystem that doesn't fire so it doesn't get added to the processing list and waste cpu.")
/datum/controller/subsystem/Destroy()
dequeue()
@@ -141,6 +172,31 @@
Master.subsystems -= src
return ..()
+
+/** Update next_fire for the next run.
+ * reset_time (bool) - Ignore things that would normally alter the next fire, like tick_overrun, and last_fire. (also resets postpone)
+ */
+/datum/controller/subsystem/proc/update_nextfire(reset_time = FALSE)
+ var/queue_node_flags = flags
+
+ if (reset_time)
+ postponed_fires = 0
+ if (queue_node_flags & SS_TICKER)
+ next_fire = world.time + (world.tick_lag * wait)
+ else
+ next_fire = world.time + wait
+ return
+
+ if (queue_node_flags & SS_TICKER)
+ next_fire = world.time + (world.tick_lag * wait)
+ else if (queue_node_flags & SS_POST_FIRE_TIMING)
+ next_fire = world.time + wait + (world.tick_lag * (tick_overrun/100))
+ else if (queue_node_flags & SS_KEEP_TIMING)
+ next_fire += wait
+ else
+ next_fire = queued_time + wait + (world.tick_lag * (tick_overrun/100))
+
+
///Queue it to run.
/// (we loop thru a linked list until we get to the end or find the right point)
/// (this lets us sort our run order correctly without having to re-sort the entire already sorted list)
@@ -155,8 +211,8 @@
queue_node_priority = queue_node.queued_priority
queue_node_flags = queue_node.flags
- if (queue_node_flags & SS_TICKER)
- if (!(SS_flags & SS_TICKER))
+ if (queue_node_flags & (SS_TICKER|SS_BACKGROUND) == SS_TICKER)
+ if ((SS_flags & (SS_TICKER|SS_BACKGROUND)) != SS_TICKER)
continue
if (queue_node_priority < SS_priority)
break
@@ -207,9 +263,9 @@
queue_next.queue_prev = queue_prev
if (queue_prev)
queue_prev.queue_next = queue_next
- if (src == Master.queue_tail)
+ if (Master && (src == Master.queue_tail))
Master.queue_tail = queue_prev
- if (src == Master.queue_head)
+ if (Master && (src == Master.queue_head))
Master.queue_head = queue_next
queued_time = 0
if (state == SS_QUEUED)
@@ -228,14 +284,13 @@
/datum/controller/subsystem/proc/OnConfigLoad()
/**
- * Used to initialize the subsystem. This is expected to be overriden by subtypes.
+ * Used to initialize the subsystem. This is expected to be overridden by subtypes.
*/
/datum/controller/subsystem/Initialize()
return SS_INIT_NONE
-//hook for printing stats to the "MC" statuspanel for admins to see performance and related stats etc.
/datum/controller/subsystem/stat_entry(msg)
- if(can_fire && !(SS_NO_FIRE & flags))
+ if(can_fire && !(SS_NO_FIRE & flags) && init_stage <= Master.init_stage_completed)
msg = "[round(cost,1)]ms|[round(tick_usage,1)]%([round(tick_overrun,1)]%)|[round(ticks,0.1)]\t[msg]"
else
msg = "OFFLINE\t[msg]"
@@ -254,45 +309,30 @@
if (SS_IDLE)
. = " "
-//could be used to postpone a costly subsystem for (default one) var/cycles, cycles
-//for instance, during cpu intensive operations like explosions
+/// Causes the next "cycle" fires to be missed. Effect is accumulative but can reset by calling update_nextfire(reset_time = TRUE)
/datum/controller/subsystem/proc/postpone(cycles = 1)
- if(next_fire - world.time < wait)
- next_fire += (wait*cycles)
+ if (can_fire && cycles >= 1)
+ postponed_fires += cycles
+
+/// Prunes out of date entries in our rolling usage list
+/datum/controller/subsystem/proc/prune_rolling_usage()
+ var/list/rolling_usage = src.rolling_usage
+ var/cut_to = 0
+ while(cut_to + 2 <= length(rolling_usage) && rolling_usage[cut_to + 1] < DS2TICKS(world.time - Master.rolling_usage_length))
+ cut_to += 2
+ if(cut_to)
+ rolling_usage.Cut(1, cut_to + 1)
//usually called via datum/controller/subsystem/New() when replacing a subsystem (i.e. due to a recurring crash)
//should attempt to salvage what it can from the old instance of subsystem
/datum/controller/subsystem/Recover()
-// Suspends this subsystem from being queued for running. If already in the queue, sleeps until idle. Returns FALSE if the subsystem was already suspended.
-/datum/controller/subsystem/proc/suspend()
- . = (can_fire > 0) // Return true if we were previously runnable, false if previously suspended.
- can_fire = FALSE
- // Safely sleep in a loop until the subsystem is idle, (or its been un-suspended somehow)
- while(can_fire <= 0 && state != SS_IDLE)
- stoplag() // Safely sleep in a loop until
-
-// Wakes a suspended subsystem.
-/datum/controller/subsystem/proc/wake()
- can_fire = TRUE
-
-// This subsystem has destabilized the game and is being put on warning. At this point there may be
-// an opportunity to clean up the subsystem or check it for errors in ways that would otherwise be too slow.
-// You should log the errors/cleanup results, so you can fix the problem rather than using this as a crutch.
-/datum/controller/subsystem/proc/fail()
- var/msg = "[name] subsystem being blamed for MC failure"
- log_world(msg)
- log_game(msg)
-
-// DO NOT ATTEMPT RECOVERY. Only log debugging info. You should leave the subsystem as it is.
-// Attempting recovery here could make things worse, create hard recursions with the MC disabling it every run, etc.
-/datum/controller/subsystem/proc/critfail()
- var/msg = "[name] subsystem received final blame for MC failure"
- log_world(msg)
- log_game(msg)
-
/datum/controller/subsystem/vv_edit_var(var_name, var_value)
switch (var_name)
+ if (NAMEOF(src, can_fire))
+ //this is so the subsystem doesn't rapid fire to make up missed ticks causing more lag
+ if (var_value)
+ update_nextfire(reset_time = TRUE)
if (NAMEOF(src, queued_priority)) //editing this breaks things.
return FALSE
. = ..()
diff --git a/code/controllers/subsystems/admin_verbs.dm b/code/controllers/subsystems/admin_verbs.dm
index b4bfcf487c..fabb7eff7b 100644
--- a/code/controllers/subsystems/admin_verbs.dm
+++ b/code/controllers/subsystems/admin_verbs.dm
@@ -3,7 +3,7 @@ GENERAL_PROTECT_DATUM(/datum/controller/subsystem/admin_verbs)
SUBSYSTEM_DEF(admin_verbs)
name = "Admin Verbs"
flags = SS_NO_FIRE
- //init_stage = INITSTAGE_EARLY
+ init_stage = INITSTAGE_EARLY
/// A list of all admin verbs indexed by their type.
var/list/datum/admin_verb/admin_verbs_by_type = list()
/// A list of all admin verbs indexed by their visibility flag.
diff --git a/code/controllers/subsystems/ai.dm b/code/controllers/subsystems/ai.dm
index 4e5256d949..090a405a52 100644
--- a/code/controllers/subsystems/ai.dm
+++ b/code/controllers/subsystems/ai.dm
@@ -1,11 +1,15 @@
SUBSYSTEM_DEF(ai)
name = "AI"
- init_order = INIT_ORDER_AI
priority = FIRE_PRIORITY_AI
wait = 2 SECONDS
flags = SS_NO_INIT
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
+ dependencies = list(
+ /datum/controller/subsystem/air,
+ /datum/controller/subsystem/mobs
+ )
+
var/list/processing = list()
var/list/currentrun = list()
diff --git a/code/controllers/subsystems/aifast.dm b/code/controllers/subsystems/aifast.dm
index 2580c156ac..bd3294af0e 100644
--- a/code/controllers/subsystems/aifast.dm
+++ b/code/controllers/subsystems/aifast.dm
@@ -1,11 +1,14 @@
SUBSYSTEM_DEF(aifast)
name = "AI (Fast)"
- init_order = INIT_ORDER_AI_FAST
priority = FIRE_PRIORITY_AI
wait = 0.25 SECONDS // Every quarter second
flags = SS_NO_INIT
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
+ dependencies = list(
+ /datum/controller/subsystem/ai
+ )
+
var/list/processing = list()
var/list/currentrun = list()
diff --git a/code/controllers/subsystems/air.dm b/code/controllers/subsystems/air.dm
index 3a342124fc..6fa1c5e616 100644
--- a/code/controllers/subsystems/air.dm
+++ b/code/controllers/subsystems/air.dm
@@ -65,7 +65,9 @@ Class Procs:
SUBSYSTEM_DEF(air)
name = "Air"
- init_order = INIT_ORDER_AIR
+ dependencies = list(
+ /datum/controller/subsystem/atoms
+ )
priority = FIRE_PRIORITY_AIR
wait = 2 SECONDS // seconds (We probably can speed this up actually)
flags = SS_BACKGROUND // TODO - Should this really be background? It might be important.
diff --git a/code/controllers/subsystems/air_traffic.dm b/code/controllers/subsystems/air_traffic.dm
index 8ee8a354fd..3e78fa84ad 100644
--- a/code/controllers/subsystems/air_traffic.dm
+++ b/code/controllers/subsystems/air_traffic.dm
@@ -6,7 +6,7 @@ SUBSYSTEM_DEF(atc)
priority = FIRE_PRIORITY_ATC
runlevels = RUNLEVEL_GAME
wait = 2 SECONDS
- init_order = INIT_ORDER_ATC
+ init_stage = INITSTAGE_LAST
flags = SS_BACKGROUND
VAR_PRIVATE/next_tick = 0
diff --git a/code/controllers/subsystems/airflow.dm b/code/controllers/subsystems/airflow.dm
index 30ad9f9ea0..0e87df8259 100644
--- a/code/controllers/subsystems/airflow.dm
+++ b/code/controllers/subsystems/airflow.dm
@@ -16,6 +16,11 @@ SUBSYSTEM_DEF(airflow)
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
priority = FIRE_PRIORITY_AIRFLOW
+ dependencies = list(
+ /datum/controller/subsystem/atoms,
+ /datum/controller/subsystem/air
+ )
+
var/list/processing = list()
var/list/currentrun = list()
diff --git a/code/controllers/subsystems/alarm.dm b/code/controllers/subsystems/alarm.dm
index 1fb94ac556..61af1f852d 100644
--- a/code/controllers/subsystems/alarm.dm
+++ b/code/controllers/subsystems/alarm.dm
@@ -2,7 +2,9 @@ SUBSYSTEM_DEF(alarm)
name = "Alarm"
wait = 2 SECONDS
priority = FIRE_PRIORITY_ALARM
- init_order = INIT_ORDER_ALARM
+ dependencies = list(
+ /datum/controller/subsystem/mapping
+ )
var/list/datum/alarm/all_handlers
var/tmp/list/currentrun = null
var/static/list/active_alarm_cache = list()
diff --git a/code/controllers/subsystems/asset_loading.dm b/code/controllers/subsystems/asset_loading.dm
index ecdf406894..642f9ca4b5 100644
--- a/code/controllers/subsystems/asset_loading.dm
+++ b/code/controllers/subsystems/asset_loading.dm
@@ -13,10 +13,11 @@ SUBSYSTEM_DEF(asset_loading)
while(length(generate_queue))
var/datum/asset/to_load = generate_queue[generate_queue.len]
- to_load.queued_generation()
-
if(MC_TICK_CHECK)
return
+
+ to_load.queued_generation()
+
last_queue_len = length(generate_queue)
generate_queue.len--
// We just emptied the queue
diff --git a/code/controllers/subsystems/assets.dm b/code/controllers/subsystems/assets.dm
index d34d05ff55..32d8b4c390 100644
--- a/code/controllers/subsystems/assets.dm
+++ b/code/controllers/subsystems/assets.dm
@@ -1,6 +1,11 @@
SUBSYSTEM_DEF(assets)
name = "Assets"
- init_order = INIT_ORDER_ASSETS
+ dependencies = list(
+ /datum/controller/subsystem/holomaps,
+ /datum/controller/subsystem/robot_sprites
+ ///datum/controller/subsystem/persistent_paintings,
+ ///datum/controller/subsystem/greyscale_previews,
+ )
flags = SS_NO_FIRE
var/list/datum/asset_cache_item/cache = list()
var/list/preload = list()
@@ -32,7 +37,7 @@ SUBSYSTEM_DEF(assets)
transport.Initialize(cache)
- subsystem_initialized = TRUE
+ initialized = TRUE
return SS_INIT_SUCCESS
/datum/controller/subsystem/assets/Recover()
diff --git a/code/controllers/subsystems/atoms.dm b/code/controllers/subsystems/atoms.dm
index e30803118c..f8d3cece62 100644
--- a/code/controllers/subsystems/atoms.dm
+++ b/code/controllers/subsystems/atoms.dm
@@ -1,6 +1,14 @@
SUBSYSTEM_DEF(atoms)
name = "Atoms"
- init_order = INIT_ORDER_ATOMS
+ dependencies = list(
+ /datum/controller/subsystem/garbage,
+ /datum/controller/subsystem/mapping,
+ /datum/controller/subsystem/planets,
+ /datum/controller/subsystem/transcore,
+ /datum/controller/subsystem/chemistry,
+ /datum/controller/subsystem/sounds,
+ /datum/controller/subsystem/job
+ )
flags = SS_NO_FIRE
/// A stack of list(source, desired initialized state)
@@ -8,7 +16,7 @@ SUBSYSTEM_DEF(atoms)
var/list/initialized_state = list()
var/base_initialized
- var/initialized = INITIALIZATION_INSSATOMS
+ var/atom_initialized = INITIALIZATION_INSSATOMS
var/list/late_loaders = list()
var/list/BadInitializeCalls = list()
@@ -25,19 +33,19 @@ SUBSYSTEM_DEF(atoms)
var/list/mapload_init_times = list()
#endif
- initialized = INITIALIZATION_INSSATOMS
+ atom_initialized = INITIALIZATION_INSSATOMS
/datum/controller/subsystem/atoms/Initialize()
init_start_time = world.time
- initialized = INITIALIZATION_INNEW_MAPLOAD
+ atom_initialized = INITIALIZATION_INNEW_MAPLOAD
InitializeAtoms()
- initialized = INITIALIZATION_INNEW_REGULAR
+ atom_initialized = INITIALIZATION_INNEW_REGULAR
return SS_INIT_SUCCESS
/datum/controller/subsystem/atoms/proc/InitializeAtoms(list/atoms, list/atoms_to_return)
- if(initialized == INITIALIZATION_INSSATOMS)
+ if(atom_initialized == INITIALIZATION_INSSATOMS)
return
// Generate a unique mapload source for this run of InitializeAtoms
@@ -142,9 +150,9 @@ SUBSYSTEM_DEF(atoms)
/// Accepts a state and a source, the most recent state is used, sources exist to prevent overriding old values accidentally
/datum/controller/subsystem/atoms/proc/set_tracked_initalized(state, source)
if(!length(initialized_state))
- base_initialized = initialized
+ base_initialized = atom_initialized
initialized_state += list(list(source, state))
- initialized = state
+ atom_initialized = state
/datum/controller/subsystem/atoms/proc/clear_tracked_initalize(source)
if(!length(initialized_state))
@@ -155,18 +163,18 @@ SUBSYSTEM_DEF(atoms)
break
if(!length(initialized_state))
- initialized = base_initialized
+ atom_initialized = base_initialized
base_initialized = INITIALIZATION_INNEW_REGULAR
return
- initialized = initialized_state[length(initialized_state)][2]
+ atom_initialized = initialized_state[length(initialized_state)][2]
/// Returns TRUE if anything is currently being initialized
/datum/controller/subsystem/atoms/proc/initializing_something()
return length(initialized_state) > 1
/datum/controller/subsystem/atoms/Recover()
- initialized = SSatoms.initialized
- if(initialized == INITIALIZATION_INNEW_MAPLOAD)
+ atom_initialized = SSatoms.atom_initialized
+ if(atom_initialized == INITIALIZATION_INNEW_MAPLOAD)
InitializeAtoms()
initialized_state = SSatoms.initialized_state
BadInitializeCalls = SSatoms.BadInitializeCalls
@@ -187,7 +195,7 @@ SUBSYSTEM_DEF(atoms)
/// Prepares an atom to be deleted once the atoms SS is initialized.
/datum/controller/subsystem/atoms/proc/prepare_deletion(atom/target)
- if (initialized == INITIALIZATION_INNEW_REGULAR)
+ if (atom_initialized == INITIALIZATION_INNEW_REGULAR)
// Atoms SS has already completed, just kill it now.
qdel(target)
else
diff --git a/code/controllers/subsystems/character_setup.dm b/code/controllers/subsystems/character_setup.dm
index 272cd0b8ab..7a26d70c76 100644
--- a/code/controllers/subsystems/character_setup.dm
+++ b/code/controllers/subsystems/character_setup.dm
@@ -1,6 +1,5 @@
SUBSYSTEM_DEF(character_setup)
name = "Character Setup"
- init_order = INIT_ORDER_DEFAULT
priority = FIRE_PRIORITY_CHARSETUP
flags = SS_BACKGROUND | SS_NO_INIT
wait = 1 SECOND
diff --git a/code/controllers/subsystems/chat.dm b/code/controllers/subsystems/chat.dm
index b8b2917c8d..575a9fad31 100644
--- a/code/controllers/subsystems/chat.dm
+++ b/code/controllers/subsystems/chat.dm
@@ -8,7 +8,7 @@ SUBSYSTEM_DEF(chat)
flags = SS_TICKER|SS_NO_INIT
wait = 1
priority = FIRE_PRIORITY_CHAT
- init_order = INIT_ORDER_CHAT
+ init_stage = INITSTAGE_LAST
/// Assosciates a ckey with a list of messages to send to them.
var/list/list/datum/chat_payload/client_to_payloads = list()
diff --git a/code/controllers/subsystems/chemistry.dm b/code/controllers/subsystems/chemistry.dm
index 0e9aae6f58..6de865b9fd 100644
--- a/code/controllers/subsystems/chemistry.dm
+++ b/code/controllers/subsystems/chemistry.dm
@@ -2,7 +2,9 @@ SUBSYSTEM_DEF(chemistry)
name = "Chemistry"
wait = 20
flags = SS_NO_FIRE
- init_order = INIT_ORDER_CHEMISTRY
+ dependencies = list(
+ /datum/controller/subsystem/garbage
+ )
var/list/chemical_reactions = list()
var/list/chemical_reactions_by_product = list()
diff --git a/code/controllers/subsystems/circuits.dm b/code/controllers/subsystems/circuits.dm
index 777b74f4d7..a1130bff25 100644
--- a/code/controllers/subsystems/circuits.dm
+++ b/code/controllers/subsystems/circuits.dm
@@ -4,8 +4,12 @@
//
SUBSYSTEM_DEF(circuit)
name = "Circuit"
- init_order = INIT_ORDER_CIRCUIT
flags = SS_NO_FIRE
+
+ dependencies = list(
+ /datum/controller/subsystem/atoms
+ )
+
var/list/all_components = list() // Associative list of [component_name]:[component_path] pairs
var/list/cached_components = list() // Associative list of [component_path]:[component] pairs
var/list/all_assemblies = list() // Associative list of [assembly_name]:[assembly_path] pairs
diff --git a/code/controllers/subsystems/communications.dm b/code/controllers/subsystems/communications.dm
new file mode 100644
index 0000000000..6c25eecd59
--- /dev/null
+++ b/code/controllers/subsystems/communications.dm
@@ -0,0 +1,207 @@
+/*
+ HOW IT WORKS
+
+ The SSradio is a global object maintaining all radio transmissions, think about it as about "ether".
+ Note that walkie-talkie, intercoms and headsets handle transmission using nonstandard way.
+ procs:
+
+ add_object(obj/device as obj, var/new_frequency as num, var/radio_filter as text|null = null)
+ Adds listening object.
+ parameters:
+ device - device receiving signals, must have proc receive_signal (see description below).
+ one device may listen several frequencies, but not same frequency twice.
+ new_frequency - see possibly frequencies below;
+ radio_filter - thing for optimization. Optional, but recommended.
+ All filters should be consolidated in this file, see defines later.
+ Device without listening filter will receive all signals (on specified frequency).
+ Device with filter will receive any signals sent without filter.
+ Device with filter will not receive any signals sent with different filter.
+ returns:
+ Reference to frequency object.
+
+ remove_object (obj/device, old_frequency)
+ Obliviously, after calling this proc, device will not receive any signals on old_frequency.
+ Other frequencies will left unaffected.
+
+ return_frequency(var/frequency as num)
+ returns:
+ Reference to frequency object. Use it if you need to send and do not need to listen.
+
+ radio_frequency is a global object maintaining list of devices that listening specific frequency.
+ procs:
+
+ post_signal(obj/source as obj|null, datum/signal/signal, var/radio_filter as text|null = null, var/range as num|null = null)
+ Sends signal to all devices that wants such signal.
+ parameters:
+ source - object, emitted signal. Usually, devices will not receive their own signals.
+ signal - see description below.
+ radio_filter - described above.
+ range - radius of regular byond's square circle on that z-level. null means everywhere, on all z-levels.
+
+ obj/proc/receive_signal(datum/signal/signal, var/receive_method as num, var/receive_param)
+ Handler from received signals. By default does nothing. Define your own for your object.
+ Avoid of sending signals directly from this proc, use spawn(-1). DO NOT use sleep() here or call procs that sleep please. If you must, use spawn()
+ parameters:
+ signal - see description below. Extract all needed data from the signal before doing sleep(), spawn() or return!
+ receive_method - may be TRANSMISSION_WIRE or TRANSMISSION_RADIO.
+ TRANSMISSION_WIRE is currently unused.
+ receive_param - for TRANSMISSION_RADIO here comes frequency.
+
+ datum/signal
+ vars:
+ source
+ an object that emitted signal. Used for debug and bearing.
+ data
+ list with transmitting data. Usual use pattern:
+ data["msg"] = "hello world"
+ encryption
+ Some number symbolizing "encryption key".
+ Note that game actually do not use any cryptography here.
+ If receiving object don't know right key, it must ignore encrypted signal in its receive_signal.
+
+*/
+
+SUBSYSTEM_DEF(radio)
+ name = "Radio"
+ flags = SS_NO_FIRE
+ dependencies = list(
+ /datum/controller/subsystem/machines
+ )
+ var/list/datum/radio_frequency/frequencies = list()
+
+/datum/controller/subsystem/radio/Initialize()
+ GLOB.autospeaker = new (null, FALSE, null, null, TRUE) //Set up Global Announcer
+ return SS_INIT_SUCCESS
+
+/datum/controller/subsystem/radio/proc/add_object(obj/device as obj, var/new_frequency as num, var/radio_filter = null as text|null)
+ var/f_text = num2text(new_frequency)
+ var/datum/radio_frequency/frequency = frequencies[f_text]
+
+ if(!frequency)
+ frequency = new
+ frequency.frequency = new_frequency
+ frequencies[f_text] = frequency
+
+ frequency.add_listener(device, radio_filter)
+ return frequency
+
+/datum/controller/subsystem/radio/proc/remove_object(obj/device, old_frequency)
+ var/f_text = num2text(old_frequency)
+ var/datum/radio_frequency/frequency = frequencies[f_text]
+
+ if(frequency)
+ frequency.remove_listener(device)
+
+ if(frequency.devices.len == 0)
+ qdel(frequency)
+ frequencies -= f_text
+
+ return 1
+
+/datum/controller/subsystem/radio/proc/return_frequency(var/new_frequency as num)
+ var/f_text = num2text(new_frequency)
+ var/datum/radio_frequency/frequency = frequencies[f_text]
+
+ if(!frequency)
+ frequency = new
+ frequency.frequency = new_frequency
+ frequencies[f_text] = frequency
+
+ return frequency
+
+//Frequency channels
+
+/datum/radio_frequency
+ var/frequency as num
+ var/list/list/obj/devices = list()
+
+/datum/radio_frequency/proc/post_signal(obj/source as obj|null, datum/signal/signal, var/radio_filter = null as text|null, var/range = null as num|null)
+ var/turf/start_point
+ if(range)
+ start_point = get_turf(source)
+ if(!start_point)
+ qdel(signal)
+ return 0
+ if (radio_filter)
+ send_to_filter(source, signal, radio_filter, start_point, range)
+ send_to_filter(source, signal, RADIO_DEFAULT, start_point, range)
+ else
+ //Broadcast the signal to everyone!
+ for (var/next_filter in devices)
+ send_to_filter(source, signal, next_filter, start_point, range)
+
+//Sends a signal to all machines belonging to a given filter. Should be called by post_signal()
+/datum/radio_frequency/proc/send_to_filter(obj/source, datum/signal/signal, var/radio_filter, var/turf/start_point = null, var/range = null)
+ if (range && !start_point)
+ return
+
+ for(var/obj/device in devices[radio_filter])
+ if(device == source)
+ continue
+ if(range)
+ var/turf/end_point = get_turf(device)
+ if(!end_point)
+ continue
+ if(start_point.z!=end_point.z || get_dist(start_point, end_point) > range)
+ continue
+
+ device.receive_signal(signal, TRANSMISSION_RADIO, frequency)
+
+/datum/radio_frequency/proc/add_listener(obj/device as obj, var/radio_filter as text|null)
+ if (!radio_filter)
+ radio_filter = RADIO_DEFAULT
+ //log_admin("add_listener(device=[device],radio_filter=[radio_filter]) frequency=[frequency]")
+ var/list/obj/devices_line = devices[radio_filter]
+ if (!devices_line)
+ devices_line = new
+ devices[radio_filter] = devices_line
+ devices_line+=device
+// var/list/obj/devices_line___ = devices[filter_str]
+// var/l = devices_line___.len
+ //log_admin("DEBUG: devices_line.len=[devices_line.len]")
+ //log_admin("DEBUG: devices(filter_str).len=[l]")
+
+/datum/radio_frequency/proc/remove_listener(obj/device)
+ for (var/devices_filter in devices)
+ var/list/devices_line = devices[devices_filter]
+ devices_line-=device
+ while (null in devices_line)
+ devices_line -= null
+ if (devices_line.len==0)
+ devices -= devices_filter
+
+/datum/signal
+ var/obj/source
+
+ var/transmission_method = 0 //unused at the moment
+ //0 = wire
+ //1 = radio transmission
+ //2 = subspace transmission
+
+ var/list/data = list()
+ var/encryption
+
+ var/frequency = 0
+
+/datum/signal/proc/copy_from(datum/signal/model)
+ source = model.source
+ transmission_method = model.transmission_method
+ data = model.data
+ encryption = model.encryption
+ frequency = model.frequency
+
+/datum/signal/proc/debug_print()
+ if (source)
+ . = "signal = {source = '[source]' ([source:x],[source:y],[source:z])\n"
+ else
+ . = "signal = {source = '[source]' ()\n"
+ for (var/i in data)
+ . += "data\[\"[i]\"\] = \"[data[i]]\"\n"
+ if(islist(data[i]))
+ var/list/L = data[i]
+ for(var/t in L)
+ . += "data\[\"[i]\"\] list has: [t]"
+
+//callback used by objects to react to incoming radio signals
+/obj/proc/receive_signal(datum/signal/signal, receive_method, receive_param)
+ return null
diff --git a/code/controllers/subsystems/dbcore.dm b/code/controllers/subsystems/dbcore.dm
index e800b4aaca..b8acc18c9b 100644
--- a/code/controllers/subsystems/dbcore.dm
+++ b/code/controllers/subsystems/dbcore.dm
@@ -1,8 +1,10 @@
SUBSYSTEM_DEF(dbcore)
name = "Database"
flags = SS_TICKER
+ init_stage = INITSTAGE_EARLY
wait = 10 // Not seconds because we're running on SS_TICKER
- init_order = INIT_ORDER_DBCORE
+ runlevels = RUNLEVEL_LOBBY|RUNLEVELS_DEFAULT
+ priority = FIRE_PRIORITY_DATABASE
var/failed_connection_timeout = 0
@@ -41,6 +43,7 @@ SUBSYSTEM_DEF(dbcore)
//var/db_daemon_started = FALSE
/datum/controller/subsystem/dbcore/Initialize()
+ Connect()
return SS_INIT_SUCCESS
/datum/controller/subsystem/dbcore/stat_entry(msg)
diff --git a/code/controllers/subsystems/events.dm b/code/controllers/subsystems/events.dm
index 1a7ce0a4aa..ef229b58fe 100644
--- a/code/controllers/subsystems/events.dm
+++ b/code/controllers/subsystems/events.dm
@@ -1,6 +1,7 @@
SUBSYSTEM_DEF(events)
name = "Events" // VOREStation Edit - This is still the main events subsystem for us.
wait = 2 SECONDS
+ init_stage = INITSTAGE_LAST
var/tmp/list/currentrun = null
diff --git a/code/controllers/subsystems/events2.dm b/code/controllers/subsystems/events2.dm
index a0706fadef..a30d2a970a 100644
--- a/code/controllers/subsystems/events2.dm
+++ b/code/controllers/subsystems/events2.dm
@@ -5,6 +5,9 @@ SUBSYSTEM_DEF(event_ticker)
name = "Events (Ticker)"
wait = 2 SECONDS
runlevels = RUNLEVEL_GAME
+ dependencies = list(
+ /datum/controller/subsystem/events
+ )
// List of `/datum/event2/event`s that are currently active, and receiving process() ticks.
var/list/active_events = list()
diff --git a/code/controllers/subsystems/game_master.dm b/code/controllers/subsystems/game_master.dm
index fb1a2e7c35..ec9ae6378b 100644
--- a/code/controllers/subsystems/game_master.dm
+++ b/code/controllers/subsystems/game_master.dm
@@ -96,7 +96,7 @@ SUBSYSTEM_DEF(game_master)
// These are ran before committing to an event.
// Returns TRUE if the system is allowed to procede, otherwise returns FALSE.
/datum/controller/subsystem/game_master/proc/pre_event_checks(quiet = FALSE)
- if(!ticker || ticker.current_state != GAME_STATE_PLAYING)
+ if(!SSticker || SSticker.current_state != GAME_STATE_PLAYING)
if(!quiet)
log_game_master("Unable to start event: Ticker is nonexistent, or the game is not ongoing.")
return FALSE
diff --git a/code/controllers/subsystems/garbage.dm b/code/controllers/subsystems/garbage.dm
index a4bc5ee17d..3780495812 100644
--- a/code/controllers/subsystems/garbage.dm
+++ b/code/controllers/subsystems/garbage.dm
@@ -27,8 +27,7 @@ SUBSYSTEM_DEF(garbage)
wait = 2 SECONDS
flags = SS_POST_FIRE_TIMING|SS_BACKGROUND|SS_NO_INIT
runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
- init_order = INIT_ORDER_GARBAGE
-// init_stage = INITSTAGE_EARLY
+ init_stage = INITSTAGE_FIRST
var/list/collection_timeout = list(GC_FILTER_QUEUE, GC_CHECK_QUEUE, GC_DEL_QUEUE) // deciseconds to wait before moving something up in the queue to the next level
diff --git a/code/controllers/subsystems/holomaps.dm b/code/controllers/subsystems/holomaps.dm
index 11270bc2a5..8ee708c721 100644
--- a/code/controllers/subsystems/holomaps.dm
+++ b/code/controllers/subsystems/holomaps.dm
@@ -4,8 +4,10 @@
//
SUBSYSTEM_DEF(holomaps)
name = "HoloMiniMaps"
- init_order = INIT_ORDER_HOLOMAPS
flags = SS_NO_FIRE
+ dependencies = list(
+ /datum/controller/subsystem/atoms
+ )
var/static/holomaps_initialized = FALSE
var/static/list/holoMiniMaps = list()
var/static/list/extraMiniMaps = list()
diff --git a/code/controllers/subsystems/input.dm b/code/controllers/subsystems/input.dm
index 70d1f9cda6..482b84de67 100644
--- a/code/controllers/subsystems/input.dm
+++ b/code/controllers/subsystems/input.dm
@@ -1,7 +1,7 @@
SUBSYSTEM_DEF(input)
name = "Input"
wait = 1 // SS_TICKER means this runs every tick
- init_order = INIT_ORDER_INPUT
+ init_stage = INITSTAGE_EARLY
flags = SS_TICKER | SS_NO_INIT
priority = FIRE_PRIORITY_INPUT
runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
diff --git a/code/controllers/subsystems/internal_wiki.dm b/code/controllers/subsystems/internal_wiki.dm
index 86fc917982..1f40ca1ad8 100644
--- a/code/controllers/subsystems/internal_wiki.dm
+++ b/code/controllers/subsystems/internal_wiki.dm
@@ -6,7 +6,12 @@
SUBSYSTEM_DEF(internal_wiki)
name = "Wiki"
wait = 1
- init_order = INIT_ORDER_WIKI
+ //dependencies = list(
+ // /datum/controller/subsystem/chemistry,
+ // /datum/controller/subsystem/plants,
+ // /datum/controller/subsystem/supply
+ //)
+ init_stage = INITSTAGE_LAST
flags = SS_NO_FIRE
VAR_PRIVATE/list/pages = list()
diff --git a/code/controllers/subsystems/job.dm b/code/controllers/subsystems/job.dm
index c86d108415..7ee0918444 100644
--- a/code/controllers/subsystems/job.dm
+++ b/code/controllers/subsystems/job.dm
@@ -1,6 +1,8 @@
SUBSYSTEM_DEF(job)
name = "Job"
- init_order = INIT_ORDER_JOB
+ dependencies = list(
+ /datum/controller/subsystem/mapping,
+ )
flags = SS_NO_FIRE
var/list/occupations = list() //List of all jobs
diff --git a/code/controllers/subsystems/lighting.dm b/code/controllers/subsystems/lighting.dm
index c0c0e7b377..1e861298f7 100644
--- a/code/controllers/subsystems/lighting.dm
+++ b/code/controllers/subsystems/lighting.dm
@@ -1,7 +1,9 @@
SUBSYSTEM_DEF(lighting)
name = "Lighting"
+ dependencies = list(
+ /datum/controller/subsystem/machines
+ )
wait = 1
- init_order = INIT_ORDER_LIGHTING
flags = SS_TICKER
runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY // Do some work during lobby waiting period. May as well.
var/sun_mult = 1.0
@@ -19,8 +21,8 @@ SUBSYSTEM_DEF(lighting)
/datum/controller/subsystem/lighting/Initialize()
- if(!subsystem_initialized)
- subsystem_initialized = TRUE
+ if(!initialized)
+ initialized = TRUE
create_all_lighting_objects()
for(var/datum/planet/planet in SSplanets.planets)
@@ -177,5 +179,5 @@ SUBSYSTEM_DEF(lighting)
/datum/controller/subsystem/lighting
/datum/controller/subsystem/lighting/Recover()
- subsystem_initialized = SSlighting.subsystem_initialized
+ initialized = SSlighting.initialized
..()
diff --git a/code/controllers/subsystems/lobby_monitor.dm b/code/controllers/subsystems/lobby_monitor.dm
index 0a18f89f39..5f34a0a013 100644
--- a/code/controllers/subsystems/lobby_monitor.dm
+++ b/code/controllers/subsystems/lobby_monitor.dm
@@ -1,7 +1,6 @@
SUBSYSTEM_DEF(lobby_monitor)
name = "Lobby Art"
- init_order = INIT_ORDER_LOBBY
- // init_stage = INITSTAGE_EARLY
+ init_stage = INITSTAGE_EARLY
flags = SS_NO_INIT
wait = 1 SECOND
runlevels = ALL
diff --git a/code/controllers/subsystems/machines.dm b/code/controllers/subsystems/machines.dm
index a0edb971c9..f20d38d175 100644
--- a/code/controllers/subsystems/machines.dm
+++ b/code/controllers/subsystems/machines.dm
@@ -9,8 +9,10 @@
SUBSYSTEM_DEF(machines)
name = "Machines"
+ dependencies = list(
+ /datum/controller/subsystem/points_of_interest
+ )
priority = FIRE_PRIORITY_MACHINES
- init_order = INIT_ORDER_MACHINES
flags = SS_KEEP_TIMING
runlevels = RUNLEVEL_GAME|RUNLEVEL_POSTGAME
diff --git a/code/controllers/subsystems/mapping.dm b/code/controllers/subsystems/mapping.dm
index 958f65f35a..f49d004998 100644
--- a/code/controllers/subsystems/mapping.dm
+++ b/code/controllers/subsystems/mapping.dm
@@ -1,19 +1,32 @@
// Handles map-related tasks, mostly here to ensure it does so after the MC initializes.
SUBSYSTEM_DEF(mapping)
name = "Mapping"
- init_order = INIT_ORDER_MAPPING
+ //dependencies = list(
+ // /datum/controller/subsystem/job,
+ ///datum/controller/subsystem/processing/station,
+ // /datum/controller/subsystem/chemistry
+ // ///datum/controller/subsystem/processing/reagents
+ //)
+ dependencies = list(
+ ///datum/controller/subsystem/garbage,
+ /datum/controller/subsystem/vis_overlays,
+ /datum/controller/subsystem/chemistry
+ )
flags = SS_NO_FIRE
var/list/map_templates = list()
var/obj/effect/landmark/engine_loader/engine_loader
var/list/shelter_templates = list()
+ // TODO: Implement Later
+ var/datum/map/current_map
+
/datum/controller/subsystem/mapping/Recover()
flags |= SS_NO_INIT // Make extra sure we don't initialize twice.
shelter_templates = SSmapping.shelter_templates
/datum/controller/subsystem/mapping/Initialize()
- if(subsystem_initialized)
+ if(initialized)
return
world.max_z_changed() // This is to set up the player z-level list, maxz hasn't actually changed (probably)
load_map_templates()
@@ -24,6 +37,7 @@ SUBSYSTEM_DEF(mapping)
// TODO - Other stuff related to maps and areas could be moved here too. Look at /tg
// Lateload Code related to Expedition areas.
if(using_map) // VOREStation Edit: Re-enable this.
+ current_map = using_map
loadLateMaps()
if(CONFIG_GET(flag/generate_map)) // VOREStation Edit: Re-order this.
diff --git a/code/controllers/subsystems/media_tracks.dm b/code/controllers/subsystems/media_tracks.dm
index 9034bdc665..0c567cf63c 100644
--- a/code/controllers/subsystems/media_tracks.dm
+++ b/code/controllers/subsystems/media_tracks.dm
@@ -1,7 +1,7 @@
SUBSYSTEM_DEF(media_tracks)
name = "Media Tracks"
flags = SS_NO_FIRE
- init_order = INIT_ORDER_MEDIA_TRACKS
+ init_stage = INITSTAGE_EARLY
/// Every track, including secret
var/list/all_tracks = list()
diff --git a/code/controllers/subsystems/mobs.dm b/code/controllers/subsystems/mobs.dm
index 36043e9748..521d7cef4f 100644
--- a/code/controllers/subsystems/mobs.dm
+++ b/code/controllers/subsystems/mobs.dm
@@ -12,6 +12,12 @@ SUBSYSTEM_DEF(mobs)
flags = SS_KEEP_TIMING|SS_NO_INIT
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
+ dependencies = list(
+ /datum/controller/subsystem/atoms,
+ /datum/controller/subsystem/points_of_interest,
+ /datum/controller/subsystem/shuttles
+ )
+
var/list/currentrun = list()
var/log_extensively = FALSE
var/list/timelog = list()
@@ -96,22 +102,14 @@ SUBSYSTEM_DEF(mobs)
log_game(msg)
log_world(msg)
-/datum/controller/subsystem/mobs/fail()
- ..()
- log_recent()
-
-/datum/controller/subsystem/mobs/critfail()
- ..()
- log_recent()
-
/datum/controller/subsystem/mobs/proc/report_death(var/mob/living/L)
if(!L)
return
if(!L.key || !L.mind)
return
- if(!ticker || !ticker.mode)
+ if(!SSticker || !SSticker.mode)
return
- ticker.mode.check_win()
+ SSticker.mode.check_win()
// Don't bother with the rest if we've not got a DB to do anything with
if(!CONFIG_GET(flag/enable_stat_tracking) || !CONFIG_GET(flag/sql_enabled))
diff --git a/code/controllers/subsystems/nightshift.dm b/code/controllers/subsystems/nightshift.dm
index ecf7fa30e7..201a4794b9 100644
--- a/code/controllers/subsystems/nightshift.dm
+++ b/code/controllers/subsystems/nightshift.dm
@@ -1,6 +1,8 @@
SUBSYSTEM_DEF(nightshift)
name = "Night Shift"
- init_order = INIT_ORDER_NIGHTSHIFT
+ dependencies = list(
+ /datum/controller/subsystem/lighting
+ )
priority = FIRE_PRIORITY_NIGHTSHIFT
wait = 60 SECONDS
flags = SS_NO_TICK_CHECK
diff --git a/code/controllers/subsystems/overlays.dm b/code/controllers/subsystems/overlays.dm
index d1f1247081..236ee537f3 100644
--- a/code/controllers/subsystems/overlays.dm
+++ b/code/controllers/subsystems/overlays.dm
@@ -3,7 +3,9 @@ SUBSYSTEM_DEF(overlays)
flags = SS_TICKER
wait = 1 // SS_TICKER - Ticks
priority = FIRE_PRIORITY_OVERLAYS
- init_order = INIT_ORDER_OVERLAY
+ dependencies = list(
+ /datum/controller/subsystem/atoms
+ )
/// The queue of atoms that need overlay updates.
var/static/tmp/list/queue = list()
diff --git a/code/controllers/subsystems/overmap_renamer_vr.dm b/code/controllers/subsystems/overmap_renamer_vr.dm
index 149ece296e..460e172f46 100644
--- a/code/controllers/subsystems/overmap_renamer_vr.dm
+++ b/code/controllers/subsystems/overmap_renamer_vr.dm
@@ -4,8 +4,12 @@ Readme at code\modules\awaymissions\overmap_renamer\readme.md
SUBSYSTEM_DEF(overmap_renamer)
name = "Overmap Renamer"
- init_order = INIT_ORDER_MAPRENAME //Loaded very late in initializations. Must come before mapping and objs. Uses both as inputs.
- runlevels = RUNLEVEL_INIT
+ //Loaded very late in initializations. Must come before mapping and objs. Uses both as inputs.
+ init_stage = INITSTAGE_LAST
+ dependencies = list(
+ /datum/controller/subsystem/skybox
+ )
+ runlevels = RUNLEVEL_SETUP
flags = SS_NO_FIRE
diff --git a/code/controllers/subsystems/persistence.dm b/code/controllers/subsystems/persistence.dm
index 2b1cef0e77..575e81dfe1 100644
--- a/code/controllers/subsystems/persistence.dm
+++ b/code/controllers/subsystems/persistence.dm
@@ -1,7 +1,12 @@
SUBSYSTEM_DEF(persistence)
name = "Persistence"
- init_order = INIT_ORDER_PERSISTENCE
+ dependencies = list(
+ /datum/controller/subsystem/mapping,
+ /datum/controller/subsystem/atoms,
+ /datum/controller/subsystem/points_of_interest
+ )
flags = SS_NO_FIRE
+
var/list/tracking_values = list()
var/list/persistence_datums = list()
diff --git a/code/controllers/subsystems/ping.dm b/code/controllers/subsystems/ping.dm
index 8f4836290a..2ef44bc167 100644
--- a/code/controllers/subsystems/ping.dm
+++ b/code/controllers/subsystems/ping.dm
@@ -6,7 +6,7 @@
SUBSYSTEM_DEF(ping)
name = "Ping"
priority = FIRE_PRIORITY_PING
- // init_stage = INITSTAGE_EARLY
+ init_stage = INITSTAGE_EARLY
wait = 4 SECONDS
flags = SS_NO_INIT
runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT
diff --git a/code/controllers/subsystems/planets.dm b/code/controllers/subsystems/planets.dm
index 4ad613b4ce..b3d319bad4 100644
--- a/code/controllers/subsystems/planets.dm
+++ b/code/controllers/subsystems/planets.dm
@@ -1,10 +1,12 @@
SUBSYSTEM_DEF(planets)
name = "Planets"
- init_order = INIT_ORDER_PLANETS
priority = FIRE_PRIORITY_PLANETS
wait = 2 SECONDS
flags = SS_BACKGROUND
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
+ dependencies = list(
+ /datum/controller/subsystem/plants
+ )
var/static/list/planets = list()
var/static/list/z_to_planet = list()
@@ -75,7 +77,7 @@ SUBSYSTEM_DEF(planets)
if(MC_TICK_CHECK)
return
- #ifndef UNIT_TEST // Don't be updating temperatures and such during unit tests
+ #ifndef UNIT_TESTS // Don't be updating temperatures and such during unit tests
var/list/needs_temp_update = src.needs_temp_update
while(needs_temp_update.len)
var/datum/planet/P = needs_temp_update[needs_temp_update.len]
diff --git a/code/controllers/subsystems/plants.dm b/code/controllers/subsystems/plants.dm
index 922a2c42e5..9d266640cf 100644
--- a/code/controllers/subsystems/plants.dm
+++ b/code/controllers/subsystems/plants.dm
@@ -2,7 +2,9 @@
SUBSYSTEM_DEF(plants)
name = "Plants"
- init_order = INIT_ORDER_PLANTS
+ dependencies = list(
+ /datum/controller/subsystem/mapping
+ )
priority = FIRE_PRIORITY_PLANTS
wait = PLANT_TICK_TIME
diff --git a/code/controllers/subsystems/pois.dm b/code/controllers/subsystems/pois.dm
index e5bfbbecf0..053e6abfc1 100644
--- a/code/controllers/subsystems/pois.dm
+++ b/code/controllers/subsystems/pois.dm
@@ -3,8 +3,10 @@ SUBSYSTEM_DEF(points_of_interest)
name = "Points of Interest"
wait = 1 SECONDS
priority = FIRE_PRIORITY_POIS
- init_order = INIT_ORDER_POIS
runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT //POIs can be loaded mid-round.
+ dependencies = list(
+ /datum/controller/subsystem/holomaps
+ )
var/list/obj/effect/landmark/poi_loader/poi_queue = list()
/datum/controller/subsystem/points_of_interest/Initialize()
diff --git a/code/controllers/subsystems/processing/instruments.dm b/code/controllers/subsystems/processing/instruments.dm
index e3a4802f11..af6bacc2fc 100644
--- a/code/controllers/subsystems/processing/instruments.dm
+++ b/code/controllers/subsystems/processing/instruments.dm
@@ -1,7 +1,6 @@
PROCESSING_SUBSYSTEM_DEF(instruments)
name = "Instruments"
wait = 0.5
- init_order = INIT_ORDER_INSTRUMENTS
flags = SS_KEEP_TIMING
priority = FIRE_PRIORITY_INSTRUMENTS
/// List of all instrument data, associative id = datum
diff --git a/code/controllers/subsystems/processing/processing.dm b/code/controllers/subsystems/processing/processing.dm
index 51c1afe015..42ebfba08c 100644
--- a/code/controllers/subsystems/processing/processing.dm
+++ b/code/controllers/subsystems/processing/processing.dm
@@ -96,14 +96,6 @@ SUBSYSTEM_DEF(processing)
log_game(msg)
log_world(msg)
-/datum/controller/subsystem/processing/fail()
- ..()
- log_recent()
-
-/datum/controller/subsystem/processing/critfail()
- ..()
- log_recent()
-
/datum/proc/DebugSubsystemProcess(var/wait, var/times_fired, var/datum/controller/subsystem/processing/subsystem)
subsystem.debug_last_thing = src
var/start_tick = world.time
diff --git a/code/controllers/subsystems/profiler.dm b/code/controllers/subsystems/profiler.dm
new file mode 100644
index 0000000000..578bb8bc08
--- /dev/null
+++ b/code/controllers/subsystems/profiler.dm
@@ -0,0 +1,69 @@
+SUBSYSTEM_DEF(profiler)
+ name = "Profiler"
+ init_stage = INITSTAGE_FIRST
+ runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
+ wait = 300 SECONDS
+ var/fetch_cost = 0
+ var/write_cost = 0
+
+/datum/controller/subsystem/profiler/stat_entry(msg)
+ msg += "F:[round(fetch_cost,1)]ms"
+ msg += "|W:[round(write_cost,1)]ms"
+ return msg
+
+/datum/controller/subsystem/profiler/Initialize()
+ if(CONFIG_GET(flag/auto_profile))
+ StartProfiling()
+ else
+ StopProfiling() //Stop the early start profiler
+ wait = CONFIG_GET(number/profiler_interval)
+ return SS_INIT_SUCCESS
+
+/datum/controller/subsystem/profiler/OnConfigLoad()
+ if(CONFIG_GET(flag/auto_profile))
+ StartProfiling()
+ can_fire = TRUE
+ else
+ StopProfiling()
+ can_fire = FALSE
+
+/datum/controller/subsystem/profiler/fire()
+ DumpFile()
+
+/datum/controller/subsystem/profiler/Shutdown()
+ if(CONFIG_GET(flag/auto_profile))
+ DumpFile(allow_yield = FALSE)
+ world.Profile(PROFILE_CLEAR, type = "sendmaps")
+ return ..()
+
+/datum/controller/subsystem/profiler/proc/StartProfiling()
+ world.Profile(PROFILE_START)
+ world.Profile(PROFILE_START, type = "sendmaps")
+
+/datum/controller/subsystem/profiler/proc/StopProfiling()
+ world.Profile(PROFILE_STOP)
+ world.Profile(PROFILE_STOP, type = "sendmaps")
+
+/datum/controller/subsystem/profiler/proc/DumpFile(allow_yield = TRUE)
+ var/timer = TICK_USAGE_REAL
+ var/current_profile_data = world.Profile(PROFILE_REFRESH, format = "json")
+ var/current_sendmaps_data = world.Profile(PROFILE_REFRESH, type = "sendmaps", format="json")
+ fetch_cost = MC_AVERAGE(fetch_cost, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
+ if(allow_yield)
+ CHECK_TICK
+
+ if(!length(current_profile_data)) //Would be nice to have explicit proc to check this
+ stack_trace("Warning, profiling stopped manually before dump.")
+ var/prof_file = file("[GLOB.log_directory]/profiler/profiler-[round(world.time * 0.1, 10)].json")
+ if(fexists(prof_file))
+ fdel(prof_file)
+ if(!length(current_sendmaps_data)) //Would be nice to have explicit proc to check this
+ stack_trace("Warning, sendmaps profiling stopped manually before dump.")
+ var/sendmaps_file = file("[GLOB.log_directory]/profiler/sendmaps-[round(world.time * 0.1, 10)].json")
+ if(fexists(sendmaps_file))
+ fdel(sendmaps_file)
+
+ timer = TICK_USAGE_REAL
+ WRITE_FILE(prof_file, current_profile_data)
+ WRITE_FILE(sendmaps_file, current_sendmaps_data)
+ write_cost = MC_AVERAGE(write_cost, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
diff --git a/code/controllers/subsystems/research.dm b/code/controllers/subsystems/research.dm
index cc581440c4..467304bd78 100644
--- a/code/controllers/subsystems/research.dm
+++ b/code/controllers/subsystems/research.dm
@@ -2,11 +2,13 @@
SUBSYSTEM_DEF(research)
name = "Research"
// priority = FIRE_PRIORITY_RESEARCH
- init_order = INIT_ORDER_RESEARCH
wait = 10
// dependencies = list(
// /datum/controller/subsystem/processing/station,
// )
+ dependencies = list(
+ /datum/controller/subsystem/mapping
+ )
//TECHWEB STATIC
var/list/techweb_nodes = list() //associative id = node datum
var/list/techweb_designs = list() //associative id = node datum
diff --git a/code/controllers/subsystems/robot_sprites.dm b/code/controllers/subsystems/robot_sprites.dm
index fbb1ab41c1..a7f283c0e3 100644
--- a/code/controllers/subsystems/robot_sprites.dm
+++ b/code/controllers/subsystems/robot_sprites.dm
@@ -1,6 +1,8 @@
SUBSYSTEM_DEF(robot_sprites)
name = "Robot Sprites"
- init_order = INIT_ORDER_ROBOT_SPRITES
+ dependencies = list(
+ /datum/controller/subsystem/garbage
+ )
flags = SS_NO_FIRE
var/list/all_cyborg_sprites = list()
var/list/cyborg_sprites_by_module = list()
diff --git a/code/controllers/subsystems/server_maint.dm b/code/controllers/subsystems/server_maint.dm
index a6ed6e471a..0db62b2606 100644
--- a/code/controllers/subsystems/server_maint.dm
+++ b/code/controllers/subsystems/server_maint.dm
@@ -5,8 +5,7 @@ SUBSYSTEM_DEF(server_maint)
wait = 6
flags = SS_POST_FIRE_TIMING
priority = FIRE_PRIORITY_SERVER_MAINT
- init_order = INIT_ORDER_SERVER_MAINT
- //init_stage = INITSTAGE_EARLY
+ init_stage = INITSTAGE_FIRST
runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT
var/list/currentrun
///Associated list of list names to lists to clear of nulls
diff --git a/code/controllers/subsystems/shuttles.dm b/code/controllers/subsystems/shuttles.dm
index ebd4cdcc5c..ac8825feb4 100644
--- a/code/controllers/subsystems/shuttles.dm
+++ b/code/controllers/subsystems/shuttles.dm
@@ -8,7 +8,11 @@ SUBSYSTEM_DEF(shuttles)
name = "Shuttles"
wait = 2 SECONDS
priority = FIRE_PRIORITY_SHUTTLES
- init_order = INIT_ORDER_SHUTTLES
+ dependencies = list(
+ /datum/controller/subsystem/machines,
+ /datum/controller/subsystem/atoms,
+ /datum/controller/subsystem/radio
+ )
flags = SS_KEEP_TIMING|SS_NO_TICK_CHECK
runlevels = RUNLEVEL_GAME|RUNLEVEL_POSTGAME
diff --git a/code/controllers/subsystems/skybox.dm b/code/controllers/subsystems/skybox.dm
index 9692c55160..fc11fff590 100644
--- a/code/controllers/subsystems/skybox.dm
+++ b/code/controllers/subsystems/skybox.dm
@@ -2,7 +2,7 @@
//Exists to handle a few global variables that change enough to justify this. Technically a parallax, but it exhibits a skybox effect.
SUBSYSTEM_DEF(skybox)
name = "Space skybox"
- init_order = INIT_ORDER_SKYBOX
+ init_stage = INITSTAGE_LAST
flags = SS_NO_FIRE
var/static/list/skybox_cache = list()
@@ -64,7 +64,7 @@ SUBSYSTEM_DEF(skybox)
speedspace_cache["EW_[i]"] = MA
//Over-the-edge images
- for (var/dir in GLOB.alldirs)
+ for (var/dir in ALL_POSSIBLE_DIRS)
var/mutable_appearance/MA = new(normal_space)
var/matrix/M = matrix()
var/horizontal = (dir & (WEST|EAST))
@@ -93,7 +93,7 @@ SUBSYSTEM_DEF(skybox)
return SS_INIT_SUCCESS
/datum/controller/subsystem/skybox/proc/get_skybox(z)
- if(!subsystem_initialized)
+ if(!initialized)
return // WAIT
if(!skybox_cache["[z]"])
skybox_cache["[z]"] = generate_skybox(z)
diff --git a/code/controllers/subsystems/sounds.dm b/code/controllers/subsystems/sounds.dm
index 2c55777473..e2840eace8 100644
--- a/code/controllers/subsystems/sounds.dm
+++ b/code/controllers/subsystems/sounds.dm
@@ -3,7 +3,9 @@
SUBSYSTEM_DEF(sounds)
name = "Sounds"
flags = SS_NO_FIRE
- init_order = INIT_ORDER_SOUNDS
+ dependencies = list(
+ /datum/controller/subsystem/mapping
+ )
var/static/using_channels_max = CHANNEL_HIGHEST_AVAILABLE //BYOND max channels
/// Amount of channels to reserve for random usage rather than reservations being allowed to reserve all channels. Also a nice safeguard for when someone screws up.
var/static/random_channels_min = 50
diff --git a/code/controllers/subsystems/sqlite.dm b/code/controllers/subsystems/sqlite.dm
index 725c4acf39..bb2da0b118 100644
--- a/code/controllers/subsystems/sqlite.dm
+++ b/code/controllers/subsystems/sqlite.dm
@@ -3,7 +3,9 @@
// however this makes it a lot easier to test, and it is natively supported by BYOND.
SUBSYSTEM_DEF(sqlite)
name = "SQLite"
- init_order = INIT_ORDER_SQLITE
+ dependencies = list(
+ /datum/controller/subsystem/dbcore
+ )
flags = SS_NO_FIRE
var/database/sqlite_db = null
diff --git a/code/controllers/subsystems/starmover.dm b/code/controllers/subsystems/starmover.dm
index f83059e4fc..36a7678a52 100644
--- a/code/controllers/subsystems/starmover.dm
+++ b/code/controllers/subsystems/starmover.dm
@@ -3,9 +3,11 @@
//
SUBSYSTEM_DEF(starmover)
name = "Shuttle Star Movement"
- init_order = INIT_ORDER_STARMOVER
priority = FIRE_PRIORITY_STARMOVER
wait = 1 // This needs to be done pretty quickly
+ dependencies = list(
+ /datum/controller/subsystem/points_of_interest
+ )
var/list/zqueue = list()
var/list/current_movement = null
//list used to track which zlevels are being 'moved' by the proc below
diff --git a/code/controllers/subsystems/statpanel.dm b/code/controllers/subsystems/statpanel.dm
index 0639d31400..b3c5cd7d66 100644
--- a/code/controllers/subsystems/statpanel.dm
+++ b/code/controllers/subsystems/statpanel.dm
@@ -1,8 +1,6 @@
SUBSYSTEM_DEF(statpanels)
name = "Stat Panels"
wait = 4
- init_order = INIT_ORDER_STATPANELS
- //init_stage = INITSTAGE_EARLY
priority = FIRE_PRIORITY_STATPANEL
runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
flags = SS_NO_INIT
@@ -42,6 +40,15 @@ SUBSYSTEM_DEF(statpanels)
var/ETA = emergency_shuttle.get_status_panel_eta()
if(ETA)
global_data += "[ETA]"
+
+ if(SSticker.reboot_timer)
+ var/reboot_time = timeleft(SSticker.reboot_timer)
+ if(reboot_time)
+ global_data += "Reboot: [DisplayTimeText(reboot_time, 1)]"
+ // admin must have delayed round end
+ else if(SSticker.ready_for_reboot)
+ global_data += "Reboot: DELAYED"
+
src.currentrun = GLOB.clients.Copy()
mc_data = null
@@ -140,7 +147,7 @@ SUBSYSTEM_DEF(statpanels)
var/coord_entry = COORD(eye_turf)
if(!mc_data)
generate_mc_data()
- target.stat_panel.send_message("update_mc", list(mc_data = mc_data, coord_entry = coord_entry))
+ target.stat_panel.send_message("update_mc", list("mc_data" = mc_data, "coord_entry" = coord_entry))
/datum/controller/subsystem/statpanels/proc/set_examine_tab(client/target)
var/description_holders = target.description_holders
@@ -206,13 +213,31 @@ SUBSYSTEM_DEF(statpanels)
list("CPU:", world.cpu),
list("Instances:", "[num2text(world.contents.len, 10)]"),
list("World Time:", "[world.time]"),
- list("Globals:", GLOB.stat_entry(), "\ref[GLOB]"),
- list("[config]:", config.stat_entry(), "\ref[config]"),
+ list("Globals:", GLOB.stat_entry(), text_ref(GLOB)),
+ list("[config]:", config.stat_entry(), text_ref(config)),
list("Byond:", "(FPS:[world.fps]) (TickCount:[world.time/world.tick_lag]) (TickDrift:[round(Master.tickdrift,1)]([round((Master.tickdrift/(world.time/world.tick_lag))*100,0.1)]%)) (Internal Tick Usage: [round(MAPTICK_LAST_INTERNAL_TICK_USAGE,0.1)]%)"),
- list("Master Controller:", Master.stat_entry(), "\ref[Master]"),
- list("Failsafe Controller:", Failsafe.stat_entry(), "\ref[Failsafe]"),
+ list("Master Controller:", Master.stat_entry(), text_ref(Master)),
+ list("Failsafe Controller:", Failsafe.stat_entry(), text_ref(Failsafe)),
list("","")
)
+#if defined(MC_TAB_TRACY_INFO) || defined(SPACEMAN_DMM)
+ var/static/tracy_dll
+ var/static/tracy_present
+ if(isnull(tracy_dll))
+ tracy_dll = TRACY_DLL_PATH
+ tracy_present = fexists(tracy_dll)
+ if(tracy_present)
+ if(Tracy.enabled)
+ mc_data.Insert(2, list(list("byond-tracy:", "Active (reason: [Tracy.init_reason || "N/A"])")))
+ else if(Tracy.error)
+ mc_data.Insert(2, list(list("byond-tracy:", "Errored ([Tracy.error])")))
+ else if(fexists(TRACY_ENABLE_PATH))
+ mc_data.Insert(2, list(list("byond-tracy:", "Queued for next round")))
+ else
+ mc_data.Insert(2, list(list("byond-tracy:", "Inactive")))
+ else
+ mc_data.Insert(2, list(list("byond-tracy:", "[tracy_dll] not present")))
+#endif
for(var/datum/controller/subsystem/sub_system as anything in Master.subsystems)
mc_data[++mc_data.len] = list("\[[sub_system.state_letter()]][sub_system.name]", sub_system.stat_entry(), "\ref[sub_system]")
mc_data[++mc_data.len] = list("Camera Net", "Cameras: [global.cameranet.cameras.len] | Chunks: [global.cameranet.chunks.len]", "\ref[global.cameranet]")
diff --git a/code/controllers/subsystems/ticker.dm b/code/controllers/subsystems/ticker.dm
index 343cd44950..240d05f82b 100644
--- a/code/controllers/subsystems/ticker.dm
+++ b/code/controllers/subsystems/ticker.dm
@@ -1,59 +1,85 @@
-//
-// Ticker controls the state of the game, being responsible for round start, game mode, and round end.
-//
SUBSYSTEM_DEF(ticker)
- name = "Gameticker"
- wait = 2 SECONDS
- init_order = INIT_ORDER_TICKER
+ name = "Ticker"
priority = FIRE_PRIORITY_TICKER
- flags = SS_NO_TICK_CHECK | SS_KEEP_TIMING
- runlevels = RUNLEVEL_LOBBY | RUNLEVEL_SETUP | RUNLEVEL_GAME | RUNLEVEL_POSTGAME // Every runlevel!
+ flags = SS_KEEP_TIMING
+ runlevels = RUNLEVEL_LOBBY | RUNLEVEL_SETUP | RUNLEVEL_GAME
- var/const/restart_timeout = 4 MINUTES // Default time to wait before rebooting in desiseconds.
- var/current_state = GAME_STATE_INIT // We aren't even at pregame yet // TODO replace with CURRENT_GAME_STATE
+ /// state of current round (used by process()) Use the defines GAME_STATE_* !
+ var/current_state = GAME_STATE_STARTUP
+ /// Boolean to track if round should be forcibly ended next ticker tick.
+ /// Set by admin intervention ([ADMIN_FORCE_END_ROUND])
+ /// or a "round-ending" event, like summoning Nar'Sie, a blob victory, the nuke going off, etc. ([FORCE_END_ROUND])
+ var/force_ending = END_ROUND_AS_NORMAL
+ /// If TRUE, there is no lobby phase, the game starts immediately.
+ var/start_immediately = FALSE
+ /// Boolean to track and check if our subsystem setup is done.
+ var/setup_done = FALSE
- /* Relies upon the following globals (TODO move those in here) */
- // var/GLOB.master_mode = "extended" //The underlying game mode (so "secret" or the voted mode).
- // Set by SSvote when VOTE_GAMEMODE finishes.
- // var/round_progressing = 1 //Whether the lobby clock is ticking down.
+ var/hide_mode = FALSE
+ var/datum/game_mode/mode = null
- var/pregame_timeleft = 0 // Time remaining until game starts in seconds. Set by config
- var/start_immediately = FALSE // If true there is no lobby phase, the game starts immediately.
+ 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/hide_mode = FALSE // If the true game mode should be hidden (because we chose "secret")
- var/datum/game_mode/mode = null // The actual gamemode, if selected.
+ var/list/datum/mind/minds = list() //The characters in the game. Used for objective tracking.
- var/end_game_state = END_GAME_NOT_OVER // Track where we are ending game/round
- var/restart_timeleft // Time remaining until restart in desiseconds
- var/last_restart_notify // world.time of last restart warning.
- var/delay_end = FALSE // If set, the round will not restart on its own.
+ var/delay_end = FALSE //if set true, the round will not restart on it's own
+ var/admin_delay_notice = "" //a message to display to anyone who tries to restart the world after a delay
+ var/ready_for_reboot = FALSE //all roundend preparation done with, all that's left is reboot
- // var/login_music // music played in pregame lobby // VOREStation Edit - We do music differently
+ var/tipped = FALSE //Did we broadcast the tip of the day yet?
+ var/selected_tip // What will be the tip of the day?
- var/list/datum/mind/minds = list() // The people in the game. Used for objective tracking.
+ var/timeLeft //pregame timer
+ var/start_at
- var/random_players = FALSE // If set to nonzero, ALL players who latejoin or declare-ready join will have random appearances/genders
+ var/gametime_offset = 432000 //Deciseconds to add to world.time for station time.
+ var/station_time_rate_multiplier = 12 //factor of station time progressal vs real time.
- // TODO - Should this go here or in the job subsystem?
- var/triai = FALSE // Global flag for Triumvirate AI being enabled
+ /// Num of players, used for pregame stats on statpanel
+ var/totalPlayers = 0
+ /// Num of ready players, used for pregame stats on statpanel (only viewable by admins)
+ var/totalPlayersReady = 0
+ /// Num of ready admins, used for pregame stats on statpanel (only viewable by admins)
+ var/total_admins_ready = 0
- //station_explosion used to be a variable for every mob's hud. Which was a waste!
- //Now we have a general cinematic centrally held within the gameticker....far more efficient!
- var/obj/screen/cinematic = null
+ var/queue_delay = 0
+ var/list/queued_players = list() //used for join queues when the server exceeds the hard population cap
+
+ /// What is going to be reported to other stations at end of round?
+ var/news_report
+
+
+ var/roundend_check_paused = FALSE
var/round_start_time = 0
var/list/round_start_events
+ var/list/round_end_events
+ var/mode_result = "undefined"
+ var/end_state = "undefined"
+ /// People who have been commended and will receive a heart
+ var/list/hearts
+ /// Why an emergency shuttle was called
+ var/emergency_reason
-// This global variable exists for legacy support so we don't have to rename every 'ticker' to 'SSticker' yet.
-var/global/datum/controller/subsystem/ticker/ticker
-/datum/controller/subsystem/ticker/PreInit()
- global.ticker = src // TODO - Remove this! Change everything to point at SSticker intead
+ /// ID of round reboot timer, if it exists
+ var/reboot_timer = null
+
+ /// ### LEGACY VARS ###
+ /// Default time to wait before rebooting in desiseconds.
+ var/const/restart_timeout = 4 MINUTES
+ /// Track where we are ending game/round
+ var/end_game_state = END_GAME_NOT_OVER
+ /// Time remaining until restart in desiseconds
+ var/restart_timeleft
+ /// world.time of last restart warning.
+ var/last_restart_notify
/datum/controller/subsystem/ticker/Initialize()
- pregame_timeleft = CONFIG_GET(number/pregame_time)
- send2mainirc("Server lobby is loaded and open at byond://[CONFIG_GET(string/serverurl) ? CONFIG_GET(string/serverurl) : (CONFIG_GET(string/server) ? CONFIG_GET(string/server) : "[world.address]:[world.port]")]")
+ start_at = world.time + (CONFIG_GET(number/lobby_countdown) * 10)
SSwebhooks.send(
WEBHOOK_ROUNDPREP,
list(
@@ -61,63 +87,185 @@ var/global/datum/controller/subsystem/ticker/ticker
"url" = get_world_url()
)
)
- GLOB.autospeaker = new (null, FALSE, null, null, TRUE) //Set up Global Announcer
+
return SS_INIT_SUCCESS
/datum/controller/subsystem/ticker/fire(resumed = FALSE)
switch(current_state)
- if(GAME_STATE_INIT)
- pregame_welcome()
+ if(GAME_STATE_STARTUP)
+ if(Master.initializations_finished_with_no_players_logged_in)
+ start_at = world.time + (CONFIG_GET(number/lobby_countdown) * 10)
+ for(var/client/C in GLOB.clients)
+ window_flash(C, ignorepref = TRUE) //let them know lobby has opened up.
+ to_chat(world, span_notice("Welcome to [station_name()]!"))
+ //for(var/channel_tag in CONFIG_GET(str_list/channel_announce_new_game))
+ // send2chat(new /datum/tgs_message_content("New round starting on [SSmapping.current_map.map_name]!"), channel_tag)
current_state = GAME_STATE_PREGAME
+ SEND_SIGNAL(src, COMSIG_TICKER_ENTER_PREGAME)
+
+ fire()
if(GAME_STATE_PREGAME)
- pregame_tick()
+ //lobby stats for statpanels
+ if(isnull(timeLeft))
+ timeLeft = max(0,start_at - world.time)
+ totalPlayers = LAZYLEN(GLOB.new_player_list)
+ totalPlayersReady = 0
+ total_admins_ready = 0
+ for(var/mob/new_player/player as anything in GLOB.new_player_list)
+ if(player.ready == PLAYER_READY_TO_PLAY)
+ ++totalPlayersReady
+ if(player.client?.holder)
+ ++total_admins_ready
+
+ if(start_immediately)
+ timeLeft = 0
+
+ //countdown
+ if(timeLeft < 0)
+ return
+ timeLeft -= wait
+
+ //if(timeLeft <= 300 && !tipped)
+ // send_tip_of_the_round(world, selected_tip)
+ // tipped = TRUE
+
+ if(timeLeft <= 0)
+ SEND_SIGNAL(src, COMSIG_TICKER_ENTER_SETTING_UP)
+ current_state = GAME_STATE_SETTING_UP
+ Master.SetRunLevel(RUNLEVEL_SETUP)
+ if(start_immediately)
+ fire()
+
if(GAME_STATE_SETTING_UP)
- setup_tick()
+ if(!setup())
+ //setup failed
+ current_state = GAME_STATE_STARTUP
+ start_at = world.time + (CONFIG_GET(number/lobby_countdown) * 10)
+ timeLeft = null
+ Master.SetRunLevel(RUNLEVEL_LOBBY)
+ SEND_SIGNAL(src, COMSIG_TICKER_ERROR_SETTING_UP)
+
if(GAME_STATE_PLAYING)
- playing_tick()
+ mode.process() // So THIS is where we run mode.process() huh? Okay
+
+ if(mode.explosion_in_progress)
+ return // wait until explosion is done.
+
+ if(force_ending)
+ current_state = GAME_STATE_FINISHED
+ declare_completion(force_ending)
+ Master.SetRunLevel(RUNLEVEL_POSTGAME)
+ else
+ // Calculate if game and/or mode are finished (Complicated by the continuous_rounds config option)
+ var/game_finished = FALSE
+ var/mode_finished = FALSE
+ if (CONFIG_GET(flag/continuous_rounds)) // Game keeps going after mode ends.
+ game_finished = (emergency_shuttle.returned() || mode.station_was_nuked)
+ mode_finished = ((end_game_state >= END_GAME_MODE_FINISHED) || mode.check_finished()) // Short circuit if already finished.
+ else // Game ends when mode does
+ game_finished = (mode.check_finished() || (emergency_shuttle.returned() && emergency_shuttle.evac == 1)) || GLOB.universe_has_ended
+ mode_finished = game_finished
+
+ if(game_finished && mode_finished)
+ end_game_state = END_GAME_READY_TO_END
+ current_state = GAME_STATE_FINISHED
+ Master.SetRunLevel(RUNLEVEL_POSTGAME)
+ INVOKE_ASYNC(src, PROC_REF(declare_completion))
+ else if (mode_finished && (end_game_state < END_GAME_MODE_FINISHED))
+ end_game_state = END_GAME_MODE_FINISHED // Only do this cleanup once!
+ mode.cleanup()
+ //call a transfer shuttle vote
+ to_world(span_boldannounce("The round has ended!"))
+ SSvote.start_vote(new /datum/vote/crew_transfer)
+
+ // FIXME: IMPROVE THIS LATER!
if(GAME_STATE_FINISHED)
post_game_tick()
-/datum/controller/subsystem/ticker/proc/pregame_welcome()
- to_world(span_boldannounce(span_notice("Welcome to the pregame lobby!")))
- to_world(span_boldannounce(span_notice("Please set up your character and select ready. The round will start in [pregame_timeleft] seconds.")))
- world << sound('sound/misc/server-ready.ogg', volume = 100)
+ if (world.time - last_restart_notify >= 1 MINUTE && !delay_end)
+ to_world(span_boldannounce("Restarting in [round(restart_timeleft/600, 1)] minute\s."))
+ last_restart_notify = world.time
-// Called during GAME_STATE_PREGAME (RUNLEVEL_LOBBY)
-/datum/controller/subsystem/ticker/proc/pregame_tick()
- if(GLOB.round_progressing && last_fire)
- pregame_timeleft -= (world.time - last_fire) / (1 SECOND)
+/datum/controller/subsystem/ticker/proc/setup()
+ to_chat(world, span_boldannounce("Starting game..."))
+ var/init_start = world.timeofday
- if(start_immediately)
- pregame_timeleft = 0
- else if(SSvote.active_vote)
- return // vote still going, wait for it.
+ CHECK_TICK
+ setup_choose_gamemode()
+ // TODO
- // Time to start the game!
- if(pregame_timeleft <= 0)
- current_state = GAME_STATE_SETTING_UP
- Master.SetRunLevel(RUNLEVEL_SETUP)
- if(start_immediately)
- fire() // Don't wait for next tick, do it now!
- return
+ CHECK_TICK
+ setup_economy()
+ create_characters() //Create player characters
+ collect_minds()
+ equip_characters()
- //if(pregame_timeleft <= config.vote_autogamemode_timeleft && !SSvote.gamemode_vote_called)
- //.autogamemode() // Start the game mode vote (if we haven't had one already)
+ // data_core.manifest()
-// Called during GAME_STATE_SETTING_UP (RUNLEVEL_SETUP)
-/datum/controller/subsystem/ticker/proc/setup_tick(resumed = FALSE)
- round_start_time = world.time // otherwise round_start_time would be 0 for the signals
- if(!setup_choose_gamemode())
- // It failed, go back to lobby state and re-send the welcome message
- pregame_timeleft = CONFIG_GET(number/pregame_time)
- // SSvote.gamemode_vote_called = FALSE // Allow another autogamemode vote
- current_state = GAME_STATE_PREGAME
- Master.SetRunLevel(RUNLEVEL_LOBBY)
- pregame_welcome()
- return
- // If we got this far we succeeded in picking a game mode. Punch it!
- setup_startgame()
- return
+ for(var/I in round_start_events)
+ var/datum/callback/cb = I
+ cb.InvokeAsync()
+ LAZYCLEARLIST(round_start_events)
+
+ round_start_time = world.time //otherwise round_start_time would be 0 for the signals
+ SEND_SIGNAL(src, COMSIG_TICKER_ROUND_STARTING, world.time)
+ callHook("roundstart")
+
+ log_world("Game start took [(world.timeofday - init_start)/10]s")
+ INVOKE_ASYNC(SSdbcore, TYPE_PROC_REF(/datum/controller/subsystem/dbcore,SetRoundStart))
+
+ to_chat(world, span_notice(span_bold("Welcome to [station_name()], enjoy your stay!")))
+ world << sound('sound/AI/welcome.ogg') // Skie
+ //SEND_SOUND(world, sound(SSstation.announcer.get_rand_welcome_sound()))
+
+ current_state = GAME_STATE_PLAYING
+ Master.SetRunLevel(RUNLEVEL_GAME)
+
+ //Holiday Round-start stuff ~Carn
+ Holiday_Game_Start()
+
+ // TODO END
+
+ PostSetup()
+
+ return TRUE
+
+/datum/controller/subsystem/ticker/proc/PostSetup()
+ set waitfor = FALSE
+ mode.post_setup()
+ // TODO
+
+ var/list/adm = get_admin_counts()
+ var/list/allmins = adm["present"]
+ // TODO: IMPLEMENT: send2adminchat("Server", "Round [GLOB.round_id ? "#[GLOB.round_id]" : ""] has started[allmins.len ? ".":" with no active admins online!"]")
+ if(!allmins.len)
+ send2adminirc("A round has started with no admins online.")
+
+ setup_done = TRUE
+ // TODO START
+
+ // TODO END
+ for(var/obj/effect/landmark/start/S in GLOB.landmarks_list)
+ //Deleting Startpoints but we need the ai point to AI-ize people later
+ if (S.name != "AI")
+ qdel(S)
+
+ if(CONFIG_GET(flag/sql_enabled))
+ statistic_cycle() // Polls population totals regularly and stores them in an SQL DB -- TLE
+
+//These callbacks will fire after roundstart key transfer
+/datum/controller/subsystem/ticker/proc/OnRoundstart(datum/callback/cb)
+ if(!HasRoundStarted())
+ LAZYADD(round_start_events, cb)
+ else
+ cb.InvokeAsync()
+
+//These callbacks will fire before roundend report
+/datum/controller/subsystem/ticker/proc/OnRoundend(datum/callback/cb)
+ if(current_state >= GAME_STATE_FINISHED)
+ cb.InvokeAsync()
+ else
+ LAZYADD(round_end_events, cb)
// Formerly the first half of setup() - The part that chooses the game mode.
// Returns 0 if failed to pick a mode, otherwise 1
@@ -170,74 +318,6 @@ var/global/datum/controller/subsystem/ticker/ticker
src.mode.announce()
return 1
-// Formerly the second half of setup() - The part that actually initializes everything and starts the game.
-/datum/controller/subsystem/ticker/proc/setup_startgame()
- setup_economy()
- create_characters() //Create player characters and transfer them.
- collect_minds()
- equip_characters()
-// data_core.manifest()
-
- callHook("roundstart")
- for(var/I in round_start_events)
- var/datum/callback/cb = I
- cb.InvokeAsync()
- LAZYCLEARLIST(round_start_events)
-
- spawn(0)//Forking here so we dont have to wait for this to finish
- mode.post_setup()
- //Cleanup some stuff
- for(var/obj/effect/landmark/start/S in GLOB.landmarks_list)
- //Deleting Startpoints but we need the ai point to AI-ize people later
- if (S.name != "AI")
- qdel(S)
- to_world(span_boldannounce(span_notice("Enjoy the game!")))
- world << sound('sound/AI/welcome.ogg') // Skie
- //Holiday Round-start stuff ~Carn
- Holiday_Game_Start()
-
- var/list/adm = get_admin_counts()
- if(adm["total"] == 0)
- send2adminirc("A round has started with no admins online.")
-
- current_state = GAME_STATE_PLAYING
- Master.SetRunLevel(RUNLEVEL_GAME)
-
- if(CONFIG_GET(flag/sql_enabled))
- statistic_cycle() // Polls population totals regularly and stores them in an SQL DB -- TLE
-
- return 1
-
-
-// Called during GAME_STATE_PLAYING (RUNLEVEL_GAME)
-/datum/controller/subsystem/ticker/proc/playing_tick(resumed = FALSE)
- mode.process() // So THIS is where we run mode.process() huh? Okay
-
- if(mode.explosion_in_progress)
- return // wait until explosion is done.
-
- // Calculate if game and/or mode are finished (Complicated by the continuous_rounds config option)
- var/game_finished = FALSE
- var/mode_finished = FALSE
- if (CONFIG_GET(flag/continuous_rounds)) // Game keeps going after mode ends.
- game_finished = (emergency_shuttle.returned() || mode.station_was_nuked)
- mode_finished = ((end_game_state >= END_GAME_MODE_FINISHED) || mode.check_finished()) // Short circuit if already finished.
- else // Game ends when mode does
- game_finished = (mode.check_finished() || (emergency_shuttle.returned() && emergency_shuttle.evac == 1)) || GLOB.universe_has_ended
- mode_finished = game_finished
-
- if(game_finished && mode_finished)
- end_game_state = END_GAME_READY_TO_END
- current_state = GAME_STATE_FINISHED
- Master.SetRunLevel(RUNLEVEL_POSTGAME)
- INVOKE_ASYNC(src, PROC_REF(declare_completion))
- else if (mode_finished && (end_game_state < END_GAME_MODE_FINISHED))
- end_game_state = END_GAME_MODE_FINISHED // Only do this cleanup once!
- mode.cleanup()
- //call a transfer shuttle vote
- to_world(span_boldannounce("The round has ended!"))
- SSvote.start_vote(new /datum/vote/crew_transfer)
-
// Called during GAME_STATE_FINISHED (RUNLEVEL_POSTGAME)
/datum/controller/subsystem/ticker/proc/post_game_tick()
switch(end_game_state)
@@ -259,148 +339,6 @@ var/global/datum/controller/subsystem/ticker/ticker
end_game_state = END_GAME_ENDING
return
- if(END_GAME_ENDING)
- restart_timeleft -= (world.time - last_fire)
- if(delay_end)
- to_world(span_boldannounce("An admin has delayed the round end."))
- end_game_state = END_GAME_DELAYED
- else if(restart_timeleft <= 0)
- to_world(span_boldannounce("Restarting world!"))
- sleep(5)
- world.Reboot()
- else if (world.time - last_restart_notify >= 1 MINUTE)
- to_world(span_boldannounce("Restarting in [round(restart_timeleft/600, 1)] minute\s."))
- last_restart_notify = world.time
- return
- if(END_GAME_DELAYED)
- restart_timeleft -= (world.time - last_fire)
- if(!delay_end)
- end_game_state = END_GAME_ENDING
- else
- log_error("Ticker arrived at round end in an unexpected endgame state '[end_game_state]'.")
- end_game_state = END_GAME_READY_TO_END
-
-
-// ----------------------------------------------------------------------
-// These two below are not used! But they could be
-
-// Use these preferentially to directly examining ticker.current_state to help prepare for transition to ticker as subsystem!
-
-/datum/controller/subsystem/ticker/proc/PreRoundStart()
- return (current_state < GAME_STATE_PLAYING)
-
-/datum/controller/subsystem/ticker/proc/IsSettingUp()
- return (current_state == GAME_STATE_SETTING_UP)
-
-/datum/controller/subsystem/ticker/proc/IsRoundInProgress()
- return (current_state == GAME_STATE_PLAYING)
-
-/datum/controller/subsystem/ticker/proc/HasRoundStarted()
- return (current_state >= GAME_STATE_PLAYING)
-
-// ------------------------------------------------------------------------
-// HELPER PROCS!
-// ------------------------------------------------------------------------
-
-//Plus it provides an easy way to make cinematics for other events. Just use this as a template :)
-/datum/controller/subsystem/ticker/proc/station_explosion_cinematic(var/station_missed=0, var/override = null)
- if( cinematic ) return //already a cinematic in progress!
-
- //initialise our cinematic screen object
- cinematic = new(src)
- cinematic.icon = 'icons/effects/station_explosion.dmi'
- cinematic.icon_state = "station_intact"
- cinematic.layer = 100
- cinematic.plane = PLANE_PLAYER_HUD
- cinematic.mouse_opacity = 0
- cinematic.screen_loc = "1,0"
-
- var/obj/structure/bed/temp_buckle = new(src)
- //Incredibly hackish. It creates a bed within the gameticker (lol) to stop mobs running around
- if(station_missed)
- for(var/mob/living/M in GLOB.living_mob_list)
- M.buckled = temp_buckle //buckles the mob so it can't do anything
- if(M.client)
- M.client.screen += cinematic //show every client the cinematic
- else //nuke kills everyone on z-level 1 to prevent "hurr-durr I survived"
- for(var/mob/living/M in GLOB.living_mob_list)
- M.buckled = temp_buckle
- if(M.client)
- M.client.screen += cinematic
-
- switch(M.z)
- if(0) //inside a crate or something
- var/turf/T = get_turf(M)
- if(T && (T.z in using_map.station_levels)) //we don't use M.death(0) because it calls a for(/mob) loop and
- M.health = 0
- M.set_stat(DEAD)
- if(1) //on a z-level 1 turf.
- M.health = 0
- M.set_stat(DEAD)
-
- //Now animate the cinematic
- switch(station_missed)
- if(1) //nuke was nearby but (mostly) missed
- if( mode && !override )
- override = mode.name
- switch( override )
- if("mercenary") //Nuke wasn't on station when it blew up
- flick("intro_nuke",cinematic)
- sleep(35)
- world << sound('sound/effects/explosionfar.ogg')
- flick("station_intact_fade_red",cinematic)
- cinematic.icon_state = "summary_nukefail"
- else
- flick("intro_nuke",cinematic)
- sleep(35)
- world << sound('sound/effects/explosionfar.ogg')
- //flick("end",cinematic)
-
-
- if(2) //nuke was nowhere nearby //TODO: a really distant explosion animation
- sleep(50)
- world << sound('sound/effects/explosionfar.ogg')
-
-
- else //station was destroyed
- if( mode && !override )
- override = mode.name
- switch( override )
- if("mercenary") //Nuke Ops successfully bombed the station
- flick("intro_nuke",cinematic)
- sleep(35)
- flick("station_explode_fade_red",cinematic)
- world << sound('sound/effects/explosionfar.ogg')
- cinematic.icon_state = "summary_nukewin"
- if("AI malfunction") //Malf (screen,explosion,summary)
- flick("intro_malf",cinematic)
- sleep(76)
- flick("station_explode_fade_red",cinematic)
- world << sound('sound/effects/explosionfar.ogg')
- cinematic.icon_state = "summary_malf"
- if("blob") //Station nuked (nuke,explosion,summary)
- flick("intro_nuke",cinematic)
- sleep(35)
- flick("station_explode_fade_red",cinematic)
- world << sound('sound/effects/explosionfar.ogg')
- cinematic.icon_state = "summary_selfdes"
- else //Station nuked (nuke,explosion,summary)
- flick("intro_nuke",cinematic)
- sleep(35)
- flick("station_explode_fade_red", cinematic)
- world << sound('sound/effects/explosionfar.ogg')
- cinematic.icon_state = "summary_selfdes"
- for(var/mob/living/M in GLOB.living_mob_list)
- if(M.loc.z in using_map.station_levels)
- M.death()//No mercy
- //If its actually the end of the round, wait for it to end.
- //Otherwise if its a verb it will continue on afterwards.
- sleep(300)
-
- if(cinematic) qdel(cinematic) //end the cinematic
- if(temp_buckle) qdel(temp_buckle) //release everybody
- return
-
/datum/controller/subsystem/ticker/proc/create_characters()
for(var/mob/new_player/player in GLOB.player_list)
@@ -431,12 +369,13 @@ var/global/datum/controller/subsystem/ticker/ticker
// If they're a carbon, they can get manifested
if(J?.mob_type & JOB_CARBON)
GLOB.data_core.manifest_inject(new_char)
+ CHECK_TICK
/datum/controller/subsystem/ticker/proc/collect_minds()
for(var/mob/living/player in GLOB.player_list)
if(player.mind)
minds += player.mind
-
+ CHECK_TICK
/datum/controller/subsystem/ticker/proc/equip_characters()
var/captainless=1
@@ -457,147 +396,107 @@ var/global/datum/controller/subsystem/ticker/ticker
if(imp.handle_implant(player,player.zone_sel.selecting))
imp.post_implant(player)
//VOREStation Addition End
+ CHECK_TICK
if(captainless)
for(var/mob/M in GLOB.player_list)
if(!isnewplayer(M))
to_chat(M, span_notice("Site Management is not forced on anyone."))
+///Whether the game has started, including roundend.
+/datum/controller/subsystem/ticker/proc/HasRoundStarted()
+ return current_state >= GAME_STATE_PLAYING
-/datum/controller/subsystem/ticker/proc/declare_completion()
- to_world(span_filter_system("
A round of [mode.name] has ended!
"))
- for(var/mob/Player in GLOB.player_list)
- if(Player.mind && !isnewplayer(Player))
- if(Player.stat != DEAD)
- var/turf/playerTurf = get_turf(Player)
- if(emergency_shuttle.departed && emergency_shuttle.evac)
- if(isNotAdminLevel(playerTurf.z))
- to_chat(Player, span_filter_system(span_blue(span_bold("You survived the round, but remained on [station_name()] as [Player.real_name]."))))
- else
- to_chat(Player, span_filter_system(span_green(span_bold("You managed to survive the events on [station_name()] as [Player.real_name]."))))
- else if(isAdminLevel(playerTurf.z))
- to_chat(Player, span_filter_system(span_green(span_bold("You successfully underwent crew transfer after events on [station_name()] as [Player.real_name]."))))
- else if(issilicon(Player))
- to_chat(Player, span_filter_system(span_green(span_bold("You remain operational after the events on [station_name()] as [Player.real_name]."))))
- else
- to_chat(Player, span_filter_system(span_blue(span_bold("You missed the crew transfer after the events on [station_name()] as [Player.real_name]."))))
- else
- if(isobserver(Player))
- var/mob/observer/dead/O = Player
- if(!O.started_as_observer)
- to_chat(Player, span_filter_system(span_red(span_bold("You did not survive the events on [station_name()]..."))))
- else
- to_chat(Player, span_filter_system(span_red(span_bold("You did not survive the events on [station_name()]..."))))
- to_world(span_filter_system(" "))
+///Whether the game is currently in progress, excluding roundend
+/datum/controller/subsystem/ticker/proc/IsRoundInProgress()
+ return current_state == GAME_STATE_PLAYING
- for (var/mob/living/silicon/ai/aiPlayer in GLOB.mob_list)
- if (aiPlayer.stat != 2)
- to_world(span_filter_system(span_bold("[aiPlayer.name]'s laws at the end of the round were:"))) // VOREStation edit
- else
- to_world(span_filter_system(span_bold("[aiPlayer.name]'s laws when it was deactivated were:"))) // VOREStation edit
- aiPlayer.show_laws(1)
-
- if (aiPlayer.connected_robots.len)
- var/robolist = span_bold("The AI's loyal minions were:") + " "
- for(var/mob/living/silicon/robot/robo in aiPlayer.connected_robots)
- robolist += "[robo.name][robo.stat?" (Deactivated), ":", "]" // VOREStation edit
- to_world(span_filter_system("[robolist]"))
-
- var/dronecount = 0
-
- for (var/mob/living/silicon/robot/robo in GLOB.mob_list)
-
- if(istype(robo, /mob/living/silicon/robot/platform))
- var/mob/living/silicon/robot/platform/tank = robo
- if(!tank.has_had_player)
- continue
-
- if(istype(robo,/mob/living/silicon/robot/drone) && !istype(robo,/mob/living/silicon/robot/drone/swarm))
- dronecount++
- continue
-
- if (!robo.connected_ai)
- if (robo.stat != 2)
- to_world(span_filter_system(span_bold("[robo.name] survived as an AI-less stationbound synthetic! Its laws were:"))) // VOREStation edit
- else
- to_world(span_filter_system(span_bold("[robo.name] was unable to survive the rigors of being a stationbound synthetic without an AI. Its laws were:"))) // VOREStation edit
-
- if(robo) //How the hell do we lose robo between here and the world messages directly above this?
- robo.laws.show_laws(world)
-
- if(dronecount)
- to_world(span_filter_system(span_bold("There [dronecount>1 ? "were" : "was"] [dronecount] industrious maintenance [dronecount>1 ? "drones" : "drone"] at the end of this round.")))
-
- mode.declare_completion()//To declare normal completion.
-
- //Ask the event manager to print round end information
- SSevents.RoundEnd()
-
- //Print a list of antagonists to the server log
- var/list/total_antagonists = list()
- //Look into all mobs in world, dead or alive
- for(var/datum/mind/Mind in minds)
- var/temprole = Mind.special_role
- if(temprole) //if they are an antagonist of some sort.
- if(temprole in total_antagonists) //If the role exists already, add the name to it
- total_antagonists[temprole] += ", [Mind.name]([Mind.key])"
- else
- total_antagonists.Add(temprole) //If the role doesnt exist in the list, create it and add the mob
- total_antagonists[temprole] += ": [Mind.name]([Mind.key])"
-
- //Now print them all into the log!
- log_game("Antagonists at round end were...")
- for(var/i in total_antagonists)
- log_game("[i]s[total_antagonists[i]].")
-
- SSdbcore.SetRoundEnd()
-
- return 1
-
-/datum/controller/subsystem/ticker/stat_entry(msg)
- switch(current_state)
- if(GAME_STATE_INIT)
- ..()
- if(GAME_STATE_PREGAME) // RUNLEVEL_LOBBY
- msg = "START [GLOB.round_progressing ? "[round(pregame_timeleft)]s" : "(PAUSED)"]"
- if(GAME_STATE_SETTING_UP) // RUNLEVEL_SETUP
- msg = "SETUP"
- if(GAME_STATE_PLAYING) // RUNLEVEL_GAME
- msg = "GAME"
- if(GAME_STATE_FINISHED) // RUNLEVEL_POSTGAME
- switch(end_game_state)
- if(END_GAME_MODE_FINISHED)
- msg = "MODE OVER, WAITING"
- if(END_GAME_READY_TO_END)
- msg = "ENDGAME PROCESSING"
- if(END_GAME_ENDING)
- msg = "END IN [round(restart_timeleft/10)]s"
- if(END_GAME_DELAYED)
- msg = "END PAUSED"
- else
- msg = "ENDGAME ERROR:[end_game_state]"
- return ..()
+///Whether the game is currently in progress, excluding roundend
+/datum/controller/subsystem/ticker/proc/IsPostgame()
+ return current_state == GAME_STATE_FINISHED
/datum/controller/subsystem/ticker/Recover()
- flags |= SS_NO_INIT // Don't initialize again
-
current_state = SSticker.current_state
- mode = SSticker.mode
- pregame_timeleft = SSticker.pregame_timeleft
+ force_ending = SSticker.force_ending
- end_game_state = SSticker.end_game_state
- delay_end = SSticker.delay_end
- restart_timeleft = SSticker.restart_timeleft
+ login_music = SSticker.login_music
+ round_end_sound = SSticker.round_end_sound
minds = SSticker.minds
- random_players = SSticker.random_players
+ delay_end = SSticker.delay_end
+ tipped = SSticker.tipped
+ selected_tip = SSticker.selected_tip
+
+ timeLeft = SSticker.timeLeft
+
+ totalPlayers = SSticker.totalPlayers
+ totalPlayersReady = SSticker.totalPlayersReady
+ total_admins_ready = SSticker.total_admins_ready
+
+ queue_delay = SSticker.queue_delay
+ queued_players = SSticker.queued_players
round_start_time = SSticker.round_start_time
+ queue_delay = SSticker.queue_delay
+ queued_players = SSticker.queued_players
-//These callbacks will fire after roundstart key transfer
-/datum/controller/subsystem/ticker/proc/OnRoundstart(datum/callback/cb)
- if(!HasRoundStarted())
- LAZYADD(round_start_events, cb)
- else
- cb.InvokeAsync()
+ if (Master) //Set Masters run level if it exists
+ switch (current_state)
+ if(GAME_STATE_SETTING_UP)
+ Master.SetRunLevel(RUNLEVEL_SETUP)
+ if(GAME_STATE_PLAYING)
+ Master.SetRunLevel(RUNLEVEL_GAME)
+ if(GAME_STATE_FINISHED)
+ Master.SetRunLevel(RUNLEVEL_POSTGAME)
+
+/datum/controller/subsystem/ticker/proc/Reboot(reason, end_string, delay)
+ set waitfor = FALSE
+ if(usr && !check_rights(R_SERVER, TRUE))
+ return
+
+ if(!delay)
+ delay = CONFIG_GET(number/round_end_countdown) * 10
+
+ var/skip_delay = check_rights()
+ if(delay_end && !skip_delay)
+ to_chat(world, span_boldannounce("An admin has delayed the round end."))
+ return
+
+ to_chat(world, span_boldannounce("Rebooting World in [DisplayTimeText(delay)]. [reason]"))
+
+ // We dont have those
+ //var/statspage = CONFIG_GET(string/roundstatsurl)
+ //var/gamelogloc = CONFIG_GET(string/gamelogurl)
+ //if(statspage)
+ // to_chat(world, span_info("Round statistics and logs can be viewed at this website!"))
+ //else if(gamelogloc)
+ // to_chat(world, span_info("Round logs can be located at this website!"))
+
+ var/start_wait = world.time
+ UNTIL(round_end_sound_sent || (world.time - start_wait) > (delay * 2)) //don't wait forever
+ reboot_timer = addtimer(CALLBACK(src, PROC_REF(reboot_callback), reason, end_string), delay - (world.time - start_wait), TIMER_STOPPABLE)
+
+
+/datum/controller/subsystem/ticker/proc/reboot_callback(reason, end_string)
+ if(end_string)
+ end_state = end_string
+
+ log_game(span_boldannounce("Rebooting World. [reason]"))
+
+ world.Reboot()
+
+/**
+ * Deletes the current reboot timer and nulls the var
+ *
+ * Arguments:
+ * * user - the user that cancelled the reboot, may be null
+ */
+/datum/controller/subsystem/ticker/proc/cancel_reboot(mob/user)
+ if(!reboot_timer)
+ to_chat(user, span_warning("There is no pending reboot!"))
+ return FALSE
+ to_chat(world, span_boldannounce("An admin has delayed the round end."))
+ deltimer(reboot_timer)
+ reboot_timer = null
+ return TRUE
diff --git a/code/controllers/subsystems/timer.dm b/code/controllers/subsystems/timer.dm
index c314fa7d38..a90bcc334b 100644
--- a/code/controllers/subsystems/timer.dm
+++ b/code/controllers/subsystems/timer.dm
@@ -17,9 +17,11 @@
SUBSYSTEM_DEF(timer)
name = "Timer"
wait = 1 // SS_TICKER subsystem, so wait is in ticks
- init_order = INIT_ORDER_TIMER
priority = FIRE_PRIORITY_TIMER
flags = SS_TICKER|SS_NO_INIT
+ dependencies = list(
+ /datum/controller/subsystem/machines
+ )
/// Queue used for storing timers that do not fit into the current buckets
var/list/datum/timedevent/second_queue = list()
diff --git a/code/controllers/subsystems/transcore_vr.dm b/code/controllers/subsystems/transcore_vr.dm
index 52e10970b2..23948787ea 100644
--- a/code/controllers/subsystems/transcore_vr.dm
+++ b/code/controllers/subsystems/transcore_vr.dm
@@ -12,7 +12,9 @@ SUBSYSTEM_DEF(transcore)
wait = 3 MINUTES
flags = SS_BACKGROUND
runlevels = RUNLEVEL_GAME
- init_order = INIT_ORDER_TRANSCORE
+ dependencies = list(
+ /datum/controller/subsystem/mapping
+ )
// THINGS
var/overdue_time = 6 MINUTES // Has to be a multiple of wait var, or else will just round up anyway.
diff --git a/code/controllers/subsystems/verb_manager.dm b/code/controllers/subsystems/verb_manager.dm
index 8e8c4cd3bd..f09c050964 100644
--- a/code/controllers/subsystems/verb_manager.dm
+++ b/code/controllers/subsystems/verb_manager.dm
@@ -78,7 +78,7 @@ SUBSYSTEM_DEF(verb_manager)
//we want unit tests to be able to directly call verbs that attempt to queue, and since unit tests should test internal behavior, we want the queue
//to happen as if it was actually from player input if its called on a mob.
-#ifdef UNIT_TEST
+#ifdef UNIT_TESTS
if(QDELETED(usr) && ismob(incoming_callback.object))
incoming_callback.user = WEAKREF(incoming_callback.object)
var/datum/callback/new_us = CALLBACK(arglist(list(GLOBAL_PROC, GLOBAL_PROC_REF(_queue_verb)) + args.Copy()))
@@ -120,7 +120,7 @@ SUBSYSTEM_DEF(verb_manager)
return TRUE
if((usr.client?.holder && !can_queue_admin_verbs) \
- || (!subsystem_initialized && !(flags & SS_NO_INIT)) \
+ || (!initialized && !(flags & SS_NO_INIT)) \
|| FOR_ADMINS_IF_VERBS_FUCKED_immediately_execute_all_verbs \
|| !(runlevels & Master.current_runlevel))
return FALSE
diff --git a/code/controllers/subsystems/vis_overlays.dm b/code/controllers/subsystems/vis_overlays.dm
index 61cb58170a..372dd4b306 100644
--- a/code/controllers/subsystems/vis_overlays.dm
+++ b/code/controllers/subsystems/vis_overlays.dm
@@ -2,7 +2,6 @@ SUBSYSTEM_DEF(vis_overlays)
name = "Vis contents overlays"
wait = 1 MINUTES
priority = FIRE_PRIORITY_VIS
- init_order = INIT_ORDER_VIS
var/list/vis_overlay_cache
var/list/currentrun
diff --git a/code/controllers/subsystems/vote.dm b/code/controllers/subsystems/vote.dm
index b593026cf2..d462073c80 100644
--- a/code/controllers/subsystems/vote.dm
+++ b/code/controllers/subsystems/vote.dm
@@ -24,7 +24,7 @@ SUBSYSTEM_DEF(vote)
/datum/controller/subsystem/vote/fire(resumed)
if(mode)
time_remaining = round((started_time + duration - world.time)/10)
- if(mode == VOTE_GAMEMODE && ticker.current_state >= GAME_STATE_SETTING_UP)
+ if(mode == VOTE_GAMEMODE && SSticker.current_state >= GAME_STATE_SETTING_UP)
to_chat(world, span_bold("Gamemode vote aborted: Game has already started."))
reset()
return
@@ -151,7 +151,7 @@ SUBSYSTEM_DEF(vote)
if(VOTE_GAMEMODE)
if(GLOB.master_mode != .)
world.save_mode(.)
- if(ticker && ticker.mode)
+ if(SSticker && SSticker.mode)
restart = 1
else
GLOB.master_mode = .
@@ -203,7 +203,7 @@ SUBSYSTEM_DEF(vote)
if(VOTE_RESTART)
choices.Add("Restart Round", "Continue Playing")
if(VOTE_GAMEMODE)
- if(ticker.current_state >= GAME_STATE_SETTING_UP)
+ if(SSticker.current_state >= GAME_STATE_SETTING_UP)
return 0
choices.Add(config.votable_modes)
for(var/F in choices)
@@ -218,13 +218,13 @@ SUBSYSTEM_DEF(vote)
if(get_security_level() == "red" || get_security_level() == "delta")
to_chat(initiator_key, "The current alert status is too high to call for a crew transfer!")
return 0
- if(ticker.current_state <= GAME_STATE_SETTING_UP)
+ if(SSticker.current_state <= GAME_STATE_SETTING_UP)
to_chat(initiator_key, "The crew transfer button has been disabled!")
return 0
question = "Your PDA beeps with a message from Central. Would you like an additional hour to finish ongoing projects?" //VOREStation Edit
choices.Add("Initiate Crew Transfer", "Extend the Shift") //VOREStation Edit
if(VOTE_ADD_ANTAGONIST)
- if(!config.allow_extra_antags || ticker.current_state >= GAME_STATE_SETTING_UP)
+ if(!config.allow_extra_antags || SSticker.current_state >= GAME_STATE_SETTING_UP)
return 0
for(var/antag_type in all_antag_types)
var/datum/antagonist/antag = all_antag_types[antag_type]
diff --git a/code/controllers/subsystems/webhooks.dm b/code/controllers/subsystems/webhooks.dm
index 3a8031b4ea..17a12f6f77 100644
--- a/code/controllers/subsystems/webhooks.dm
+++ b/code/controllers/subsystems/webhooks.dm
@@ -1,6 +1,8 @@
SUBSYSTEM_DEF(webhooks)
name = "Webhooks"
- init_order = INIT_ORDER_WEBHOOKS
+ dependencies = list(
+ /datum/controller/subsystem/server_maint,
+ )
flags = SS_NO_FIRE
var/list/webhook_decls = list()
@@ -63,7 +65,7 @@ SUBSYSTEM_DEF(webhooks)
if(!check_rights_for(src, R_HOLDER))
return
- if(!SSwebhooks.subsystem_initialized)
+ if(!SSwebhooks.initialized)
to_chat(usr, span_warning("Let the webhook subsystem initialize before trying to reload it."))
return
diff --git a/code/controllers/subsystems/xenoarch.dm b/code/controllers/subsystems/xenoarch.dm
index 1711af7407..fecc64906b 100644
--- a/code/controllers/subsystems/xenoarch.dm
+++ b/code/controllers/subsystems/xenoarch.dm
@@ -14,8 +14,8 @@
//
SUBSYSTEM_DEF(xenoarch)
name = "Xenoarch"
- init_order = INIT_ORDER_XENOARCH
flags = SS_NO_FIRE
+ init_stage = INITSTAGE_LAST
var/list/artifact_spawning_turfs = list()
var/list/digsite_spawning_turfs = list()
diff --git a/code/datums/cinematics/_cinematic.dm b/code/datums/cinematics/_cinematic.dm
new file mode 100644
index 0000000000..6a49548f1f
--- /dev/null
+++ b/code/datums/cinematics/_cinematic.dm
@@ -0,0 +1,181 @@
+#define CINEMATIC_SOURCE "cinematic"
+
+/**
+ * Plays a cinematic, duh. Can be to a select few people, or everyone.
+ *
+ * cinematic_type - datum typepath to what cinematic you wish to play.
+ * watchers - a list of all mobs you are playing the cinematic to. If world, the cinematical will play globally to all players.
+ * special_callback - optional callback to be invoked mid-cinematic.
+ */
+/proc/play_cinematic(datum/cinematic/cinematic_type, watchers, datum/callback/special_callback)
+ if(!ispath(cinematic_type, /datum/cinematic))
+ CRASH("play_cinematic called with a non-cinematic type. (Got: [cinematic_type])")
+ var/datum/cinematic/playing = new cinematic_type(watchers, special_callback)
+
+ if(watchers == world)
+ watchers = GLOB.mob_list
+
+ playing.start_cinematic(watchers)
+
+ return playing
+
+/// The cinematic screen showed to everyone
+/atom/movable/screen/cinematic
+ icon = 'icons/effects/station_explosion.dmi'
+ icon_state = "station_intact"
+ plane = SPLASHSCREEN_PLANE
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ screen_loc = "BOTTOM,LEFT+50%"
+ appearance_flags = APPEARANCE_UI | TILE_BOUND
+
+/// Cinematic datum. Used to show an animation to everyone.
+/datum/cinematic
+ /// A list of all clients watching the cinematic
+ var/list/client/watching = list()
+ /// A list of all mobs who have TRAIT_NO_TRANSFORM set while watching the cinematic
+ var/list/datum/weakref/locked = list()
+ /// Whether the cinematic is a global cinematic or not
+ var/is_global = FALSE
+ /// Refernce to the cinematic screen shown to everyohne
+ var/atom/movable/screen/cinematic/screen
+ /// Callbacks passed that occur during the animation
+ var/datum/callback/special_callback
+ /// How long for the final screen remains shown
+ var/cleanup_time = 30 SECONDS
+ /// Whether the cinematic turns off ooc when played globally.
+ var/stop_ooc = TRUE
+
+/datum/cinematic/New(watcher, datum/callback/special_callback)
+ screen = new(src)
+ if(watcher == world)
+ is_global = TRUE
+
+ src.special_callback = special_callback
+
+/datum/cinematic/Destroy()
+ QDEL_NULL(screen)
+ special_callback = null
+ watching.Cut()
+ locked.Cut()
+ return ..()
+
+/// Actually goes through the process of showing the cinematic to the list of watchers.
+/datum/cinematic/proc/start_cinematic(list/watchers)
+ if(SEND_GLOBAL_SIGNAL(COMSIG_GLOB_PLAY_CINEMATIC, src) & COMPONENT_GLOB_BLOCK_CINEMATIC)
+ return
+
+ // Register a signal to handle what happens when a different cinematic tries to play over us.
+ RegisterSignal(SSdcs, COMSIG_GLOB_PLAY_CINEMATIC, PROC_REF(handle_replacement_cinematics))
+
+ // Pause OOC
+ // NOT IMPLEMENTED
+ var/ooc_toggled = FALSE
+ //if(is_global && stop_ooc && GLOB.ooc_allowed)
+ // ooc_toggled = TRUE
+ // toggle_ooc(FALSE)
+
+ // Place the /atom/movable/screen/cinematic into everyone's screens, and prevent movement.
+ for(var/mob/watching_mob in watchers)
+ //show_to(watching_mob, GET_CLIENT(watching_mob)) // NOT IMPLEMENTED (GET_CLIENT)
+ show_to(watching_mob, watching_mob.client)
+ RegisterSignal(watching_mob, COMSIG_MOB_CLIENT_LOGIN, PROC_REF(show_to))
+ // Close watcher ui's, too, so they can watch it.
+ SStgui.close_user_uis(watching_mob)
+
+ // Actually plays the animation. This will sleep, likely.
+ play_cinematic()
+
+ // Cleans up after it's done playing.
+ addtimer(CALLBACK(src, PROC_REF(clean_up_cinematic), ooc_toggled), cleanup_time)
+
+/// Cleans up the cinematic after a set timer of it sticking on the end screen.
+/datum/cinematic/proc/clean_up_cinematic(was_ooc_toggled = FALSE)
+ // NOT IMPLEMENTED
+ //if(was_ooc_toggled)
+ // toggle_ooc(TRUE)
+
+ stop_cinematic()
+
+/// Whenever another cinematic starts to play over us, we have the chacne to block it.
+/datum/cinematic/proc/handle_replacement_cinematics(datum/source, datum/cinematic/other)
+ SIGNAL_HANDLER
+
+ // Stop our's and allow others to play if we're local and it's global
+ if(!is_global && other.is_global)
+ stop_cinematic()
+ return NONE
+
+ return COMPONENT_GLOB_BLOCK_CINEMATIC
+
+/// Whenever a mob watching the cinematic logs in, show them the ongoing cinematic
+/datum/cinematic/proc/show_to(mob/watching_mob, client/watching_client)
+ SIGNAL_HANDLER
+
+ if(!HAS_TRAIT_FROM(watching_mob, TRAIT_NO_TRANSFORM, CINEMATIC_SOURCE))
+ lock_mob(watching_mob)
+
+ // Only show the actual cinematic to cliented mobs.
+ if(!watching_client || (watching_client in watching))
+ return
+
+ watching += watching_client
+ watching_mob.overlay_fullscreen("cinematic", /obj/screen/fullscreen/cinematic_backdrop)
+ watching_client.screen += screen
+ RegisterSignal(watching_client, COMSIG_PARENT_QDELETING, PROC_REF(remove_watcher))
+
+/// Simple helper for playing sounds from the cinematic.
+/datum/cinematic/proc/play_cinematic_sound(sound_to_play)
+ if(is_global)
+ SEND_SOUND(world, sound_to_play)
+ else
+ for(var/client/watching_client as anything in watching)
+ SEND_SOUND(watching_client, sound_to_play)
+
+/// Invoke any special callbacks for actual effects synchronized with animation.
+/// (Such as a real nuke explosion happening midway)
+/datum/cinematic/proc/invoke_special_callback()
+ special_callback?.Invoke()
+
+/// The actual cinematic occurs here.
+/datum/cinematic/proc/play_cinematic()
+ return
+
+/// Stops the cinematic and removes it from all the viewers.
+/datum/cinematic/proc/stop_cinematic()
+ for(var/client/viewing_client as anything in watching)
+ remove_watcher(viewing_client)
+
+ for(var/datum/weakref/locked_ref as anything in locked)
+ unlock_mob(locked_ref)
+
+ qdel(src)
+
+/// Locks a mob, preventing them from moving, being hurt, or acting
+/datum/cinematic/proc/lock_mob(mob/to_lock)
+ locked += WEAKREF(to_lock)
+ ADD_TRAIT(to_lock, TRAIT_NO_TRANSFORM, CINEMATIC_SOURCE)
+
+/// Unlocks a previously locked weakref
+/datum/cinematic/proc/unlock_mob(datum/weakref/mob_ref)
+ var/mob/locked_mob = mob_ref.resolve()
+ if(isnull(locked_mob))
+ return
+ REMOVE_TRAIT(locked_mob, TRAIT_NO_TRANSFORM, CINEMATIC_SOURCE)
+ UnregisterSignal(locked_mob, COMSIG_MOB_CLIENT_LOGIN)
+
+/// Removes the passed client from our watching list.
+/datum/cinematic/proc/remove_watcher(client/no_longer_watching)
+ SIGNAL_HANDLER
+
+ if(!(no_longer_watching in watching))
+ CRASH("cinematic remove_watcher was passed a client which wasn't watching.")
+
+ UnregisterSignal(no_longer_watching, COMSIG_PARENT_QDELETING)
+ // We'll clear the cinematic if they have a mob which has one,
+ // but we won't remove TRAIT_NO_TRANSFORM. Wait for the cinematic end to do that.
+ no_longer_watching.mob?.clear_fullscreen("cinematic")
+ no_longer_watching.screen -= screen
+
+ watching -= no_longer_watching
+
+#undef CINEMATIC_SOURCE
diff --git a/code/datums/cinematics/malf_doomsday.dm b/code/datums/cinematics/malf_doomsday.dm
new file mode 100644
index 0000000000..673d229f1d
--- /dev/null
+++ b/code/datums/cinematics/malf_doomsday.dm
@@ -0,0 +1,10 @@
+/// A malfunctioning AI has activated the doomsday device and wiped the station!
+/datum/cinematic/malf
+
+/datum/cinematic/malf/play_cinematic()
+ flick("intro_malf", screen)
+ stoplag(7.6 SECONDS)
+ flick("station_explode_fade_red", screen)
+ play_cinematic_sound(sound('sound/effects/explosionfar.ogg'))
+ special_callback?.Invoke()
+ screen.icon_state = "summary_malf"
diff --git a/code/datums/cinematics/nuke_cinematics.dm b/code/datums/cinematics/nuke_cinematics.dm
new file mode 100644
index 0000000000..a17b48180d
--- /dev/null
+++ b/code/datums/cinematics/nuke_cinematics.dm
@@ -0,0 +1,160 @@
+/// Simple, base cinematic for all animations based around a nuke detonating.
+/datum/cinematic/nuke
+ /// If set, this is the summary screen that pops up after the nuke is done.
+ var/after_nuke_summary_state
+
+
+/datum/cinematic/nuke/play_cinematic()
+ flick("intro_nuke", screen)
+ stoplag(3.5 SECONDS)
+ play_nuke_effect()
+ if(special_callback)
+ special_callback.Invoke()
+ if(after_nuke_summary_state)
+ screen.icon_state = after_nuke_summary_state
+
+/// Specific effects for each type of cinematics goes here.
+/datum/cinematic/nuke/proc/play_nuke_effect()
+ return
+
+/// The syndicate nuclear bomb was activated, and destroyed the station!
+/datum/cinematic/nuke/ops_victory
+ after_nuke_summary_state = "summary_nukewin"
+
+/datum/cinematic/nuke/ops_victory/play_nuke_effect()
+ flick("station_explode_fade_red", screen)
+ play_cinematic_sound(sound('sound/effects/explosionfar.ogg'))
+
+/// The syndicate nuclear bomb was activated, but just barely missed the station!
+/datum/cinematic/nuke/ops_miss
+ after_nuke_summary_state = "summary_nukefail"
+
+/datum/cinematic/nuke/ops_miss/play_nuke_effect()
+ flick("station_intact_fade_red", screen)
+ play_cinematic_sound(sound('sound/effects/explosionfar.ogg'))
+
+/// The self destruct, or another station-destroying entity like a blob, destroyed the station!
+/datum/cinematic/nuke/self_destruct
+ after_nuke_summary_state = "summary_selfdes"
+
+/datum/cinematic/nuke/self_destruct/play_nuke_effect()
+ flick("station_explode_fade_red", screen)
+ play_cinematic_sound(sound('sound/effects/explosionfar.ogg'))
+
+/// The self destruct was activated, yet somehow avoided destroying the station!
+/datum/cinematic/nuke/self_destruct_miss
+ after_nuke_summary_state = "station_intact"
+
+/datum/cinematic/nuke/self_destruct_miss/play_nuke_effect()
+ play_cinematic_sound(sound('sound/effects/explosionfar.ogg'))
+ special_callback?.Invoke()
+
+/// The syndicate nuclear bomb was activated, but just missed the station by a whole z-level!
+/datum/cinematic/nuke/far_explosion
+ cleanup_time = 0 SECONDS
+
+/datum/cinematic/nuke/far_explosion/play_cinematic()
+ // This one has no intro sequence.
+ // It's actually just a global sound, which makes you wonder why it's a cinematic.
+ play_cinematic_sound(sound('sound/effects/explosionfar.ogg'))
+ special_callback?.Invoke()
+
+/*
+/datum/controller/subsystem/ticker/proc/station_explosion_cinematic(var/station_missed=0, var/override = null)
+ if( cinematic ) return //already a cinematic in progress!
+
+ //initialise our cinematic screen object
+ cinematic = new(src)
+ cinematic.icon = 'icons/effects/station_explosion.dmi'
+ cinematic.icon_state = "station_intact"
+ cinematic.layer = 100
+ cinematic.plane = PLANE_PLAYER_HUD
+ cinematic.mouse_opacity = 0
+ cinematic.screen_loc = "1,0"
+
+ var/obj/structure/bed/temp_buckle = new(src)
+ //Incredibly hackish. It creates a bed within the gameticker (lol) to stop mobs running around
+ if(station_missed)
+ for(var/mob/living/M in living_mob_list)
+ M.buckled = temp_buckle //buckles the mob so it can't do anything
+ if(M.client)
+ M.client.screen += cinematic //show every client the cinematic
+ else //nuke kills everyone on z-level 1 to prevent "hurr-durr I survived"
+ for(var/mob/living/M in living_mob_list)
+ M.buckled = temp_buckle
+ if(M.client)
+ M.client.screen += cinematic
+
+ switch(M.z)
+ if(0) //inside a crate or something
+ var/turf/T = get_turf(M)
+ if(T && (T.z in using_map.station_levels)) //we don't use M.death(0) because it calls a for(/mob) loop and
+ M.health = 0
+ M.set_stat(DEAD)
+ if(1) //on a z-level 1 turf.
+ M.health = 0
+ M.set_stat(DEAD)
+
+ //Now animate the cinematic
+ switch(station_missed)
+ if(1) //nuke was nearby but (mostly) missed
+ if( mode && !override )
+ override = mode.name
+ switch( override )
+ if("mercenary") //Nuke wasn't on station when it blew up
+ flick("intro_nuke",cinematic)
+ sleep(35)
+ world << sound('sound/effects/explosionfar.ogg')
+ flick("station_intact_fade_red",cinematic)
+ cinematic.icon_state = "summary_nukefail"
+ else
+ flick("intro_nuke",cinematic)
+ sleep(35)
+ world << sound('sound/effects/explosionfar.ogg')
+ //flick("end",cinematic)
+
+
+ if(2) //nuke was nowhere nearby //TODO: a really distant explosion animation
+ sleep(50)
+ world << sound('sound/effects/explosionfar.ogg')
+
+
+ else //station was destroyed
+ if( mode && !override )
+ override = mode.name
+ switch( override )
+ if("mercenary") //Nuke Ops successfully bombed the station
+ flick("intro_nuke",cinematic)
+ sleep(35)
+ flick("station_explode_fade_red",cinematic)
+ world << sound('sound/effects/explosionfar.ogg')
+ cinematic.icon_state = "summary_nukewin"
+ if("AI malfunction") //Malf (screen,explosion,summary)
+ flick("intro_malf",cinematic)
+ sleep(76)
+ flick("station_explode_fade_red",cinematic)
+ world << sound('sound/effects/explosionfar.ogg')
+ cinematic.icon_state = "summary_malf"
+ if("blob") //Station nuked (nuke,explosion,summary)
+ flick("intro_nuke",cinematic)
+ sleep(35)
+ flick("station_explode_fade_red",cinematic)
+ world << sound('sound/effects/explosionfar.ogg')
+ cinematic.icon_state = "summary_selfdes"
+ else //Station nuked (nuke,explosion,summary)
+ flick("intro_nuke",cinematic)
+ sleep(35)
+ flick("station_explode_fade_red", cinematic)
+ world << sound('sound/effects/explosionfar.ogg')
+ cinematic.icon_state = "summary_selfdes"
+ for(var/mob/living/M in living_mob_list)
+ if(M.loc.z in using_map.station_levels)
+ M.death()//No mercy
+ //If its actually the end of the round, wait for it to end.
+ //Otherwise if its a verb it will continue on afterwards.
+ sleep(300)
+
+ if(cinematic) qdel(cinematic) //end the cinematic
+ if(temp_buckle) qdel(temp_buckle) //release everybody
+ return
+*/
diff --git a/code/datums/diseases/advance/advance.dm b/code/datums/diseases/advance/advance.dm
index 0ecad3e8b9..7edcf5f089 100644
--- a/code/datums/diseases/advance/advance.dm
+++ b/code/datums/diseases/advance/advance.dm
@@ -489,8 +489,8 @@ GLOBAL_LIST_INIT(advance_cures, list(
return main_symptom.name
// Prefixes. These need a space right after.
- var/list/prefixes = list("Spacer's ", "Space ", "Infectious ","Viral ", "The ", "[capitalize(prob(50) ? pick(first_names_male) : pick(first_names_female))]'s ", "[capitalize(pick(last_names))]'s ", "Acute ")
- var/list/bodies = list(pick("[capitalize(prob(50) ? pick(first_names_male) : pick(first_names_female))]", "[pick(last_names)]"), "Space", "Disease", "Noun", "Cold", "Germ", "Virus")
+ var/list/prefixes = list("Spacer's ", "Space ", "Infectious ","Viral ", "The ", "[capitalize(prob(50) ? pick(GLOB.first_names_male) : pick(GLOB.first_names_female))]'s ", "[capitalize(pick(GLOB.last_names))]'s ", "Acute ")
+ var/list/bodies = list(pick("[capitalize(prob(50) ? pick(GLOB.first_names_male) : pick(GLOB.first_names_female))]", "[pick(GLOB.last_names)]"), "Space", "Disease", "Noun", "Cold", "Germ", "Virus")
// These might need some space before the word, depends on what you want to add.
var/list/suffixes = list("ism", "itis", "osis", "itosis", " #[rand(1,10000)]", "-[rand(1,100)]", "s", "y", " Virus", " Bug", " Infection", " Disease", " Complex", " Syndrome", " Sickness")
diff --git a/code/datums/ghost_spawn_options.dm b/code/datums/ghost_spawn_options.dm
index ed539a1909..dcf7255169 100644
--- a/code/datums/ghost_spawn_options.dm
+++ b/code/datums/ghost_spawn_options.dm
@@ -57,7 +57,7 @@
to_chat(host, span_info("You are now a mouse. Try to avoid interaction with players, and do not give hints away that you are more than a simple rodent."))
/datum/tgui_module/ghost_spawn_menu/proc/become_drone(mob/observer/dead/user, fabricator)
- if(ticker.current_state < GAME_STATE_PLAYING)
+ if(SSticker.current_state < GAME_STATE_PLAYING)
to_chat(user, span_danger("The game hasn't started yet!"))
return
diff --git a/code/datums/helper_datums/teleport.dm b/code/datums/helper_datums/teleport.dm
index 5234d3f5e7..563cd816ad 100644
--- a/code/datums/helper_datums/teleport.dm
+++ b/code/datums/helper_datums/teleport.dm
@@ -1,9 +1,10 @@
-var/bluespace_item_types = newlist(/obj/item/storage/backpack/holding,
-/obj/item/storage/bag/trash/holding,
-/obj/item/storage/pouch/holding,
-/obj/item/storage/belt/utility/holding,
-/obj/item/storage/belt/medical/holding
-)
+GLOBAL_LIST_INIT(bluespace_item_types, list(
+ /obj/item/storage/backpack/holding,
+ /obj/item/storage/bag/trash/holding,
+ /obj/item/storage/pouch/holding,
+ /obj/item/storage/belt/utility/holding,
+ /obj/item/storage/belt/medical/holding
+))
//wrapper
/proc/do_teleport(ateleatom, adestination, aprecision=0, afteleport=1, aeffectin=null, aeffectout=null, asoundin=null, asoundout=null, local=TRUE, bohsafe=FALSE)
@@ -167,7 +168,7 @@ var/bluespace_item_types = newlist(/obj/item/storage/backpack/holding,
var/list/bluespace_things = newlist()
- for (var/item in bluespace_item_types)
+ for (var/item in GLOB.bluespace_item_types)
if (istype(teleatom, item))
precision = rand(1, 100)
bluespace_things |= teleatom.search_contents_for(item)
@@ -176,7 +177,7 @@ var/bluespace_item_types = newlist(/obj/item/storage/backpack/holding,
var/mob/living/L = teleatom
if(LAZYLEN(L.buckled_mobs))
for(var/mob/rider in L.buckled_mobs)
- for (var/item in bluespace_item_types)
+ for (var/item in GLOB.bluespace_item_types)
bluespace_things |= rider.search_contents_for(item)
if(bluespace_things.len)
diff --git a/code/datums/mind.dm b/code/datums/mind.dm
index 4b4473a6dd..93ad33bf1b 100644
--- a/code/datums/mind.dm
+++ b/code/datums/mind.dm
@@ -123,7 +123,7 @@
popup.open()
/datum/mind/proc/edit_memory()
- if(!ticker || !ticker.mode)
+ if(!SSticker || !SSticker.mode)
tgui_alert_async(usr, "Not before round-start!", "Alert")
return
@@ -239,7 +239,7 @@
var/objective_type = "[objective_type_capital][objective_type_text]"//Add them together into a text string.
var/list/possible_targets = list("Free objective")
- for(var/datum/mind/possible_target in ticker.minds)
+ for(var/datum/mind/possible_target in SSticker.minds)
if ((possible_target != src) && ishuman(possible_target.current))
possible_targets += possible_target.current
@@ -509,8 +509,8 @@
else
mind = new /datum/mind(key)
mind.original = src
- if(ticker)
- ticker.minds += mind
+ if(SSticker)
+ SSticker.minds += mind
else
to_world_log("## DEBUG: mind_initialize(): No ticker ready yet! Please inform Carn")
if(!mind.name) mind.name = real_name
diff --git a/code/datums/verb_callbacks.dm b/code/datums/verb_callbacks.dm
index 77f817017d..9e887f53e0 100644
--- a/code/datums/verb_callbacks.dm
+++ b/code/datums/verb_callbacks.dm
@@ -8,7 +8,7 @@
creation_time = DS2TICKS(world.time)
. = ..()
-#ifndef UNIT_TEST
+#ifndef UNIT_TESTS
/datum/callback/verb_callback/Invoke(...)
var/mob/our_user = user?.resolve()
if(QDELETED(our_user) || isnull(our_user.client))
diff --git a/code/defines/procs/announce.dm b/code/defines/procs/announce.dm
index a686580557..b1df8246b9 100644
--- a/code/defines/procs/announce.dm
+++ b/code/defines/procs/announce.dm
@@ -142,7 +142,7 @@
command_announcement.Announce("It has come to our attention that \the [station_name()] passed through an ion storm. Please monitor all electronic equipment for malfunctions.", "Anomaly Alert")
/proc/AnnounceArrival(var/mob/living/carbon/human/character, var/rank, var/join_message, var/channel = "Common", var/zlevel)
- if (ticker.current_state == GAME_STATE_PLAYING)
+ if (SSticker.current_state == GAME_STATE_PLAYING)
var/list/zlevels = zlevel ? using_map.get_map_levels(zlevel, TRUE, om_range = DEFAULT_OVERMAP_RANGE) : null
if(character.mind.role_alt_title)
rank = character.mind.role_alt_title
diff --git a/code/defines/procs/radio.dm b/code/defines/procs/radio.dm
index 1f8f03bc9a..a73de9d384 100644
--- a/code/defines/procs/radio.dm
+++ b/code/defines/procs/radio.dm
@@ -5,13 +5,13 @@
/proc/register_radio(source, old_frequency, new_frequency, radio_filter)
if(old_frequency)
- radio_controller.remove_object(source, old_frequency)
+ SSradio.remove_object(source, old_frequency)
if(new_frequency)
- return radio_controller.add_object(source, new_frequency, radio_filter)
+ return SSradio.add_object(source, new_frequency, radio_filter)
/proc/unregister_radio(source, frequency)
- if(radio_controller)
- radio_controller.remove_object(source, frequency)
+ if(SSradio)
+ SSradio.remove_object(source, frequency)
/proc/get_frequency_name(var/display_freq)
var/freq_text
diff --git a/code/game/antagonist/antagonist.dm b/code/game/antagonist/antagonist.dm
index febff8fce8..493a127c14 100644
--- a/code/game/antagonist/antagonist.dm
+++ b/code/game/antagonist/antagonist.dm
@@ -107,7 +107,7 @@
// Prune restricted status. Broke it up for readability.
// Note that this is done before jobs are handed out.
- candidates = ticker.mode.get_players_for_role(role_type, id, ghosts_only)
+ candidates = SSticker.mode.get_players_for_role(role_type, id, ghosts_only)
for(var/datum/mind/player in candidates)
if(ghosts_only && !isobserver(player.current))
candidates -= player
@@ -143,10 +143,10 @@
if(players && players.len)
player = pick(players)
if(!istype(player))
- message_admins("[uppertext(ticker.mode.name)]: Failed to find a candidate for [role_text].")
+ message_admins("[uppertext(SSticker.mode.name)]: Failed to find a candidate for [role_text].")
return 0
to_chat(player.current, span_danger(span_italics("You have been selected this round as an antagonist!")))
- message_admins("[uppertext(ticker.mode.name)]: Selected [player] as a [role_text].")
+ message_admins("[uppertext(SSticker.mode.name)]: Selected [player] as a [role_text].")
if(isobserver(player.current))
create_default(player.current)
else
diff --git a/code/game/antagonist/antagonist_update.dm b/code/game/antagonist/antagonist_update.dm
index 04bf3a92f5..7d19f0de6a 100644
--- a/code/game/antagonist/antagonist_update.dm
+++ b/code/game/antagonist/antagonist_update.dm
@@ -78,11 +78,11 @@
/datum/antagonist/proc/update_current_antag_max()
cur_max = hard_cap
- if(ticker && ticker.mode)
- if(ticker.mode.antag_tags && (id in ticker.mode.antag_tags))
+ if(SSticker && SSticker.mode)
+ if(SSticker.mode.antag_tags && (id in SSticker.mode.antag_tags))
cur_max = hard_cap_round
- if(ticker.mode.antag_scaling_coeff)
+ if(SSticker.mode.antag_scaling_coeff)
var/count = 0
for(var/mob/living/M in GLOB.player_list)
@@ -91,4 +91,4 @@
// Minimum: initial_spawn_target
// Maximum: hard_cap or hard_cap_round
- cur_max = max(initial_spawn_target,min(round(count/ticker.mode.antag_scaling_coeff),cur_max))
+ cur_max = max(initial_spawn_target,min(round(count/SSticker.mode.antag_scaling_coeff),cur_max))
diff --git a/code/game/antagonist/outsider/deathsquad.dm b/code/game/antagonist/outsider/deathsquad.dm
index 2ea908d23c..cc748bf016 100644
--- a/code/game/antagonist/outsider/deathsquad.dm
+++ b/code/game/antagonist/outsider/deathsquad.dm
@@ -68,7 +68,7 @@ var/datum/antagonist/deathsquad/deathsquad
else
syndicate_commando_rank = pick("Lieutenant", "Captain", "Major")
- var/syndicate_commando_name = pick(last_names)
+ var/syndicate_commando_name = pick(GLOB.last_names)
player.name = "[syndicate_commando_rank] [syndicate_commando_name]"
player.current.name = player.name
diff --git a/code/game/antagonist/outsider/ninja.dm b/code/game/antagonist/outsider/ninja.dm
index b06ef042ae..3abf458521 100644
--- a/code/game/antagonist/outsider/ninja.dm
+++ b/code/game/antagonist/outsider/ninja.dm
@@ -84,8 +84,8 @@ var/datum/antagonist/ninja/ninjas
/datum/antagonist/ninja/update_antag_mob(var/datum/mind/player)
..()
- var/ninja_title = pick(ninja_titles)
- var/ninja_name = pick(ninja_names)
+ var/ninja_title = pick(GLOB.ninja_titles)
+ var/ninja_name = pick(GLOB.ninja_names)
var/mob/living/carbon/human/H = player.current
if(istype(H))
H.real_name = "[ninja_title] [ninja_name]"
diff --git a/code/game/antagonist/outsider/technomancer.dm b/code/game/antagonist/outsider/technomancer.dm
index 976aa4f762..112ff3dd54 100644
--- a/code/game/antagonist/outsider/technomancer.dm
+++ b/code/game/antagonist/outsider/technomancer.dm
@@ -29,7 +29,7 @@ var/datum/antagonist/technomancer/technomancers
/datum/antagonist/technomancer/update_antag_mob(var/datum/mind/technomancer)
..()
technomancer.store_memory(span_bold("Remember:") + " Do not forget to purchase the functions and equipment you need.")
- technomancer.current.real_name = "[pick(wizard_first)] [pick(wizard_second)]"
+ technomancer.current.real_name = "[pick(GLOB.wizard_first)] [pick(GLOB.wizard_second)]"
technomancer.current.name = technomancer.current.real_name
/datum/antagonist/technomancer/equip(var/mob/living/carbon/human/technomancer_mob)
diff --git a/code/game/antagonist/outsider/wizard.dm b/code/game/antagonist/outsider/wizard.dm
index 033c45c1ab..0543ac718b 100644
--- a/code/game/antagonist/outsider/wizard.dm
+++ b/code/game/antagonist/outsider/wizard.dm
@@ -67,7 +67,7 @@ var/datum/antagonist/wizard/wizards
/datum/antagonist/wizard/update_antag_mob(var/datum/mind/wizard)
..()
wizard.store_memory(span_bold("Remember:") + " do not forget to prepare your spells.")
- wizard.current.real_name = "[pick(wizard_first)] [pick(wizard_second)]"
+ wizard.current.real_name = "[pick(GLOB.wizard_first)] [pick(GLOB.wizard_second)]"
wizard.current.name = wizard.current.real_name
/datum/antagonist/wizard/equip(var/mob/living/carbon/human/wizard_mob)
diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm
index 85aeab1080..9d18fcd8a7 100644
--- a/code/game/area/areas.dm
+++ b/code/game/area/areas.dm
@@ -86,7 +86,7 @@ GLOBAL_LIST_EMPTY(areas_by_type)
A.contents.Add(T)
if(old_area)
// Handle dynamic lighting update if
- if(SSlighting.subsystem_initialized && T.dynamic_lighting && old_area.dynamic_lighting != A.dynamic_lighting)
+ if(SSlighting.initialized && T.dynamic_lighting && old_area.dynamic_lighting != A.dynamic_lighting)
if(A.dynamic_lighting)
T.lighting_build_overlay()
else
diff --git a/code/game/atom/README.md b/code/game/atom/README.md
new file mode 100644
index 0000000000..3ce4f9b0cf
--- /dev/null
+++ b/code/game/atom/README.md
@@ -0,0 +1,3 @@
+# Core /atom Systems
+
+This is a folder of basic systems that apply to all /atom types, split into relevant files. Keep the files named fittingly and don't put anything in `_atom.dm` if you can avoid it.
diff --git a/code/game/atoms.dm b/code/game/atom/_atom.dm
similarity index 98%
rename from code/game/atoms.dm
rename to code/game/atom/_atom.dm
index 4d770c50b5..2a43441713 100644
--- a/code/game/atoms.dm
+++ b/code/game/atom/_atom.dm
@@ -1,3 +1,9 @@
+/**
+ * The base type for nearly all physical objects in SS13
+
+ * Lots and lots of functionality lives here, although in general we are striving to move
+ * as much as possible to the components/elements system
+ */
/atom
layer = TURF_LAYER //This was here when I got here. Why though?
var/level = 2
diff --git a/code/game/atoms_init.dm b/code/game/atom/atoms_initializing_EXPENSIVE.dm
similarity index 99%
rename from code/game/atoms_init.dm
rename to code/game/atom/atoms_initializing_EXPENSIVE.dm
index 666ff6312a..fd5d4961a0 100644
--- a/code/game/atoms_init.dm
+++ b/code/game/atom/atoms_initializing_EXPENSIVE.dm
@@ -67,7 +67,7 @@
if(GLOB.use_preloader && src.type == GLOB._preloader_path)//in case the instantiated atom is creating other atoms in New()
world.preloader_load(src)
- var/do_initialize = SSatoms.initialized
+ var/do_initialize = SSatoms.atom_initialized
if(do_initialize != INITIALIZATION_INSSATOMS)
args[1] = do_initialize == INITIALIZATION_INNEW_MAPLOAD
if(SSatoms.InitAtom(src, FALSE, args))
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index d314a661d1..c306e0c7f7 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -1,7 +1,8 @@
/atom/movable
layer = OBJ_LAYER
- appearance_flags = TILE_BOUND|PIXEL_SCALE|KEEP_TOGETHER|LONG_GLIDE
glide_size = 8
+ appearance_flags = TILE_BOUND|PIXEL_SCALE|KEEP_TOGETHER|LONG_GLIDE
+
var/last_move = null //The direction the atom last moved
var/anchored = FALSE
// var/elevation = 2 - not used anywhere
@@ -512,8 +513,8 @@
new_y = TRANSITIONEDGE + 1
new_x = rand(TRANSITIONEDGE + 2, world.maxx - TRANSITIONEDGE - 2)
- if(ticker && istype(ticker.mode, /datum/game_mode/nuclear)) //only really care if the game mode is nuclear
- var/datum/game_mode/nuclear/G = ticker.mode
+ if(SSticker && istype(SSticker.mode, /datum/game_mode/nuclear)) //only really care if the game mode is nuclear
+ var/datum/game_mode/nuclear/G = SSticker.mode
G.check_nuke_disks()
var/turf/T = locate(new_x, new_y, new_z)
diff --git a/code/game/dna/dna2.dm b/code/game/dna/dna2.dm
index dcdc8f075b..49fa7554c0 100644
--- a/code/game/dna/dna2.dm
+++ b/code/game/dna/dna2.dm
@@ -14,7 +14,7 @@ GLOBAL_LIST_EMPTY_TYPED(dna_genes_bad, /datum/gene/trait)
/proc/get_gene_from_trait(var/trait_path) // ALWAYS USE THIS
RETURN_TYPE(/datum/gene/trait)
var/G = GLOB.trait_to_dna_genes[trait_path]
- #ifndef UNIT_TEST
+ #ifndef UNIT_TESTS
if(!G) // This SHOULD NOT HAPPEN, be sure any viruses or injectors that give trait paths are actually traitgenes.
stack_trace("[trait_path] was used as a traitgene, without being flagged as one.")
#endif
diff --git a/code/game/gamemodes/cult/runes.dm b/code/game/gamemodes/cult/runes.dm
index cfebf37f34..2536f1cf02 100644
--- a/code/game/gamemodes/cult/runes.dm
+++ b/code/game/gamemodes/cult/runes.dm
@@ -665,7 +665,7 @@ var/list/sacrificed = list()
if(lamb.species.rarity_value > 3)
worth = 1
- if (ticker.mode.name == "cult")
+ if (SSticker.mode.name == "cult")
if(H.mind == cult.sacrifice_target)
if(cultsinrange.len >= 3)
sacrificed += H.mind
@@ -990,7 +990,7 @@ var/list/sacrificed = list()
/obj/effect/rune/proc/bloodboil() //cultists need at least one DANGEROUS rune. Even if they're all stealthy.
/*
var/list/mob/living/carbon/cultists = new
- for(var/datum/mind/H in ticker.mode.cult)
+ for(var/datum/mind/H in SSticker.mode.cult)
if (istype(H.current,/mob/living/carbon))
cultists+=H.current
*/
diff --git a/code/game/gamemodes/endgame/supermatter_cascade/universe.dm b/code/game/gamemodes/endgame/supermatter_cascade/universe.dm
index 8ba1570153..923066ed7d 100644
--- a/code/game/gamemodes/endgame/supermatter_cascade/universe.dm
+++ b/code/game/gamemodes/endgame/supermatter_cascade/universe.dm
@@ -78,7 +78,19 @@ GLOBAL_VAR_INIT(universe_has_ended, 0)
C.req_one_access = list()
spawn(5 MINUTES)
- ticker.station_explosion_cinematic(0,null) // TODO: Custom cinematic
+ play_cinematic(/datum/cinematic/nuke/self_destruct) // TODO: Custom cinematic
+
+ // FIXME: Probably a better way
+ for(var/mob/living/M in GLOB.living_mob_list)
+ switch(M.z)
+ if(0) //inside a crate or something
+ var/turf/T = get_turf(M)
+ if(T && (T.z in using_map.station_levels)) //we don't use M.death(0) because it calls a for(/mob) loop and
+ M.health = 0
+ M.set_stat(DEAD)
+ if(1) //on a z-level 1 turf.
+ M.health = 0
+ M.set_stat(DEAD)
GLOB.universe_has_ended = 1
return
diff --git a/code/game/gamemodes/epidemic/epidemic.dm b/code/game/gamemodes/epidemic/epidemic.dm
index 6be1aeaac3..dd0a8ee2b6 100644
--- a/code/game/gamemodes/epidemic/epidemic.dm
+++ b/code/game/gamemodes/epidemic/epidemic.dm
@@ -132,7 +132,7 @@
checkwin_counter++
if(checkwin_counter >= 20)
if(!finished)
- ticker.mode.check_win()
+ SSticker.mode.check_win()
checkwin_counter = 0
return 0
@@ -165,7 +165,7 @@
///Handle crew failure(station explodes)///
///////////////////////////////////////////
/datum/game_mode/epidemic/proc/crew_lose()
- ticker.mode:explosion_in_progress = 1
+ SSticker.mode:explosion_in_progress = 1
for(var/mob/M in world)
if(M.client)
M << 'sound/machines/Alarm.ogg'
@@ -177,9 +177,9 @@
enter_allowed = 0
if(ticker)
ticker.station_explosion_cinematic(0,null)
- if(ticker.mode)
- ticker.mode:station_was_nuked = 1
- ticker.mode:explosion_in_progress = 0
+ if(SSticker.mode)
+ SSticker.mode:station_was_nuked = 1
+ SSticker.mode:explosion_in_progress = 0
finished = 2
return
diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm
index df10f34056..18d3c9c8d2 100644
--- a/code/game/gamemodes/game_mode.dm
+++ b/code/game/gamemodes/game_mode.dm
@@ -104,9 +104,9 @@ GLOBAL_LIST_EMPTY(additional_antag_types)
return
var/datum/antagonist/antag = GLOB.all_antag_types[choice]
if(antag)
- if(!islist(ticker.mode.antag_templates))
- ticker.mode.antag_templates = list()
- ticker.mode.antag_templates |= antag
+ if(!islist(SSticker.mode.antag_templates))
+ SSticker.mode.antag_templates = list()
+ SSticker.mode.antag_templates |= antag
message_admins("Admin [key_name_admin(usr)] added [antag.role_text] template to game mode.")
// I am very sure there's a better way to do this, but I'm not sure what it might be. ~Z
@@ -219,8 +219,8 @@ GLOBAL_LIST_EMPTY(additional_antag_types)
feedback_set_details("round_start","[time2text(world.realtime)]")
INVOKE_ASYNC(SSdbcore, TYPE_PROC_REF(/datum/controller/subsystem/dbcore, SetRoundStart))
- if(ticker && ticker.mode)
- feedback_set_details("game_mode","[ticker.mode]")
+ if(SSticker && SSticker.mode)
+ feedback_set_details("game_mode","[SSticker.mode]")
feedback_set_details("server_ip","[world.internet_address]:[world.port]")
return 1
@@ -394,7 +394,7 @@ GLOBAL_LIST_EMPTY(additional_antag_types)
return candidates
// If this is being called post-roundstart then it doesn't care about ready status.
- if(ticker && ticker.current_state == GAME_STATE_PLAYING)
+ if(SSticker && SSticker.current_state == GAME_STATE_PLAYING)
for(var/mob/player in GLOB.player_list)
if(!player.client)
continue
@@ -549,16 +549,16 @@ GLOBAL_LIST_EMPTY(additional_antag_types)
set name = "Check Round Info"
set category = "OOC.Game"
- if(!ticker || !ticker.mode)
+ if(!SSticker|| !SSticker.mode)
to_chat(usr, span_warning("Something is terribly wrong; there is no gametype."))
return
if(GLOB.master_mode != "secret")
- to_chat(usr, span_boldnotice("The roundtype is [capitalize(ticker.mode.name)]"))
- if(ticker.mode.round_description)
- to_chat(usr, span_notice(span_italics("[ticker.mode.round_description]")))
- if(ticker.mode.extended_round_description)
- to_chat(usr, span_notice("[ticker.mode.extended_round_description]"))
+ to_chat(usr, span_boldnotice("The roundtype is [capitalize(SSticker.mode.name)]"))
+ if(SSticker.mode.round_description)
+ to_chat(usr, span_notice(span_italics("[SSticker.mode.round_description]")))
+ if(SSticker.mode.extended_round_description)
+ to_chat(usr, span_notice("[SSticker.mode.extended_round_description]"))
else
to_chat(usr, span_notice(span_italics("Shhhh") + ". It's a secret."))
return
diff --git a/code/game/gamemodes/malfunction/newmalf_ability_trees/HARDWARE.dm b/code/game/gamemodes/malfunction/newmalf_ability_trees/HARDWARE.dm
index 0a555f5612..f2bc0d6000 100644
--- a/code/game/gamemodes/malfunction/newmalf_ability_trees/HARDWARE.dm
+++ b/code/game/gamemodes/malfunction/newmalf_ability_trees/HARDWARE.dm
@@ -105,7 +105,20 @@
radio.autosay("Self destructing now. Have a nice day.", "Self-Destruct Control")
timer--
- if(ticker)
- ticker.station_explosion_cinematic(0,null)
- if(ticker.mode)
- ticker.mode:station_was_nuked = 1
+ if(SSticker)
+ play_cinematic(/datum/cinematic/malf)
+
+ // FIXME: Probably a better way
+ for(var/mob/living/M in GLOB.living_mob_list)
+ switch(M.z)
+ if(0) //inside a crate or something
+ var/turf/T = get_turf(M)
+ if(T && (T.z in using_map.station_levels)) //we don't use M.death(0) because it calls a for(/mob) loop and
+ M.health = 0
+ M.set_stat(DEAD)
+ if(1) //on a z-level 1 turf.
+ M.health = 0
+ M.set_stat(DEAD)
+
+ if(SSticker.mode)
+ SSticker.mode:station_was_nuked = 1
diff --git a/code/game/gamemodes/newobjective.dm b/code/game/gamemodes/newobjective.dm
index 59d43ee024..e9a3429e45 100644
--- a/code/game/gamemodes/newobjective.dm
+++ b/code/game/gamemodes/newobjective.dm
@@ -22,7 +22,7 @@
/proc/GenerateAssassinate(var/job,var/datum/mind/traitor)
var/list/datum/objective/assassinate/missions = list()
- for(var/datum/mind/target in ticker.minds)
+ for(var/datum/mind/target in SSticker.minds)
if((target != traitor) && ishuman(target.current))
if(target && target.current)
var/datum/objective/target_obj = new /datum/objective/assassinate(null,job,target)
@@ -33,7 +33,7 @@
/proc/GenerateFrame(var/job,var/datum/mind/traitor)
var/list/datum/objective/frame/missions = list()
- for(var/datum/mind/target in ticker.minds)
+ for(var/datum/mind/target in SSticker.minds)
if((target != traitor) && ishuman(target.current))
if(target && target.current)
var/datum/objective/target_obj = new /datum/objective/frame(null,job,target)
@@ -44,7 +44,7 @@
/proc/GenerateProtection(var/job,var/datum/mind/traitor)
var/list/datum/objective/frame/missions = list()
- for(var/datum/mind/target in ticker.minds)
+ for(var/datum/mind/target in SSticker.minds)
if((target != traitor) && ishuman(target.current))
if(target && target.current)
var/datum/objective/target_obj = new /datum/objective/protection(null,job,target)
@@ -253,7 +253,7 @@ datum
proc/get_weight(var/job)
return INFINITY
proc/find_target_by_role(role, role_type=0)//Option sets either to check assigned role or special role. Default to assigned.
- for(var/datum/mind/possible_target in ticker.minds)
+ for(var/datum/mind/possible_target in SSticker.minds)
if((possible_target != owner) && ishuman(possible_target.current) && ((role_type ? possible_target.special_role : possible_target.assigned_role) == role) )
target = possible_target
break
@@ -387,7 +387,7 @@ datum
return 0
find_target_by_role(var/role)
- for(var/datum/mind/possible_target in ticker.minds)
+ for(var/datum/mind/possible_target in SSticker.minds)
if((possible_target != owner) && ishuman(possible_target.current) && (possible_target.assigned_role == role))
target = possible_target
break
@@ -403,7 +403,7 @@ datum
proc/find_target()
var/list/possible_targets = list()
- for(var/datum/mind/possible_target in ticker.minds)
+ for(var/datum/mind/possible_target in SSticker.minds)
if((possible_target != owner) && ishuman(possible_target.current))
possible_targets += possible_target
@@ -438,7 +438,7 @@ datum
return 1
find_target_by_role(var/role)
- for(var/datum/mind/possible_target in ticker.minds)
+ for(var/datum/mind/possible_target in SSticker.minds)
if((possible_target != owner) && ishuman(possible_target.current) && (possible_target.assigned_role == role))
target = possible_target
break
@@ -1287,13 +1287,13 @@ datum
target_amount = rand (lowbound,highbound)
if (ticker)
var/n_p = 1 //autowin
- if (ticker.current_state == GAME_STATE_SETTING_UP)
+ if (SSticker.current_state == GAME_STATE_SETTING_UP)
for(var/mob/new_player/P in world)
if(P.client && P.ready && P.mind!=owner)
n_p ++
- else if (ticker.current_state == GAME_STATE_PLAYING)
+ else if (SSticker.current_state == GAME_STATE_PLAYING)
for(var/mob/living/carbon/human/P in world)
- if(P.client && !(P.mind in ticker.mode.changelings) && P.mind!=owner)
+ if(P.client && !(P.mind in SSticker.mode.changelings) && P.mind!=owner)
n_p ++
target_amount = min(target_amount, n_p)
diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm
index db507c5fc1..b894823eea 100644
--- a/code/game/gamemodes/objective.dm
+++ b/code/game/gamemodes/objective.dm
@@ -23,7 +23,7 @@ GLOBAL_LIST_EMPTY(all_objectives)
/datum/objective/proc/find_target()
var/list/possible_targets = list()
- for(var/datum/mind/possible_target in ticker.minds)
+ for(var/datum/mind/possible_target in SSticker.minds)
if(possible_target != owner && ishuman(possible_target.current) && (possible_target.current.stat != 2))
possible_targets += possible_target
if(possible_targets.len > 0)
@@ -31,7 +31,7 @@ GLOBAL_LIST_EMPTY(all_objectives)
/datum/objective/proc/find_target_by_role(role, role_type=0)//Option sets either to check assigned role or special role. Default to assigned.
- for(var/datum/mind/possible_target in ticker.minds)
+ for(var/datum/mind/possible_target in SSticker.minds)
if((possible_target != owner) && ishuman(possible_target.current) && ((role_type ? possible_target.special_role : possible_target.assigned_role) == role) )
target = possible_target
break
@@ -573,13 +573,13 @@ GLOBAL_LIST_EMPTY(all_objectives)
/datum/objective/absorb/proc/gen_amount_goal(var/lowbound = 4, var/highbound = 6)
target_amount = rand (lowbound,highbound)
- if (ticker)
+ if (SSticker)
var/n_p = 1 //autowin
- if (ticker.current_state == GAME_STATE_SETTING_UP)
+ if (SSticker.current_state == GAME_STATE_SETTING_UP)
for(var/mob/new_player/P in GLOB.player_list)
if(P.client && P.ready && P.mind!=owner)
n_p ++
- else if (ticker.current_state == GAME_STATE_PLAYING)
+ else if (SSticker.current_state == GAME_STATE_PLAYING)
for(var/mob/living/carbon/human/P in GLOB.player_list)
var/datum/component/antag/changeling/comp = P.GetComponent(/datum/component/antag/changeling)
if(P.client && !(comp) && P.mind!=owner)
@@ -612,7 +612,7 @@ GLOBAL_LIST_EMPTY(all_objectives)
var/list/possible_targets = list()
var/list/priority_targets = list()
- for(var/datum/mind/possible_target in ticker.minds)
+ for(var/datum/mind/possible_target in SSticker.minds)
if(possible_target != owner && ishuman(possible_target.current) && (possible_target.current.stat != 2) && (!possible_target.special_role))
possible_targets += possible_target
for(var/role in roles)
diff --git a/code/game/jobs/job_controller.dm b/code/game/jobs/job_controller.dm
index b454050492..b52d7158be 100644
--- a/code/game/jobs/job_controller.dm
+++ b/code/game/jobs/job_controller.dm
@@ -228,7 +228,7 @@ var/global/datum/controller/occupations/job_master
SetupOccupations()
//Holder for Triumvirate is stored in the ticker, this just processes it
- if(ticker && ticker.triai)
+ if(SSticker && GLOB.triai)
for(var/datum/job/A in occupations)
if(A.title == JOB_AI)
A.spawn_positions = 3
@@ -273,7 +273,7 @@ var/global/datum/controller/occupations/job_master
// Loop through all levels from high to low
var/list/shuffledoccupations = shuffle(occupations)
- // var/list/disabled_jobs = ticker.mode.disabled_jobs // So we can use .Find down below without a colon.
+ // var/list/disabled_jobs = SSticker.mode.disabled_jobs // So we can use .Find down below without a colon.
for(var/level = 1 to 3)
//Check the head jobs first each level
CheckHeadPositions(level)
@@ -283,7 +283,7 @@ var/global/datum/controller/occupations/job_master
// Loop through all jobs
for(var/datum/job/job in shuffledoccupations) // SHUFFLE ME BABY
- if(!job || ticker.mode.disabled_jobs.Find(job.title) )
+ if(!job || SSticker.mode.disabled_jobs.Find(job.title) )
continue
if(jobban_isbanned(player, job.title))
@@ -346,7 +346,7 @@ var/global/datum/controller/occupations/job_master
//For ones returning to lobby
for(var/mob/new_player/player in unassigned)
if(player.client.prefs.alternate_option == RETURN_TO_LOBBY)
- player.ready = 0
+ player.ready = PLAYER_NOT_READY
unassigned -= player
return 1
@@ -503,7 +503,7 @@ var/global/datum/controller/occupations/job_master
// TWEET PEEP
if(rank == JOB_SITE_MANAGER && announce)
- var/sound/announce_sound = (ticker.current_state <= GAME_STATE_SETTING_UP) ? null : sound('sound/misc/boatswain.ogg', volume=20)
+ var/sound/announce_sound = (SSticker.current_state <= GAME_STATE_SETTING_UP) ? null : sound('sound/misc/boatswain.ogg', volume=20)
captain_announcement.Announce("All hands, [alt_title ? alt_title : JOB_SITE_MANAGER] [H.real_name] on deck!", new_sound = announce_sound, zlevel = H.z)
//Deferred item spawning.
diff --git a/code/game/json.dm b/code/game/json.dm
deleted file mode 100644
index 5a0e62b0aa..0000000000
--- a/code/game/json.dm
+++ /dev/null
@@ -1,100 +0,0 @@
-
-var/jsonpath = "/home/bay12/public_html"
-var/dmepath = "/home/bay12/git/baystation12.dme"
-var/makejson = 1 //temp
-/proc/makejson()
-
- if(!makejson)
- return
- fdel("[jsonpath]/info.json")
- //to_chat(usr, "Error cant delete json")
- //else
- //to_chat(usr, "Deleted json in public html")
- fdel("info.json")
- //to_chat(usr, "error cant delete local json")
- //else
- //to_chat(usr, "Deleted local json")
- var/F = file("info.json")
- if(!isfile(F))
- return
- var/mode
- if(ticker)
- if(ticker.current_state == 1)
- mode = "Round Setup"
- else if(ticker.hide_mode)
- mode = "SECRET"
- else
- mode = GLOB.master_mode
- var/playerscount = 0
- var/players = ""
- var/admins = "no"
- for(var/client/C)
- playerscount++
- if(C.holder && C.holder.level >= 0) // make sure retired admins don't make nt think admins are on
- if(!C.stealth)
- admins = "yes"
- players += "[C.key];"
- else
- players += "[C.fakekey];"
- else
- players += "[C.key];"
- to_chat(F, "{\"mode\":\"[mode]\",\"players\" : \"[players]\",\"playercount\" : \"[playerscount]\",\"admin\" : \"[admins]\",\"time\" : \"[time2text(world.realtime,"MM/DD - hh:mm")]\"}")
- fcopy("info.json","[jsonpath]/info.json")
-
-/proc/switchmap(newmap,newpath)
- var/oldmap
- var/obj/mapinfo/M = locate()
-
- if(M)
- oldmap = M.mapname
-
- else
- message_admins("Did not locate mapinfo object. Go bug the mapper to add a /obj/mapinfo to their map!\n For now, you can probably spawn one manually. If you do, be sure to set it's mapname var correctly, or else you'll just get an error again.")
- return
-
- message_admins("Current map: [oldmap]")
- var/text = file2text(dmepath)
- var/path = "#include \"maps/[oldmap].dmm\""
- var/xpath = "#include \"maps/[newpath].dmm\""
- var/loc = findtext(text,path,1,0)
- if(!loc)
- path = "#include \"maps\\[oldmap].dmm\""
- xpath = "#include \"maps\\[newpath].dmm\""
- loc = findtext(text,path,1,0)
- if(!loc)
- message_admins("Could not find '#include \"maps\\[oldmap].dmm\"' or '\"maps/[oldmap].dmm\"' in the bs12.dme. The mapinfo probably has an incorrect mapname var. Alternatively, could not find the .dme itself, at [dmepath].")
- return
-
- var/rest = copytext(text, loc + length(path))
- text = copytext(text,1,loc)
- text += "\n[xpath]"
- text += rest
-/* for(var/A in lines)
- if(findtext(A,path,1,0))
- lineloc = lines.Find(A,1,0)
- lines[lineloc] = xpath
- to_world("FOUND")*/
- fdel(dmepath)
- var/file = file(dmepath)
- file << text
- message_admins("Compiling...")
- shell("./recompile")
- message_admins("Done")
- world.Reboot("Switching to [newmap]")
-
-/obj/mapinfo
- invisibility = INVISIBILITY_ABSTRACT
- var/mapname = "thismap"
- var/decks = 4
-/proc/GetMapInfo()
-// var/obj/mapinfo/M = locate()
-// Just removing these to try and fix the occasional JSON -> WORLD issue.
-// to_world(M.name)
-// to_world(M.mapname)
-/client/proc/ChangeMap(var/X as text)
- set name = "Change Map"
- set category = "Admin"
- switchmap(X,X)
-/proc/send2adminirc(channel,msg)
- world << channel << " "<< msg
- shell("python nudge.py '[channel]' [msg]")
diff --git a/code/game/machinery/air_alarm.dm b/code/game/machinery/air_alarm.dm
index 0c85dfb797..9843274ad5 100644
--- a/code/game/machinery/air_alarm.dm
+++ b/code/game/machinery/air_alarm.dm
@@ -434,9 +434,9 @@
send_signal(id_tag, list("status"))
/obj/machinery/alarm/proc/set_frequency(new_frequency)
- radio_controller.remove_object(src, frequency)
+ SSradio.remove_object(src, frequency)
frequency = new_frequency
- radio_connection = radio_controller.add_object(src, frequency, RADIO_TO_AIRALARM)
+ radio_connection = SSradio.add_object(src, frequency, RADIO_TO_AIRALARM)
/obj/machinery/alarm/proc/send_signal(var/target, var/list/command)//sends signal 'command' to 'target'. Returns 0 if no radio connection, 1 otherwise
if(!radio_connection)
@@ -497,7 +497,7 @@
update_icon()
/obj/machinery/alarm/proc/post_alert(alert_level)
- var/datum/radio_frequency/frequency = radio_controller.return_frequency(alarm_frequency)
+ var/datum/radio_frequency/frequency = SSradio.return_frequency(alarm_frequency)
if(!frequency)
return
diff --git a/code/game/machinery/atmo_control.dm b/code/game/machinery/atmo_control.dm
index fee07cde30..4fd35cf9e5 100644
--- a/code/game/machinery/atmo_control.dm
+++ b/code/game/machinery/atmo_control.dm
@@ -60,9 +60,9 @@
radio_connection.post_signal(src, signal, radio_filter = RADIO_ATMOSIA)
/obj/machinery/air_sensor/proc/set_frequency(new_frequency)
- radio_controller.remove_object(src, frequency)
+ SSradio.remove_object(src, frequency)
frequency = new_frequency
- radio_connection = radio_controller.add_object(src, frequency, RADIO_ATMOSIA)
+ radio_connection = SSradio.add_object(src, frequency, RADIO_ATMOSIA)
/obj/machinery/air_sensor/Initialize(mapload)
. = ..()
@@ -70,8 +70,8 @@
set_frequency(frequency)
/obj/machinery/air_sensor/Destroy()
- if(radio_controller)
- radio_controller.remove_object(src,frequency)
+ if(SSradio)
+ SSradio.remove_object(src,frequency)
. = ..()
/obj/machinery/computer/general_air_control
@@ -86,8 +86,8 @@
circuit = /obj/item/circuitboard/air_management
/obj/machinery/computer/general_air_control/Destroy()
- if(radio_controller)
- radio_controller.remove_object(src, frequency)
+ if(SSradio)
+ SSradio.remove_object(src, frequency)
. = ..()
/obj/machinery/computer/general_air_control/attack_hand(mob/user)
@@ -126,9 +126,9 @@
return data
/obj/machinery/computer/general_air_control/proc/set_frequency(new_frequency)
- radio_controller.remove_object(src, frequency)
+ SSradio.remove_object(src, frequency)
frequency = new_frequency
- radio_connection = radio_controller.add_object(src, frequency, RADIO_ATMOSIA)
+ radio_connection = SSradio.add_object(src, frequency, RADIO_ATMOSIA)
/obj/machinery/computer/general_air_control/Initialize(mapload)
. = ..()
diff --git a/code/game/machinery/atmoalter/meter.dm b/code/game/machinery/atmoalter/meter.dm
index 92c38482cd..62eb28e19a 100644
--- a/code/game/machinery/atmoalter/meter.dm
+++ b/code/game/machinery/atmoalter/meter.dm
@@ -61,7 +61,7 @@
icon_state = "meter4"
if(frequency)
- var/datum/radio_frequency/radio_connection = radio_controller.return_frequency(frequency)
+ var/datum/radio_frequency/radio_connection = SSradio.return_frequency(frequency)
if(!radio_connection) return
diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm
index 23d3d8d703..9d0b7939e2 100644
--- a/code/game/machinery/camera/camera.dm
+++ b/code/game/machinery/camera/camera.dm
@@ -346,7 +346,7 @@
/atom/proc/auto_turn()
//Automatically turns based on nearby walls.
var/turf/simulated/wall/T = null
- for(var/i = 1, i <= 8; i += i)
+ for(var/i = 1, i <= 8, i += i)
T = get_ranged_target_turf(src, i, 1)
if(istype(T))
//If someone knows a better way to do this, let me know. -Giacom
diff --git a/code/game/machinery/computer/arcade.dm b/code/game/machinery/computer/arcade.dm
index c376eab801..6e8fe1da9b 100644
--- a/code/game/machinery/computer/arcade.dm
+++ b/code/game/machinery/computer/arcade.dm
@@ -982,9 +982,9 @@
newcrew = specific
else
if(prob(50))
- newcrew = pick(first_names_male)
+ newcrew = pick(GLOB.first_names_male)
else
- newcrew = pick(first_names_female)
+ newcrew = pick(GLOB.first_names_female)
if(newcrew)
settlers += newcrew
alive++
diff --git a/code/game/machinery/computer/medical.dm b/code/game/machinery/computer/medical.dm
index dd0153c3b7..97b58e93e5 100644
--- a/code/game/machinery/computer/medical.dm
+++ b/code/game/machinery/computer/medical.dm
@@ -487,7 +487,7 @@
if(prob(10/severity))
switch(rand(1,6))
if(1)
- R.fields["name"] = "[pick(pick(first_names_male), pick(first_names_female))] [pick(last_names)]"
+ R.fields["name"] = "[pick(pick(GLOB.first_names_male), pick(GLOB.first_names_female))] [pick(GLOB.last_names)]"
if(2)
R.fields["sex"] = pick("Male", "Female")
if(3)
diff --git a/code/game/machinery/computer/prisonshuttle.dm b/code/game/machinery/computer/prisonshuttle.dm
index ababe6e073..5e2c0e9399 100644
--- a/code/game/machinery/computer/prisonshuttle.dm
+++ b/code/game/machinery/computer/prisonshuttle.dm
@@ -117,7 +117,7 @@ GLOBAL_VAR_INIT(prison_shuttle_timeleft, 0)
/obj/machinery/computer/prison_shuttle/proc/post_signal(var/command)
- var/datum/radio_frequency/frequency = radio_controller.return_frequency(1311)
+ var/datum/radio_frequency/frequency = SSradio.return_frequency(1311)
if(!frequency) return
var/datum/signal/status_signal = new
status_signal.source = src
diff --git a/code/game/machinery/computer/security.dm b/code/game/machinery/computer/security.dm
index eb6daa5865..04ae7ac3c0 100644
--- a/code/game/machinery/computer/security.dm
+++ b/code/game/machinery/computer/security.dm
@@ -498,7 +498,7 @@
if(prob(10/severity))
switch(rand(1,6))
if(1)
- R.fields["name"] = "[pick(pick(first_names_male), pick(first_names_female))] [pick(last_names)]"
+ R.fields["name"] = "[pick(pick(GLOB.first_names_male), pick(GLOB.first_names_female))] [pick(GLOB.last_names)]"
if(2)
R.fields["sex"] = pick("Male", "Female")
if(3)
diff --git a/code/game/machinery/computer/shuttle.dm b/code/game/machinery/computer/shuttle.dm
index d79c4ae186..bd464438f7 100644
--- a/code/game/machinery/computer/shuttle.dm
+++ b/code/game/machinery/computer/shuttle.dm
@@ -10,7 +10,7 @@
/obj/machinery/computer/shuttle/attackby(var/obj/item/card/W as obj, var/mob/user as mob)
if(stat & (BROKEN|NOPOWER)) return
- if ((!( istype(W, /obj/item/card) ) || !( ticker ) || emergency_shuttle.location() || !( user ))) return
+ if ((!( istype(W, /obj/item/card) ) || !( SSticker ) || emergency_shuttle.location() || !( user ))) return
if (istype(W, /obj/item/card/id)||istype(W, /obj/item/pda))
if (istype(W, /obj/item/pda))
var/obj/item/pda/pda = W
diff --git a/code/game/machinery/computer/skills.dm b/code/game/machinery/computer/skills.dm
index fce51ce7cb..0e959f8d5d 100644
--- a/code/game/machinery/computer/skills.dm
+++ b/code/game/machinery/computer/skills.dm
@@ -375,7 +375,7 @@
if(prob(10/severity))
switch(rand(1,6))
if(1)
- R.fields["name"] = "[pick(pick(first_names_male), pick(first_names_female))] [pick(last_names)]"
+ R.fields["name"] = "[pick(pick(GLOB.first_names_male), pick(GLOB.first_names_female))] [pick(GLOB.last_names)]"
if(2)
R.fields["sex"] = pick("Male", "Female")
if(3)
diff --git a/code/game/machinery/computer/supply.dm b/code/game/machinery/computer/supply.dm
index e9a6132c57..d610f6a187 100644
--- a/code/game/machinery/computer/supply.dm
+++ b/code/game/machinery/computer/supply.dm
@@ -488,7 +488,7 @@
add_fingerprint(ui.user)
/obj/machinery/computer/supplycomp/proc/post_signal(var/command)
- var/datum/radio_frequency/frequency = radio_controller.return_frequency(1435)
+ var/datum/radio_frequency/frequency = SSradio.return_frequency(1435)
if(!frequency) return
diff --git a/code/game/machinery/cryopod.dm b/code/game/machinery/cryopod.dm
index 5bd314c23b..47af6f3134 100644
--- a/code/game/machinery/cryopod.dm
+++ b/code/game/machinery/cryopod.dm
@@ -489,8 +489,8 @@
to_despawn.mind.special_role = null
//else
- //if(ticker.mode.name == "AutoTraitor")
- //var/datum/game_mode/traitor/autotraitor/current_mode = ticker.mode
+ //if(SSticker.mode.name == "AutoTraitor")
+ //var/datum/game_mode/traitor/autotraitor/current_mode = SSticker.mode
//current_mode.possible_traitors.Remove(to_despawn)
// Delete them from datacore.
diff --git a/code/game/machinery/doors/airlock_control.dm b/code/game/machinery/doors/airlock_control.dm
index a66d2bc772..01a1038971 100644
--- a/code/game/machinery/doors/airlock_control.dm
+++ b/code/game/machinery/doors/airlock_control.dm
@@ -143,15 +143,15 @@
/obj/machinery/door/airlock/proc/set_frequency(new_frequency)
radio_connection = null
- radio_controller.remove_object(src, frequency)
+ SSradio.remove_object(src, frequency)
frequency = new_frequency
if(new_frequency)
- radio_connection = radio_controller.add_object(src, new_frequency, RADIO_AIRLOCK)
+ radio_connection = SSradio.add_object(src, new_frequency, RADIO_AIRLOCK)
/obj/machinery/door/airlock/Destroy()
- if(frequency && radio_controller)
- radio_controller.remove_object(src,frequency)
+ if(frequency && SSradio)
+ SSradio.remove_object(src,frequency)
return ..()
/obj/machinery/airlock_sensor
@@ -218,17 +218,17 @@
update_icon()
/obj/machinery/airlock_sensor/proc/set_frequency(new_frequency)
- radio_controller.remove_object(src, frequency)
+ SSradio.remove_object(src, frequency)
frequency = new_frequency
- radio_connection = radio_controller.add_object(src, frequency, RADIO_AIRLOCK)
+ radio_connection = SSradio.add_object(src, frequency, RADIO_AIRLOCK)
/obj/machinery/airlock_sensor/Initialize(mapload)
. = ..()
set_frequency(frequency)
/obj/machinery/airlock_sensor/Destroy()
- if(radio_controller)
- radio_controller.remove_object(src,frequency)
+ if(SSradio)
+ SSradio.remove_object(src,frequency)
return ..()
/obj/machinery/airlock_sensor/examine(mob/user, infix, suffix)
@@ -363,9 +363,9 @@
/obj/machinery/access_button/proc/set_frequency(new_frequency)
- radio_controller.remove_object(src, frequency)
+ SSradio.remove_object(src, frequency)
frequency = new_frequency
- radio_connection = radio_controller.add_object(src, frequency, RADIO_AIRLOCK)
+ radio_connection = SSradio.add_object(src, frequency, RADIO_AIRLOCK)
/obj/machinery/access_button/Initialize(mapload)
@@ -373,8 +373,8 @@
set_frequency(frequency)
/obj/machinery/access_button/Destroy()
- if(radio_controller)
- radio_controller.remove_object(src, frequency)
+ if(SSradio)
+ SSradio.remove_object(src, frequency)
return ..()
/obj/machinery/access_button/airlock_interior
diff --git a/code/game/machinery/doors/alarmlock.dm b/code/game/machinery/doors/alarmlock.dm
index 399d11b1fe..76950f5f9a 100644
--- a/code/game/machinery/doors/alarmlock.dm
+++ b/code/game/machinery/doors/alarmlock.dm
@@ -10,14 +10,14 @@
autoclose = 0
/obj/machinery/door/airlock/alarmlock/Destroy()
- if(radio_controller)
- radio_controller.remove_object(src,air_frequency)
+ if(SSradio)
+ SSradio.remove_object(src,air_frequency)
. = ..()
/obj/machinery/door/airlock/alarmlock/Initialize(mapload)
. = ..()
- radio_controller.remove_object(src, air_frequency)
- air_connection = radio_controller.add_object(src, air_frequency, RADIO_TO_AIRALARM)
+ SSradio.remove_object(src, air_frequency)
+ air_connection = SSradio.add_object(src, air_frequency, RADIO_TO_AIRALARM)
open()
diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm
index 597e138491..f1fcf8be38 100644
--- a/code/game/machinery/doors/door.dm
+++ b/code/game/machinery/doors/door.dm
@@ -99,12 +99,12 @@
START_MACHINE_PROCESSING(src)
/obj/machinery/door/proc/can_open()
- if(!density || operating || !ticker)
+ if(!density || operating || !SSticker)
return 0
return 1
/obj/machinery/door/proc/can_close()
- if(density || operating || !ticker)
+ if(density || operating || !SSticker)
return 0
return 1
diff --git a/code/game/machinery/doors/windowdoor.dm b/code/game/machinery/doors/windowdoor.dm
index 5880c713bd..db33596538 100644
--- a/code/game/machinery/doors/windowdoor.dm
+++ b/code/game/machinery/doors/windowdoor.dm
@@ -74,7 +74,7 @@
open()
addtimer(CALLBACK(src, PROC_REF(close)), 50)
return
- if (!( ticker ))
+ if (!( SSticker ))
return
if (src.operating)
return
@@ -106,7 +106,7 @@
/obj/machinery/door/window/open()
if (operating == 1 || !density) //doors can still open when emag-disabled
return 0
- if (!ticker)
+ if (!SSticker)
return 0
if (!operating) //in case of emag
operating = 1
diff --git a/code/game/machinery/embedded_controller/embedded_controller_base.dm b/code/game/machinery/embedded_controller/embedded_controller_base.dm
index f58515b19e..c2f3bba77e 100644
--- a/code/game/machinery/embedded_controller/embedded_controller_base.dm
+++ b/code/game/machinery/embedded_controller/embedded_controller_base.dm
@@ -89,8 +89,8 @@
. = ..()
/obj/machinery/embedded_controller/radio/Destroy()
- if(radio_controller)
- radio_controller.remove_object(src,frequency)
+ if(SSradio)
+ SSradio.remove_object(src,frequency)
. = ..()
/obj/machinery/embedded_controller/radio/update_icon()
@@ -111,6 +111,6 @@
qdel(signal)
/obj/machinery/embedded_controller/radio/proc/set_frequency(new_frequency)
- radio_controller.remove_object(src, frequency)
+ SSradio.remove_object(src, frequency)
frequency = new_frequency
- radio_connection = radio_controller.add_object(src, frequency, radio_filter)
+ radio_connection = SSradio.add_object(src, frequency, radio_filter)
diff --git a/code/game/machinery/magnet.dm b/code/game/machinery/magnet.dm
index 3743ca2b2c..cceb8e6177 100644
--- a/code/game/machinery/magnet.dm
+++ b/code/game/machinery/magnet.dm
@@ -33,8 +33,8 @@
hide(!T.is_plating())
center = T
- if(radio_controller)
- radio_controller.add_object(src, freq, RADIO_MAGNETS)
+ if(SSradio)
+ SSradio.add_object(src, freq, RADIO_MAGNETS)
magnetic_process()
@@ -182,8 +182,8 @@
pulling = 0
/obj/machinery/magnetic_module/Destroy()
- if(radio_controller)
- radio_controller.remove_object(src, freq)
+ if(SSradio)
+ SSradio.remove_object(src, freq)
. = ..()
/obj/machinery/magnetic_controller
@@ -220,8 +220,8 @@
magnets.Add(M)
- if(radio_controller)
- radio_connection = radio_controller.add_object(src, frequency, RADIO_MAGNETS)
+ if(SSradio)
+ radio_connection = SSradio.add_object(src, frequency, RADIO_MAGNETS)
if(path) // check for default path
@@ -390,6 +390,6 @@
// there doesn't HAVE to be separators but it makes paths syntatically visible
/obj/machinery/magnetic_controller/Destroy()
- if(radio_controller)
- radio_controller.remove_object(src, frequency)
+ if(SSradio)
+ SSradio.remove_object(src, frequency)
. = ..()
diff --git a/code/game/machinery/nuclear_bomb.dm b/code/game/machinery/nuclear_bomb.dm
index b16ae0a156..5471070d9f 100644
--- a/code/game/machinery/nuclear_bomb.dm
+++ b/code/game/machinery/nuclear_bomb.dm
@@ -371,8 +371,8 @@ GLOBAL_VAR(bomb_set)
if(!lighthack)
icon_state = "nuclearbomb3"
playsound(src,'sound/machines/Alarm.ogg',100,0,5)
- if(ticker && ticker.mode)
- ticker.mode.explosion_in_progress = 1
+ if(SSticker && SSticker.mode)
+ SSticker.mode.explosion_in_progress = 1
sleep(100)
var/off_station = 0
@@ -383,21 +383,47 @@ GLOBAL_VAR(bomb_set)
else
off_station = 2
- if(ticker)
- if(ticker.mode && ticker.mode.name == "Mercenary")
+ if(SSticker)
+ if(SSticker.mode && SSticker.mode.name == "Mercenary")
var/obj/machinery/computer/shuttle_control/multi/syndicate/syndie_location = locate(/obj/machinery/computer/shuttle_control/multi/syndicate)
if(syndie_location)
- ticker.mode:syndies_didnt_escape = (syndie_location.z > 1 ? 0 : 1) //muskets will make me change this, but it will do for now
- ticker.mode:nuke_off_station = off_station
- ticker.station_explosion_cinematic(off_station,null)
- if(ticker.mode)
- ticker.mode.explosion_in_progress = 0
+ SSticker.mode:syndies_didnt_escape = (syndie_location.z > 1 ? 0 : 1) //muskets will make me change this, but it will do for now
+ SSticker.mode:nuke_off_station = off_station
+
+ switch(off_station)
+ if(0)
+ if(SSticker.mode.name == "mercenary")
+ play_cinematic(/datum/cinematic/nuke/ops_victory)
+ else
+ play_cinematic(/datum/cinematic/nuke/self_destruct)
+
+ // FIXME: Probably a better way
+ for(var/mob/living/M in GLOB.living_mob_list)
+ switch(M.z)
+ if(0) //inside a crate or something
+ var/turf/T = get_turf(M)
+ if(T && (T.z in using_map.station_levels)) //we don't use M.death(0) because it calls a for(/mob) loop and
+ M.health = 0
+ M.set_stat(DEAD)
+ if(1) //on a z-level 1 turf.
+ M.health = 0
+ M.set_stat(DEAD)
+ if(1)
+ if(SSticker.mode.name == "mercenary")
+ play_cinematic(/datum/cinematic/nuke/ops_miss)
+ else
+ play_cinematic(/datum/cinematic/nuke/self_destruct_miss)
+ if(2)
+ play_cinematic(/datum/cinematic/nuke/far_explosion)
+
+ if(SSticker.mode)
+ SSticker.mode.explosion_in_progress = 0
to_world(span_boldannounce("The station was destoyed by the nuclear blast!"))
- ticker.mode.station_was_nuked = (off_station<2) //offstation==1 is a draw. the station becomes irradiated and needs to be evacuated.
+ SSticker.mode.station_was_nuked = (off_station<2) //offstation==1 is a draw. the station becomes irradiated and needs to be evacuated.
//kinda shit but I couldn't get permission to do what I wanted to do.
- if(!ticker.mode.check_finished())//If the mode does not deal with the nuke going off so just reboot because everyone is stuck as is
+ 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
to_world(span_boldannounce("Resetting in 30 seconds!"))
feedback_set_details("end_error","nuke - unhandled ending")
diff --git a/code/game/machinery/status_display.dm b/code/game/machinery/status_display.dm
index 706a2f4939..ec8ae422d8 100644
--- a/code/game/machinery/status_display.dm
+++ b/code/game/machinery/status_display.dm
@@ -53,8 +53,8 @@
var/seclevel = "green"
/obj/machinery/status_display/Destroy()
- if(radio_controller)
- radio_controller.remove_object(src,frequency)
+ if(SSradio)
+ SSradio.remove_object(src,frequency)
return ..()
/obj/machinery/status_display/attackby(I as obj, user as mob)
@@ -67,8 +67,8 @@
// register for radio system
/obj/machinery/status_display/Initialize(mapload)
. = ..()
- if(radio_controller)
- radio_controller.add_object(src, frequency)
+ if(SSradio)
+ SSradio.add_object(src, frequency)
// timed process
/obj/machinery/status_display/process()
diff --git a/code/game/machinery/telecomms/broadcaster.dm b/code/game/machinery/telecomms/broadcaster.dm
index 2330451a4c..015ef3a5bd 100644
--- a/code/game/machinery/telecomms/broadcaster.dm
+++ b/code/game/machinery/telecomms/broadcaster.dm
@@ -379,7 +379,7 @@ GLOBAL_VAR_INIT(message_delay, 0) // To make sure restarting the recentmessages
else if(data == DATA_ANTAG)
for(var/antag_freq in ANTAG_FREQS)
- var/datum/radio_frequency/antag_connection = radio_controller.return_frequency(antag_freq)
+ var/datum/radio_frequency/antag_connection = SSradio.return_frequency(antag_freq)
for (var/obj/item/radio/R in antag_connection.devices["[RADIO_CHAT]"])
if(R.receive_range(antag_freq, level) > -1)
radios |= R
@@ -564,7 +564,7 @@ GLOBAL_VAR_INIT(message_delay, 0) // To make sure restarting the recentmessages
var/mob/living/carbon/human/H = new
M = H
- var/datum/radio_frequency/connection = radio_controller.return_frequency(frequency)
+ var/datum/radio_frequency/connection = SSradio.return_frequency(frequency)
var/display_freq = connection.frequency
@@ -598,7 +598,7 @@ GLOBAL_VAR_INIT(message_delay, 0) // To make sure restarting the recentmessages
else if(data == DATA_ANTAG)
for(var/freq in ANTAG_FREQS)
- var/datum/radio_frequency/antag_connection = radio_controller.return_frequency(freq)
+ var/datum/radio_frequency/antag_connection = SSradio.return_frequency(freq)
for (var/obj/item/radio/R in antag_connection.devices["[RADIO_CHAT]"])
var/turf/position = get_turf(R)
if(position && position.z == level)
diff --git a/code/game/mecha/mech_sensor.dm b/code/game/mecha/mech_sensor.dm
index 1541e22ad8..77a6a64575 100644
--- a/code/game/mecha/mech_sensor.dm
+++ b/code/game/mecha/mech_sensor.dm
@@ -75,10 +75,10 @@
/obj/machinery/mech_sensor/proc/set_frequency(new_frequency)
if(radio_connection)
- radio_controller.remove_object(src, frequency)
+ SSradio.remove_object(src, frequency)
frequency = new_frequency
if(frequency)
- radio_connection = radio_controller.add_object(src, frequency)
+ radio_connection = SSradio.add_object(src, frequency)
/obj/machinery/mech_sensor/receive_signal(datum/signal/signal)
if(stat & NOPOWER)
diff --git a/code/game/objects/buckling.dm b/code/game/objects/buckling.dm
index 0404e30235..9320b64701 100644
--- a/code/game/objects/buckling.dm
+++ b/code/game/objects/buckling.dm
@@ -116,7 +116,7 @@
//Wrapper procs that handle sanity and user feedback
/atom/movable/proc/user_buckle_mob(mob/living/M, mob/user, var/forced = FALSE, var/silent = FALSE)
- if(!ticker)
+ if(!SSticker)
to_chat(user, span_warning("You can't buckle anyone in before the game starts."))
return FALSE // Is this really needed?
if(!user.Adjacent(M) || user.restrained() || user.stat || ispAI(user))
diff --git a/code/game/objects/effects/landmarks.dm b/code/game/objects/effects/landmarks.dm
index ee8495c602..3f3e41f5f0 100644
--- a/code/game/objects/effects/landmarks.dm
+++ b/code/game/objects/effects/landmarks.dm
@@ -268,3 +268,13 @@
. = ..()
new /obj/item/clothing/mask/gas/sexymime(src.loc)
new /obj/item/clothing/under/sexymime(src.loc)
+
+/// Marks the bottom left of the testing zone.
+/// In landmarks.dm and not unit_test.dm so it is always active in the mapping tools.
+/obj/effect/landmark/unit_test_bottom_left
+ name = "unit test zone bottom left"
+
+/// Marks the top right of the testing zone.
+/// In landmarks.dm and not unit_test.dm so it is always active in the mapping tools.
+/obj/effect/landmark/unit_test_top_right
+ name = "unit test zone top right"
diff --git a/code/game/objects/effects/prop/snake.dm b/code/game/objects/effects/prop/snake.dm
index 463c5dc4ca..057e3d5ba4 100644
--- a/code/game/objects/effects/prop/snake.dm
+++ b/code/game/objects/effects/prop/snake.dm
@@ -47,7 +47,7 @@
if(LAZYLEN(iterated_turfs) && iterated_turfs.len > total_turf_memory)
iterated_turfs.Cut(total_turf_memory + 1)
- for(var/direction in list(NORTH, SOUTH, EAST, WEST, NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST) - turn(src.dir,180))
+ for(var/direction in GLOB.alldirs - turn(src.dir,180))
var/turf/T = get_step(src, direction)
if(T in iterated_turfs)
continue
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index ad72c91d4d..89c2e8aa8e 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -904,7 +904,7 @@ modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out.
// testing("[src] (\ref[src]) - Slot: [slot_name], Inhands: [inhands], Worn Icon:[icon2use], Worn State:[state2use], Worn Layer:[layer2use]")
// Send icon data to unit test when it is running, hello old testing(). I'm like, your great great grandkid! THE FUTURE IS NOW OLD MAN!
- #ifdef UNIT_TEST
+ #ifdef UNIT_TESTS
var/mob/living/carbon/human/H = loc
if(ishuman(H))
SEND_SIGNAL(H, COMSIG_UNITTEST_DATA, list("set_slot",slot_name,icon2use,state2use,inhands,type,H.species?.name))
diff --git a/code/game/objects/items/devices/communicator/messaging.dm b/code/game/objects/items/devices/communicator/messaging.dm
index ba35b16549..370fd2d819 100644
--- a/code/game/objects/items/devices/communicator/messaging.dm
+++ b/code/game/objects/items/devices/communicator/messaging.dm
@@ -118,7 +118,7 @@
set name = "Text Communicator"
set desc = "If there is a communicator available, send a text message to it."
- if(ticker.current_state < GAME_STATE_PLAYING)
+ if(SSticker.current_state < GAME_STATE_PLAYING)
to_chat(src, span_danger("The game hasn't started yet!"))
return
diff --git a/code/game/objects/items/devices/communicator/phone.dm b/code/game/objects/items/devices/communicator/phone.dm
index 3d9c95ce12..ee28d8b20f 100644
--- a/code/game/objects/items/devices/communicator/phone.dm
+++ b/code/game/objects/items/devices/communicator/phone.dm
@@ -276,7 +276,7 @@
set name = "Call Communicator"
set desc = "If there is a communicator available, send a request to speak through it. This will reset your respawn timer, if someone picks up."
- if(ticker.current_state < GAME_STATE_PLAYING)
+ if(SSticker.current_state < GAME_STATE_PLAYING)
to_chat(src, span_danger("The game hasn't started yet!"))
return
diff --git a/code/game/objects/items/devices/paicard.dm b/code/game/objects/items/devices/paicard.dm
index 3498e259c9..d8c8eaddd6 100644
--- a/code/game/objects/items/devices/paicard.dm
+++ b/code/game/objects/items/devices/paicard.dm
@@ -502,7 +502,7 @@
if(has_channel_access(card.pai, internal_chan))
channels += ch_name
channels[ch_name] = 1
- secure_radio_connections[ch_name] = radio_controller.add_object(src, radiochannels[ch_name], RADIO_CHAT)
+ secure_radio_connections[ch_name] = SSradio.add_object(src, radiochannels[ch_name], RADIO_CHAT)
/obj/item/paicard/typeb
name = "personal AI device"
diff --git a/code/game/objects/items/devices/radio/electropack.dm b/code/game/objects/items/devices/radio/electropack.dm
index 76500af122..ec099a8536 100644
--- a/code/game/objects/items/devices/radio/electropack.dm
+++ b/code/game/objects/items/devices/radio/electropack.dm
@@ -47,7 +47,7 @@
//..()
if(usr.stat || usr.restrained())
return
- if(((ishuman(usr) && ((!( ticker ) || (ticker && ticker.mode != "monkey")) && usr.contents.Find(src))) || (usr.contents.Find(master) || (in_range(src, usr) && istype(loc, /turf)))))
+ if(((ishuman(usr) && ((!( SSticker ) || (SSticker && SSticker.mode != "monkey")) && usr.contents.Find(src))) || (usr.contents.Find(master) || (in_range(src, usr) && istype(loc, /turf)))))
usr.set_machine(src)
if(href_list["freq"])
var/new_frequency = sanitize_frequency(frequency + text2num(href_list["freq"]))
diff --git a/code/game/objects/items/devices/radio/headset.dm b/code/game/objects/items/devices/radio/headset.dm
index d8bbc60462..7995048000 100644
--- a/code/game/objects/items/devices/radio/headset.dm
+++ b/code/game/objects/items/devices/radio/headset.dm
@@ -95,7 +95,7 @@
for(var/ch_name in channels)
- radio_controller.remove_object(src, radiochannels[ch_name])
+ SSradio.remove_object(src, radiochannels[ch_name])
secure_radio_connections[ch_name] = null
@@ -183,15 +183,15 @@
/obj/item/radio/headset/proc/handle_finalize_recalculatechannels(var/setDescription = FALSE, var/initial_run = FALSE)
PRIVATE_PROC(TRUE)
SHOULD_NOT_OVERRIDE(TRUE)
- if(!radio_controller && initial_run)
+ if(!SSradio && initial_run)
addtimer(CALLBACK(src,PROC_REF(handle_finalize_recalculatechannels),setDescription, FALSE),3 SECONDS)
return
- if(!radio_controller && !initial_run)
+ if(!SSradio && !initial_run)
name = "broken radio headset"
return
for (var/ch_name in channels)
- secure_radio_connections[ch_name] = radio_controller.add_object(src, radiochannels[ch_name], RADIO_CHAT)
+ secure_radio_connections[ch_name] = SSradio.add_object(src, radiochannels[ch_name], RADIO_CHAT)
if(setDescription)
setupRadioDescription()
diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm
index 20aeb06a96..74746d6b56 100644
--- a/code/game/objects/items/devices/radio/radio.dm
+++ b/code/game/objects/items/devices/radio/radio.dm
@@ -45,9 +45,9 @@
var/list/datum/radio_frequency/secure_radio_connections
/obj/item/radio/proc/set_frequency(new_frequency)
- radio_controller.remove_object(src, frequency)
+ SSradio.remove_object(src, frequency)
frequency = new_frequency
- radio_connection = radio_controller.add_object(src, frequency, RADIO_CHAT)
+ radio_connection = SSradio.add_object(src, frequency, RADIO_CHAT)
/obj/item/radio/Initialize(mapload)
. = ..()
@@ -57,7 +57,7 @@
set_frequency(frequency)
for (var/ch_name in channels)
- secure_radio_connections[ch_name] = radio_controller.add_object(src, radiochannels[ch_name], RADIO_CHAT)
+ secure_radio_connections[ch_name] = SSradio.add_object(src, radiochannels[ch_name], RADIO_CHAT)
wires = new(src)
internal_channels = GLOB.default_internal_channels.Copy()
@@ -106,10 +106,10 @@
qdel(wires)
wires = null
GLOB.listening_objects -= src
- if(radio_controller)
- radio_controller.remove_object(src, frequency)
+ if(SSradio)
+ SSradio.remove_object(src, frequency)
for (var/ch_name in channels)
- radio_controller.remove_object(src, radiochannels[ch_name])
+ SSradio.remove_object(src, radiochannels[ch_name])
return ..()
/obj/item/radio/proc/recalculateChannels()
@@ -649,7 +649,7 @@ GLOBAL_DATUM(autospeaker, /mob/living/silicon/ai/announcer)
for(var/ch_name in channels)
- radio_controller.remove_object(src, radiochannels[ch_name])
+ SSradio.remove_object(src, radiochannels[ch_name])
secure_radio_connections[ch_name] = null
@@ -707,24 +707,24 @@ GLOBAL_DATUM(autospeaker, /mob/living/silicon/ai/announcer)
/obj/item/radio/borg/proc/controller_check(var/initial_run = FALSE)
PRIVATE_PROC(TRUE)
SHOULD_NOT_OVERRIDE(TRUE)
- if(!radio_controller && initial_run)
+ if(!SSradio && initial_run)
addtimer(CALLBACK(src,PROC_REF(controller_check), FALSE),3 SECONDS)
return
- if(!radio_controller && !initial_run)
+ if(!SSradio && !initial_run)
name = "broken radio headset"
return
for (var/ch_name in channels)
- secure_radio_connections[ch_name] = radio_controller.add_object(src, radiochannels[ch_name], RADIO_CHAT)
+ secure_radio_connections[ch_name] = SSradio.add_object(src, radiochannels[ch_name], RADIO_CHAT)
/obj/item/radio/proc/config(op)
- if(radio_controller)
+ if(SSradio)
for (var/ch_name in channels)
- radio_controller.remove_object(src, radiochannels[ch_name])
+ SSradio.remove_object(src, radiochannels[ch_name])
secure_radio_connections = new
channels = op
- if(radio_controller)
+ if(SSradio)
for (var/ch_name in op)
- secure_radio_connections[ch_name] = radio_controller.add_object(src, radiochannels[ch_name], RADIO_CHAT)
+ secure_radio_connections[ch_name] = SSradio.add_object(src, radiochannels[ch_name], RADIO_CHAT)
return
/obj/item/radio/off
diff --git a/code/game/objects/structures/window_spawner.dm b/code/game/objects/structures/window_spawner.dm
index 98b239f54a..463d413ba3 100644
--- a/code/game/objects/structures/window_spawner.dm
+++ b/code/game/objects/structures/window_spawner.dm
@@ -27,7 +27,7 @@
return FALSE
/obj/effect/wingrille_spawn/Initialize(mapload)
- if(win_path && ticker && ticker.current_state < GAME_STATE_PLAYING)
+ if(win_path && SSticker && SSticker.current_state < GAME_STATE_PLAYING)
activate()
..()
return INITIALIZE_HINT_QDEL
diff --git a/code/game/objects/unit_testing.dm b/code/game/objects/unit_testing.dm
new file mode 100644
index 0000000000..f3b7a303d9
--- /dev/null
+++ b/code/game/objects/unit_testing.dm
@@ -0,0 +1,38 @@
+/// Code (OBJECTS) in here gets used by the unit tests, but also regular code to test specific things.
+/// But because unit_tests are rather late in the loading order, we have to put objects that get shared in here.
+/// Else OpenDream gets very angry.
+
+// Used to test distillations without hacking the other machinery's code up
+/obj/distilling_tester
+ icon = 'icons/obj/weapons.dmi'
+ icon_state = "cartridge"
+ var/datum/gas_mixture/GM = new()
+ var/current_temp = 0
+
+/obj/distilling_tester/Initialize(mapload)
+ create_reagents(5000,/datum/reagents/distilling)
+ . = ..()
+
+/obj/distilling_tester/return_air()
+ return GM
+
+/obj/distilling_tester/proc/test_distilling(var/decl/chemical_reaction/distilling/D, var/temp_prog)
+ qdel_swap(GM,new())
+ if(D.require_xgm_gas)
+ GM.gas[D.require_xgm_gas] = 100
+ else
+ if(D.rejects_xgm_gas == GAS_N2)
+ GM.gas[GAS_O2] = 100
+ else
+ GM.gas[GAS_N2] = 100
+ if(D.minimum_xgm_pressure)
+ GM.temperature = (D.minimum_xgm_pressure * CELL_VOLUME) / (GM.gas[D.require_xgm_gas] * R_IDEAL_GAS_EQUATION)
+
+ // Try this 10 times, We need to know if something is blocking at multiple temps.
+ // If it passes unit test, it might still be awful to make though, gotta find the right gas mix!
+ current_temp = LERP( D.temp_range[1], D.temp_range[2], temp_prog)
+ reagents.handle_reactions()
+
+/obj/distilling_tester/Destroy(force, ...)
+ qdel_null(GM)
+ . = ..()
diff --git a/code/game/periodic_news.dm b/code/game/periodic_news.dm
index f14f6b4742..15a6ff3e35 100644
--- a/code/game/periodic_news.dm
+++ b/code/game/periodic_news.dm
@@ -102,7 +102,7 @@
round_time = 60 * 60
/proc/process_newscaster()
- check_for_newscaster_updates(ticker.mode.newscaster_announcements)
+ check_for_newscaster_updates(SSticker.mode.newscaster_announcements)
var/global/tmp/announced_news_types = list()
/proc/check_for_newscaster_updates(type)
diff --git a/code/game/response_team.dm b/code/game/response_team.dm
deleted file mode 100644
index e46649b7bb..0000000000
--- a/code/game/response_team.dm
+++ /dev/null
@@ -1,137 +0,0 @@
-//STRIKE TEAMS
-//Thanks to Kilakk for the admin-button portion of this code.
-
-GLOBAL_VAR_INIT(send_emergency_team, 0) // Used for automagic response teams; 'admin_emergency_team' for admin-spawned response teams
-
-GLOBAL_VAR_INIT(ert_base_chance, 10) // Default base chance. Will be incremented by increment ERT chance.
-GLOBAL_VAR(can_call_ert)
-GLOBAL_VAR_INIT(silent_ert, 0)
-
-/client/proc/response_team()
- set name = "Dispatch Emergency Response Team"
- set category = "Fun.Event Kit"
- set desc = "Send an emergency response team to the station"
-
- if(!check_rights_for(src, R_HOLDER))
- to_chat(usr, span_danger("Only administrators may use this command."))
- return
- if(!ticker)
- to_chat(usr, span_danger("The game hasn't started yet!"))
- return
- if(ticker.current_state == 1)
- to_chat(usr, span_danger("The round hasn't started yet!"))
- return
- if(GLOB.send_emergency_team)
- to_chat(usr, span_danger("[using_map.boss_name] has already dispatched an emergency response team!"))
- return
- if(tgui_alert(usr, "Do you want to dispatch an Emergency Response Team?","ERT",list("Yes","No")) != "Yes")
- return
- if(tgui_alert(usr, "Do you want this Response Team to be announced?","ERT",list("Yes","No")) != "Yes")
- GLOB.silent_ert = 1
- if(get_security_level() != "red") // Allow admins to reconsider if the alert level isn't Red
- if(tgui_alert(usr, "The station is not in red alert. Do you still want to dispatch a response team?","ERT",list("Yes","No")) != "Yes")
- return
- if(GLOB.send_emergency_team)
- to_chat(usr, span_danger("Looks like somebody beat you to it!"))
- return
-
- message_admins("[key_name_admin(usr)] is dispatching an Emergency Response Team.", 1)
- admin_chat_message(message = "[key_name(usr)] is dispatching an Emergency Response Team", color = "#CC2222") //VOREStation Add
- log_admin("[key_name(usr)] used Dispatch Response Team.")
- trigger_armed_response_team(1)
-
-/client/verb/JoinResponseTeam()
-
- set name = "Join Response Team"
- set category = "IC.Event"
-
- if(!MayRespawn(1))
- to_chat(usr, span_warning("You cannot join the response team at this time."))
- return
-
- if(isobserver(usr) || isnewplayer(usr))
- if(!GLOB.send_emergency_team)
- to_chat(usr, "No emergency response team is currently being sent.")
- return
- if(jobban_isbanned(usr, JOB_SYNDICATE) || jobban_isbanned(usr, JOB_EMERGENCY_RESPONSE_TEAM) || jobban_isbanned(usr, JOB_SECURITY_OFFICER))
- to_chat(usr, span_danger("You are jobbanned from the emergency reponse team!"))
- return
- if(ert.current_antagonists.len >= ert.hard_cap)
- to_chat(usr, "The emergency response team is already full!")
- return
- ert.create_default(usr)
- else
- to_chat(usr, "You need to be an observer or new player to use this.")
-
-// returns a number of dead players in %
-/proc/percentage_dead()
- var/total = 0
- var/deadcount = 0
- for(var/mob/living/carbon/human/H in GLOB.mob_list)
- if(H.client) // Monkeys and mice don't have a client, amirite?
- if(H.stat == 2) deadcount++
- total++
-
- if(total == 0) return 0
- else return round(100 * deadcount / total)
-
-// counts the number of antagonists in %
-/proc/percentage_antagonists()
- var/total = 0
- var/antagonists = 0
- for(var/mob/living/carbon/human/H in GLOB.mob_list)
- if(is_special_character(H) >= 1)
- antagonists++
- total++
-
- if(total == 0) return 0
- else return round(100 * antagonists / total)
-
-// Increments the ERT chance automatically, so that the later it is in the round,
-// the more likely an ERT is to be able to be called.
-/proc/increment_ert_chance()
- while(GLOB.send_emergency_team == 0) // There is no ERT at the time.
- if(get_security_level() == "green")
- GLOB.ert_base_chance += 1
- if(get_security_level() == "yellow")
- GLOB.ert_base_chance += 1
- if(get_security_level() == "violet")
- GLOB.ert_base_chance += 2
- if(get_security_level() == "orange")
- GLOB.ert_base_chance += 2
- if(get_security_level() == "blue")
- GLOB.ert_base_chance += 2
- if(get_security_level() == "red")
- GLOB.ert_base_chance += 3
- if(get_security_level() == "delta")
- GLOB.ert_base_chance += 10 // Need those big guns
- sleep(600 * 3) // Minute * Number of Minutes
-
-
-/proc/trigger_armed_response_team(var/force = 0)
- if(!GLOB.can_call_ert && !force)
- return
- if(GLOB.send_emergency_team)
- return
-
- var/send_team_chance = GLOB.ert_base_chance // Is incremented by increment_ert_chance.
- send_team_chance += 2*percentage_dead() // the more people are dead, the higher the chance
- send_team_chance += percentage_antagonists() // the more antagonists, the higher the chance
- send_team_chance = min(send_team_chance, 100)
-
- if(force) send_team_chance = 100
-
- // there's only a certain chance a team will be sent
- if(!prob(send_team_chance))
- command_announcement.Announce("It would appear that an emergency response team was requested for [station_name()]. Unfortunately, we were unable to send one at this time.", "[using_map.boss_name]")
- GLOB.can_call_ert = 0 // Only one call per round, ladies.
- return
- if(GLOB.silent_ert == 0)
- command_announcement.Announce("It would appear that an emergency response team was requested for [station_name()]. We will prepare and send one as soon as possible.", "[using_map.boss_name]")
-
- GLOB.can_call_ert = 0 // Only one call per round, gentleman.
- GLOB.send_emergency_team = 1
- consider_ert_load() //VOREStation Add
-
- sleep(600 * 5)
- GLOB.send_emergency_team = 0 // Can no longer join the ERT.
diff --git a/code/game/response_team_vr.dm b/code/game/response_team_vr.dm
deleted file mode 100644
index 3d686cd723..0000000000
--- a/code/game/response_team_vr.dm
+++ /dev/null
@@ -1,11 +0,0 @@
-GLOBAL_VAR(ert_loaded)
-
-/proc/consider_ert_load()
- if(!GLOB.ert_loaded)
- GLOB.ert_loaded = TRUE
- var/datum/map_template/MT = SSmapping.map_templates["Special Area - ERT"]
- if(!istype(MT))
- error("ERT Area is not a valid map template!")
- else
- MT.load_new_z(centered = TRUE)
- log_and_message_admins("Loaded the ERT shuttle just now.")
diff --git a/code/game/socket_talk.dm b/code/game/socket_talk.dm
deleted file mode 100644
index 6cad496937..0000000000
--- a/code/game/socket_talk.dm
+++ /dev/null
@@ -1,26 +0,0 @@
-// Module used for fast interprocess communication between BYOND and other processes
-
-/datum/socket_talk
- var
- enabled = 0
- New()
- ..()
- src.enabled = config.socket_talk
-
- if(enabled)
- LIBCALL("DLLSocket.so","establish_connection")("127.0.0.1","8019")
-
- proc
- send_raw(message)
- if(enabled)
- return LIBCALL("DLLSocket.so","send_message")(message)
- receive_raw()
- if(enabled)
- return LIBCALL("DLLSocket.so","recv_message")()
- send_log(var/log, var/message)
- return send_raw("type=log&log=[log]&message=[message]")
- send_keepalive()
- return send_raw("type=keepalive")
-
-
-var/global/datum/socket_talk/socket_talk
diff --git a/code/game/sound.dm b/code/game/sound.dm
index 19214b639d..124fed98b1 100644
--- a/code/game/sound.dm
+++ b/code/game/sound.dm
@@ -136,7 +136,7 @@
return rand(32000, 55000) //Frequency stuff only works with 45kbps oggs.
/client/proc/playtitlemusic()
- if(!ticker || !SSmedia_tracks.lobby_tracks.len || !media) return
+ if(!SSticker || !SSmedia_tracks.lobby_tracks.len || !media) return
if(prefs?.read_preference(/datum/preference/toggle/play_lobby_music))
var/datum/track/T = pick(SSmedia_tracks.lobby_tracks)
media.push_music(T.url, world.time, 0.35)
diff --git a/code/game/supplyshuttle.dm b/code/game/supplyshuttle.dm
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/code/game/trader_visit_vr.dm b/code/game/trader_visit_vr.dm
deleted file mode 100644
index b9589eb2a5..0000000000
--- a/code/game/trader_visit_vr.dm
+++ /dev/null
@@ -1,11 +0,0 @@
-GLOBAL_VAR(trader_loaded)
-
-/proc/consider_trader_load()
- if(!GLOB.trader_loaded)
- GLOB.trader_loaded = TRUE
- var/datum/map_template/MT = SSmapping.map_templates["Special Area - Salamander Trader"] //was: "Special Area - Trader"
- if(!istype(MT))
- error("Trader is not a valid map template!")
- else
- MT.load_new_z(centered = TRUE)
- log_and_message_admins("Loaded the trade shuttle just now.")
diff --git a/code/game/turfs/space/space.dm b/code/game/turfs/space/space.dm
index b87ef31ff4..c3d8e95c45 100644
--- a/code/game/turfs/space/space.dm
+++ b/code/game/turfs/space/space.dm
@@ -137,7 +137,7 @@
/turf/space/Entered(var/atom/movable/A)
. = ..()
- if(edge && ticker?.mode && !density) // !density so 'fake' space turfs don't fling ghosts everywhere
+ if(edge && SSticker?.mode && !density) // !density so 'fake' space turfs don't fling ghosts everywhere
if(isliving(A))
var/mob/living/L = A
if(L.pulling)
diff --git a/code/game/turfs/turf_changing.dm b/code/game/turfs/turf_changing.dm
index 20fb5e6205..4939573623 100644
--- a/code/game/turfs/turf_changing.dm
+++ b/code/game/turfs/turf_changing.dm
@@ -117,7 +117,7 @@
dynamic_lumcount = old_dynamic_lumcount
- if(SSlighting.subsystem_initialized)
+ if(SSlighting.initialized)
lighting_object = old_lighting_object
directional_opacity = old_directional_opacity
diff --git a/code/game/world.dm b/code/game/world.dm
index 462cc92dfd..014c95c8ec 100644
--- a/code/game/world.dm
+++ b/code/game/world.dm
@@ -1,14 +1,138 @@
#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)
-#define RECOMMENDED_VERSION 513
+/**
+ * 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)
+
+ if(!tracy_initialized)
+ Tracy = new
+#ifdef USE_BYOND_TRACY
+ if(Tracy.enable("USE_BYOND_TRACY defined"))
+ Genesis(tracy_initialized = TRUE)
+ return
+#else
+ var/tracy_enable_reason
+ if(USE_TRACY_PARAMETER in params)
+ tracy_enable_reason = "world.params"
+ if(fexists(TRACY_ENABLE_PATH))
+ tracy_enable_reason ||= "enabled for round"
+ SEND_TEXT(world.log, "[TRACY_ENABLE_PATH] exists, initializing byond-tracy!")
+ fdel(TRACY_ENABLE_PATH)
+ if(!isnull(tracy_enable_reason) && Tracy.enable(tracy_enable_reason))
+ Genesis(tracy_initialized = TRUE)
+ return
+#endif
+
+ Profile(PROFILE_RESTART)
+ Profile(PROFILE_RESTART, type = "sendmaps")
+
+ // Init the debugger first so we can debug Master
+ Debugger = 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()]!")
+
world_startup_time = world.timeofday
rollover_safety_date = world.realtime - world.timeofday // 00:00 today (ish, since floating point error with world.realtime) of today
to_world_log("Map Loading Complete")
//logs
- GLOB.log_directory += time2text(world.realtime, "YYYY/MM-Month/DD-Day/round-hh-mm-ss")
+ //VOREStation Edit Start
+
+ var/override_dir = params[OVERRIDE_LOG_DIRECTORY_PARAMETER]
+ if(override_dir)
+ GLOB.log_directory = "data/logs/[override_dir]"
+ else
+ GLOB.log_directory += time2text(world.realtime, "YYYY/MM-Month/DD-Day/round-hh-mm-ss")
GLOB.diary = start_log("[GLOB.log_directory].log")
GLOB.href_logfile = start_log("[GLOB.log_directory]-hrefs.htm")
GLOB.error_log = start_log("[GLOB.log_directory]-error.log")
@@ -20,16 +144,16 @@ GLOBAL_VAR(restart_counter)
GLOB.changelog_hash = fexists(latest_changelog) ? md5(latest_changelog) : "" //for telling if the changelog has changed recently
to_world_log("Changelog Hash: '[GLOB.changelog_hash]' ([latest_changelog])")
- if(byond_version < RECOMMENDED_VERSION)
- to_world_log("Your server's byond version does not meet the recommended requirements for this server. Please update BYOND")
-
+ // First possible sleep()
InitTgs()
config.Load(params[OVERRIDE_CONFIG_DIRECTORY_PARAMETER])
- load_admins()
-
ConfigLoaded()
+
+ if(NO_INIT_PARAMETER in params)
+ return
+
makeDatumRefLists()
var servername = CONFIG_GET(string/servername)
@@ -52,14 +176,7 @@ GLOBAL_VAR(restart_counter)
src.update_status()
setup_season() //VOREStation Addition
- var/debug_server = world.GetConfig("env", "AUXTOOLS_DEBUG_DLL")
- if (debug_server)
- call_ext(debug_server, "auxtools_init")()
- enable_debugging()
-
- . = ..()
-
-#ifdef UNIT_TEST
+#ifdef UNIT_TESTS
log_unit_test("Unit Tests Enabled. This will destroy the world when testing is complete.")
log_unit_test("If you did not intend to enable this please check code/__defines/unit_testing.dm")
#endif
@@ -81,16 +198,13 @@ GLOBAL_VAR(restart_counter)
spawn(1)
master_controller.setup()
-#ifdef UNIT_TEST
- initialize_unit_tests()
-#endif
+
+ RunUnattendedFunctions()
spawn(3000) //so we aren't adding to the round-start lag
if(CONFIG_GET(flag/ToRban))
ToRban_autoupdate()
-#undef RECOMMENDED_VERSION
-
return
/// Initializes TGS and loads the returned revising info into GLOB.revdata
@@ -107,6 +221,8 @@ GLOBAL_VAR(restart_counter)
// Try to set round ID
SSdbcore.InitializeRound()
+ load_admins(initial = TRUE)
+
//apply a default value to config.python_path, if needed
if (!CONFIG_GET(string/python_path))
if(world.system_type == UNIX)
@@ -118,6 +234,38 @@ GLOBAL_VAR(restart_counter)
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
+
var/world_topic_spam_protect_ip = "0.0.0.0"
var/world_topic_spam_protect_time = world.timeofday
@@ -338,17 +486,33 @@ var/world_topic_spam_protect_time = world.timeofday
text2file("[++GLOB.restart_counter]", RESTART_COUNTER_PATH)
return FALSE
-/world/Reboot(reason = 0, fast_track = FALSE)
- /*spawn(0)
- world << sound(pick('sound/AI/newroundsexy.ogg','sound/misc/apcdestroyed.ogg','sound/misc/bangindonk.ogg')) // random end sounds!! - LastyBatsy
- */
+/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_world(span_boldannounce("[key_name_admin(usr)] has requested an immediate world restart via client side debugging tools"))
-
else
to_world(span_boldannounce("Rebooting world immediately due to host request"))
else
@@ -357,6 +521,10 @@ var/world_topic_spam_protect_time = world.timeofday
if(CONFIG_GET(string/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_GET(string/server)]")
+ #ifdef UNIT_TESTS
+ FinishTestRun()
+ return
+ #else
if(check_hard_reboot())
log_world("World hard rebooted at [time_stamp()]")
//shutdown_logging() // See comment below.
@@ -367,7 +535,19 @@ var/world_topic_spam_protect_time = world.timeofday
TgsReboot()
log_world("World rebooted at [time_stamp()]")
+
+ QDEL_NULL(Tracy)
+ QDEL_NULL(Debugger)
+
+ TgsReboot()
+
..()
+ #endif
+
+/world/Del()
+ QDEL_NULL(Tracy)
+ QDEL_NULL(Debugger)
+ . = ..()
/hook/startup/proc/loadMode()
world.load_mode()
@@ -430,7 +610,7 @@ var/world_topic_spam_protect_time = world.timeofday
var/list/features = list()
- if(ticker)
+ if(SSticker)
if(GLOB.master_mode)
features += GLOB.master_mode
else
@@ -595,10 +775,11 @@ var/failed_old_db_connections = 0
/proc/enable_debugging(mode, port)
CRASH("auxtools not loaded")
-/world/Del()
- var/debug_server = world.GetConfig("env", "AUXTOOLS_DEBUG_DLL")
- if (debug_server)
- call_ext(debug_server, "auxtools_shutdown")()
- . = ..()
+/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
diff --git a/code/genesis_call.dme b/code/genesis_call.dme
new file mode 100644
index 0000000000..2466af786c
--- /dev/null
+++ b/code/genesis_call.dme
@@ -0,0 +1,52 @@
+/*
+
+ You look around.
+
+ There is nothing but naught about you.
+
+ You've come to the end of the world.
+
+ You get a feeling that you really shouldn't be here.
+
+ Ever.
+
+ But with all ends come beginnings.
+
+ As you turn to leave, you spot it out of the corner of your eye.
+
+ Your eye widen in wonder as you look upon the the legendary treasure.
+
+ After all these years of pouring through shitcode
+ your endevours have brought you to...
+
+*/
+
+/**
+ * THE GENESIS CALL
+ *
+ * THE VERY FIRST LINE OF DM CODE TO EXECUTE
+ * Ong this must be done before !!!EVERYTHING!!! else
+ * NO IFS ANDS OR BUTS
+ * it's a hack, not an example of any sort, and DEFINITELY should NOT be emulated
+ * IT JUST HAS TO BE FIRST!!!!!!
+ * If you want to do something in the initialization pipeline
+ * FIRST RTFM IN /code/game/world.dm
+ * AND THEN NEVER RETURN TO THIS PLACE
+ *
+ *
+ *
+ * If you're still here, here's an explanation:
+ * BYOND loves to tell you about its loving spouse /global
+ * But it's actually having a sexy an affair with /static
+ * Specifically statics in procs
+ * Priority is given to these lines of code in REVERSE order of declaration in the .dme
+ * Which is why this file has a funky name
+ * So this is what we use to call world.Genesis()
+ * It's a nameless, no-op function, because it does absolutely nothing
+ * It exists to hold a static var which is initialized to null
+ * It's on /world to hide it from reflection
+ * Painful right? Good, now you share my suffering
+ * Please lock the door on your way out
+ */
+/world/proc/_()
+ var/static/_ = world.Genesis()
diff --git a/code/global_init.dm b/code/global_init.dm
index 1cb0610f81..f23bd8bf6d 100644
--- a/code/global_init.dm
+++ b/code/global_init.dm
@@ -14,15 +14,6 @@ var/global/datum/global_init/init = new ()
Pre-map initialization stuff should go here.
*/
/datum/global_init/New()
-/* VOREStation Removal - Ours is even earlier, in world/New()
- //logs
- GLOB.log_directory += time2text(world.realtime, "YYYY/MM-Month/DD-Day/round-hh-mm-ss")
- diary = file("[GLOB.log_directory].log")
- GLOB.href_logfile = file("[GLOB.log_directory]-hrefs.htm")
- GLOB.error_log = file("[GLOB.log_directory]-error.log")
- GLOB.debug_log = file("[GLOB.log_directory]-debug.log")
- GLOB.debug_log << "[log_end]\n[log_end]\nStarting up. [time_stamp()][log_end]\n---------------------[log_end]"
-*/ //VOREStation Removal End
decls_repository = new()
initialize_integrated_circuits_list()
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index ec53d53514..781a414d6e 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -625,29 +625,69 @@ ADMIN_VERB_ONLY_CONTEXT_MENU(show_player_panel, R_HOLDER, "Show Player Panel", m
/////////////////////////////////////////////////////////////////////////////////////////////////admins2.dm merge
//i.e. buttons/verbs
+#define REGULAR_RESTART "Regular Restart"
+#define REGULAR_RESTART_DELAYED "Regular Restart (with delay)"
+#define HARD_RESTART "Hard Restart (No Delay/Feedback Reason)"
+#define HARDEST_RESTART "Hardest Restart (No actions, just reboot)"
+#define TGS_RESTART "Server Restart (Kill and restart DD)"
+ADMIN_VERB(restart, R_SERVER, "Reboot World", "Restarts the world immediately.", "Server.Game")
+ var/list/options = list(REGULAR_RESTART, REGULAR_RESTART_DELAYED, HARD_RESTART)
-/datum/admins/proc/restart()
- set category = "Server.Game"
- set name = "Restart"
- set desc="Restarts the world"
- if (!check_rights_for(usr.client, R_HOLDER))
+ // this option runs a codepath that can leak db connections because it skips subsystem (specifically SSdbcore) shutdown
+ if(!SSdbcore.IsConnected())
+ options += HARDEST_RESTART
+
+ if(world.TgsAvailable())
+ options += TGS_RESTART;
+
+ if(SSticker.admin_delay_notice)
+ if(alert(user, "Are you sure? An admin has already delayed the round end for the following reason: [SSticker.admin_delay_notice]", "Confirmation", "Yes", "No") != "Yes")
+ return FALSE
+
+ var/result = input(user, "Select reboot method", "World Reboot", options[1]) as null|anything in options
+ if(isnull(result))
return
- var/confirm = alert(usr, "Restart the game world?", "Restart", "Yes", "Cancel") // Not tgui_alert for safety
- if(!confirm || confirm == "Cancel")
+
+ feedback_add_details("admin_verb","R")
+ if(blackbox)
+ blackbox.save_all_data_to_sql()
+
+ var/init_by = "Initiated by [user.holder.fakekey ? "Admin" : user.key]."
+ switch(result)
+ if(REGULAR_RESTART)
+ if(!user.is_localhost())
+ if(alert(user, "Are you sure you want to restart the server?","This server is live", "Restart", "Cancel") != "Restart")
+ return FALSE
+ SSticker.Reboot(init_by, "admin reboot - by [user.key] [user.holder.fakekey ? "(stealth)" : ""]", 10)
+ if(REGULAR_RESTART_DELAYED)
+ var/delay = input("What delay should the restart have (in seconds)?", "Restart Delay", 5) as num|null
+ if(!delay)
+ return FALSE
+ if(!user.is_localhost())
+ if(alert(user,"Are you sure you want to restart the server?","This server is live", "Restart", "Cancel") != "Restart")
+ return FALSE
+ SSticker.Reboot(init_by, "admin reboot - by [user.key] [user.holder.fakekey ? "(stealth)" : ""]", delay * 10)
+ if(HARD_RESTART)
+ to_chat(world, "World reboot - [init_by]")
+ world.Reboot()
+ if(HARDEST_RESTART)
+ to_chat(world, "Hard world reboot - [init_by]")
+ world.Reboot(fast_track = TRUE)
+ if(TGS_RESTART)
+ to_chat(world, "Server restart - [init_by]")
+ world.TgsEndProcess()
+
+#undef REGULAR_RESTART
+#undef REGULAR_RESTART_DELAYED
+#undef HARD_RESTART
+#undef HARDEST_RESTART
+#undef TGS_RESTART
+
+ADMIN_VERB(cancel_reboot, R_SERVER, "Cancel Reboot", "Cancels a pending world reboot.", "Server.Game")
+ if(!SSticker.cancel_reboot(user))
return
- if(confirm == "Yes")
- to_world(span_danger("Restarting world!" ) + span_notice("Initiated by [usr.client.holder.fakekey ? "Admin" : usr.key]!"))
- log_admin("[key_name(usr)] initiated a reboot.")
-
- feedback_set_details("end_error","admin reboot - by [usr.key] [usr.client.holder.fakekey ? "(stealth)" : ""]")
- feedback_add_details("admin_verb","R") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-
- if(blackbox)
- blackbox.save_all_data_to_sql()
-
- sleep(50)
- world.Reboot()
-
+ log_admin("[key_name(user)] cancelled the pending world reboot.")
+ message_admins("[key_name_admin(user)] cancelled the pending world reboot.")
/datum/admins/proc/announce()
set category = "Admin.Chat"
@@ -880,7 +920,7 @@ var/datum/announcement/minor/admin_min_announcer = new
if(!SSticker.start_immediately)
SSticker.start_immediately = TRUE
var/msg = ""
- if(SSticker.current_state == GAME_STATE_INIT)
+ if(SSticker.current_state == GAME_STATE_STARTUP)
msg = " (The server is still setting up, but the round will be started as soon as possible.)"
log_admin("[key_name(usr)] has started the game.[msg]")
message_admins(span_notice("[key_name_admin(usr)] has started the game.[msg]"))
@@ -1017,24 +1057,6 @@ var/datum/announcement/minor/admin_min_announcer = new
message_admins(span_blue("Toggled reviving to [CONFIG_GET(flag/allow_admin_rev)]."))
feedback_add_details("admin_verb","TAR") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-/datum/admins/proc/immreboot()
- set category = "Server.Game"
- set desc="Reboots the server post haste"
- set name="Immediate Reboot"
- if(!check_rights_for(usr.client, R_HOLDER)) return
- if(alert(usr, "Reboot server?","Reboot!","Yes","No") != "Yes") // Not tgui_alert for safety
- return
- to_world(span_filter_system("[span_red(span_bold("Rebooting world!"))] [span_blue("Initiated by [usr.client.holder.fakekey ? "Admin" : usr.key]!")]"))
- log_admin("[key_name(usr)] initiated an immediate reboot.")
-
- feedback_set_details("end_error","immediate admin reboot - by [usr.key] [usr.client.holder.fakekey ? "(stealth)" : ""]")
- feedback_add_details("admin_verb","IR") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-
- if(blackbox)
- blackbox.save_all_data_to_sql()
-
- world.Reboot()
-
/datum/admins/proc/unprison(var/mob/M in GLOB.mob_list)
set category = "Admin.Moderation"
set name = "Unprison"
@@ -1052,7 +1074,7 @@ var/datum/announcement/minor/admin_min_announcer = new
////////////////////////////////////////////////////////////////////////////////////////////////ADMIN HELPER PROCS
/proc/is_special_character(var/character) // returns 1 for special characters and 2 for heroes of gamemode
- if(!ticker || !ticker.mode)
+ if(!SSticker|| !SSticker.mode)
return 0
var/datum/mind/M
if (ismob(character))
@@ -1062,8 +1084,8 @@ var/datum/announcement/minor/admin_min_announcer = new
M = character
if(M)
- if(ticker.mode.antag_templates && ticker.mode.antag_templates.len)
- for(var/datum/antagonist/antag in ticker.mode.antag_templates)
+ if(SSticker.mode.antag_templates && SSticker.mode.antag_templates.len)
+ for(var/datum/antagonist/antag in SSticker.mode.antag_templates)
if(antag.is_antagonist(M))
return 2
if(M.special_role)
@@ -1197,70 +1219,70 @@ var/datum/announcement/minor/admin_min_announcer = new
set desc = "Show the current round configuration."
set name = "Show Game Mode"
- if(!ticker || !ticker.mode)
+ if(!SSticker|| !SSticker.mode)
tgui_alert_async(usr, "Not before roundstart!", "Alert")
return
- var/out = span_large(span_bold("Current mode: [ticker.mode.name] ([ticker.mode.config_tag])")) + " "
+ var/out = span_large(span_bold("Current mode: [SSticker.mode.name] ([SSticker.mode.config_tag])")) + " "
out += ""
- if(ticker.mode.ert_disabled)
- out += span_bold("Emergency Response Teams:") + "disabled"
+ if(SSticker.mode.ert_disabled)
+ out += span_bold("Emergency Response Teams:") + "disabled"
else
- out += span_bold("Emergency Response Teams:") + "enabled"
+ out += span_bold("Emergency Response Teams:") + "enabled"
out += " "
- if(ticker.mode.deny_respawn)
- out += span_bold("Respawning:") + "disallowed"
+ if(SSticker.mode.deny_respawn)
+ out += span_bold("Respawning:") + "disallowed"
else
- out += span_bold("Respawning:") + "allowed"
+ out += span_bold("Respawning:") + "allowed"
out += " "
- out += span_bold("Shuttle delay multiplier:") + " [ticker.mode.shuttle_delay] "
+ out += span_bold("Shuttle delay multiplier:") + " [SSticker.mode.shuttle_delay] "
- if(ticker.mode.auto_recall_shuttle)
- out += span_bold("Shuttle auto-recall:") + " enabled"
+ if(SSticker.mode.auto_recall_shuttle)
+ out += span_bold("Shuttle auto-recall:") + " enabled"
else
- out += span_bold("Shuttle auto-recall:") + " disabled"
+ out += span_bold("Shuttle auto-recall:") + " disabled"
out += "
"
- if(ticker.mode.event_delay_mod_moderate)
- out += span_bold("Moderate event time modifier:") + " [ticker.mode.event_delay_mod_moderate] "
+ if(SSticker.mode.event_delay_mod_moderate)
+ out += span_bold("Moderate event time modifier:") + " [SSticker.mode.event_delay_mod_moderate] "
else
- out += span_bold("Moderate event time modifier:") + " unset "
+ out += span_bold("Moderate event time modifier:") + " unset "
- if(ticker.mode.event_delay_mod_major)
- out += span_bold("Major event time modifier:") + " [ticker.mode.event_delay_mod_major] "
+ if(SSticker.mode.event_delay_mod_major)
+ out += span_bold("Major event time modifier:") + " [SSticker.mode.event_delay_mod_major] "
else
- out += span_bold("Major event time modifier:") + " unset "
+ out += span_bold("Major event time modifier:") + " unset "
out += ""
- if(ticker.mode.antag_tags && ticker.mode.antag_tags.len)
+ if(SSticker.mode.antag_tags && SSticker.mode.antag_tags.len)
out += span_bold("Core antag templates:") + ""
- for(var/antag_tag in ticker.mode.antag_tags)
- out += "[antag_tag]."
+ for(var/antag_tag in SSticker.mode.antag_tags)
+ out += "[antag_tag]."
- if(ticker.mode.round_autoantag)
- out += span_bold("Autotraitor enabled.")
- if(ticker.mode.antag_scaling_coeff > 0)
- out += " (scaling with [ticker.mode.antag_scaling_coeff])"
+ if(SSticker.mode.round_autoantag)
+ out += span_bold("Autotraitor enabled.")
+ if(SSticker.mode.antag_scaling_coeff > 0)
+ out += " (scaling with [SSticker.mode.antag_scaling_coeff])"
else
- out += " (not currently scaling, set a coefficient)"
+ out += " (not currently scaling, set a coefficient)"
out += " "
else
- out += span_bold("Autotraitor disabled.") + " "
+ out += span_bold("Autotraitor disabled.") + " "
out += span_bold("All antag ids:")
- if(ticker.mode.antag_templates && ticker.mode.antag_templates.len)
- for(var/datum/antagonist/antag in ticker.mode.antag_templates)
+ if(SSticker.mode.antag_templates && SSticker.mode.antag_templates.len)
+ for(var/datum/antagonist/antag in SSticker.mode.antag_templates)
antag.update_current_antag_max()
- out += " [antag.id]"
+ out += " [antag.id]"
out += " ([antag.get_antag_count()]/[antag.cur_max]) "
- out += " \[-\] "
+ out += " \[-\] "
else
out += " None."
- out += " \[+\] "
+ out += " \[+\] "
var/datum/browser/popup = new(owner, "edit_mode[src]", "Edit Game Mode")
popup.set_content(out)
@@ -1404,7 +1426,7 @@ var/datum/announcement/minor/admin_min_announcer = new
to_chat(usr, "Error: you are not an admin!")
return
- if(!ticker || !ticker.mode)
+ if(!SSticker|| !SSticker.mode)
to_chat(usr, "Mode has not started.")
return
@@ -1428,12 +1450,12 @@ var/datum/announcement/minor/admin_min_announcer = new
to_chat(usr, "Error: you are not an admin!")
return
- if(!ticker || !ticker.mode)
+ if(!SSticker|| !SSticker.mode)
to_chat(usr, "Mode has not started.")
return
log_and_message_admins("attempting to force mode autospawn.")
- ticker.mode.try_latespawn()
+ SSticker.mode.try_latespawn()
/datum/admins/proc/paralyze_mob(mob/living/H as mob)
set category = "Admin.Events"
diff --git a/code/modules/admin/admin_ranks.dm b/code/modules/admin/admin_ranks.dm
index 2995552033..65cf7949f0 100644
--- a/code/modules/admin/admin_ranks.dm
+++ b/code/modules/admin/admin_ranks.dm
@@ -213,7 +213,11 @@ GLOBAL_PROTECT(protected_ranks)
/// (Re)Loads the admin list.
/// returns TRUE if database admins had to be loaded from the backup json
-/proc/load_admins(no_update)
+/proc/load_admins(no_update, initial = FALSE)
+ if(!initial)
+ if(!global.config.PreConfigReload())
+ return
+
var/dbfail
if(!CONFIG_GET(flag/admin_legacy_system) && !SSdbcore.Connect())
message_admins("Failed to connect to database while loading admins. Loading from backup.")
diff --git a/code/modules/admin/admin_verb_lists_vr.dm b/code/modules/admin/admin_verb_lists_vr.dm
index e1e0334f2a..2ae6e2169a 100644
--- a/code/modules/admin/admin_verb_lists_vr.dm
+++ b/code/modules/admin/admin_verb_lists_vr.dm
@@ -18,7 +18,6 @@ var/list/admin_verbs_admin = list(
/client/proc/player_panel,
/client/proc/hide_verbs, //hides all our adminverbs,
/client/proc/hide_most_verbs, //hides all our hideable adminverbs,
- /client/proc/debug_variables, //allows us to -see- the variables of any instance in the game. +VAREDIT needed to modify,
/client/proc/cmd_check_new_players, //allows us to see every new player,
/client/proc/toggle_view_range, //changes how far we can see,
/client/proc/cmd_admin_pm_context, //right-click adminPM interface,
@@ -103,7 +102,6 @@ var/list/admin_verbs_fun = list(
/datum/admins/proc/cmd_admin_dress,
/client/proc/drop_bomb,
/client/proc/everyone_random,
- /client/proc/cinematic,
/datum/admins/proc/toggle_aliens,
/datum/admins/proc/toggle_space_ninja,
/client/proc/cmd_admin_add_freeform_ai_law,
@@ -152,12 +150,10 @@ var/list/admin_verbs_server = list(
/client/proc/Set_Holiday,
/client/proc/ToRban,
/datum/admins/proc/startnow,
- /datum/admins/proc/restart,
/datum/admins/proc/delay,
/datum/admins/proc/toggleaban,
/datum/admins/proc/togglepersistence,
/client/proc/toggle_log_hrefs,
- /datum/admins/proc/immreboot,
/client/proc/everyone_random,
/datum/admins/proc/toggleAI,
/client/proc/cmd_admin_delete, //delete an instance/object/mob/etc,
@@ -194,7 +190,6 @@ var/list/admin_verbs_debug = list(
/client/proc/air_report,
/client/proc/reload_admins,
/client/proc/reload_eventMs,
- /datum/admins/proc/restart,
/client/proc/print_random_map,
/client/proc/create_random_map,
/client/proc/apply_random_map,
@@ -211,7 +206,6 @@ var/list/admin_verbs_debug = list(
/client/proc/player_panel,
/client/proc/hide_verbs, //hides all our adminverbs,
/client/proc/hide_most_verbs, //hides all our hideable adminverbs,
- /client/proc/debug_variables, //allows us to -see- the variables of any instance in the game. +VAREDIT needed to modify,
/client/proc/cmd_check_new_players, //allows us to see every new player,
/datum/admins/proc/view_runtimes,
// /client/proc/show_gm_status, //We don't use SSgame_master yet.
@@ -254,7 +248,6 @@ var/list/admin_verbs_hideable = list(
/client/proc/object_talk,
/datum/admins/proc/cmd_admin_dress,
/client/proc/drop_bomb,
- /client/proc/cinematic,
/datum/admins/proc/toggle_aliens,
/datum/admins/proc/toggle_space_ninja,
/client/proc/cmd_admin_add_freeform_ai_law,
@@ -266,11 +259,9 @@ var/list/admin_verbs_hideable = list(
/client/proc/Set_Holiday,
/client/proc/ToRban,
/datum/admins/proc/startnow,
- /datum/admins/proc/restart,
/datum/admins/proc/delay,
/datum/admins/proc/toggleaban,
/client/proc/toggle_log_hrefs,
- /datum/admins/proc/immreboot,
/client/proc/everyone_random,
/datum/admins/proc/toggleAI,
/datum/admins/proc/adrev,
@@ -299,14 +290,12 @@ var/list/admin_verbs_hideable = list(
var/list/admin_verbs_mod = list(
/client/proc/cmd_admin_pm_context, //right-click adminPM interface,
/client/proc/cmd_admin_pm_panel, //admin-pm list,
- /client/proc/debug_variables, //allows us to -see- the variables of any instance in the game.,
/datum/admins/proc/PlayerNotes,
/client/proc/admin_ghost, //allows us to ghost/reenter body at will,
/client/proc/player_panel_new, //shows an interface for all players, with links to various panels,
/client/proc/player_panel,
/client/proc/hide_verbs, //hides all our adminverbs,
/client/proc/hide_most_verbs, //hides all our hideable adminverbs,
- /client/proc/debug_variables, //allows us to -see- the variables of any instance in the game. +VAREDIT needed to modify,
/client/proc/cmd_check_new_players, //allows us to see every new player,
/datum/admins/proc/show_player_info,
/datum/admins/proc/show_traitor_panel,
@@ -332,12 +321,10 @@ var/list/admin_verbs_event_manager = list(
/client/proc/player_panel,
/client/proc/hide_verbs, //hides all our adminverbs,
/client/proc/hide_most_verbs, //hides all our hideable adminverbs,
- /client/proc/debug_variables, //allows us to -see- the variables of any instance in the game. +VAREDIT needed to modify,
/client/proc/cmd_check_new_players, //allows us to see every new player,
/datum/admins/proc/show_player_info,
/client/proc/dsay,
/client/proc/cmd_admin_subtle_message,
- /client/proc/debug_variables,
/client/proc/check_antagonists,
/client/proc/aooc,
/datum/admins/proc/paralyze_mob,
@@ -439,9 +426,7 @@ var/list/admin_verbs_event_manager = list(
/datum/admins/proc/capture_map,
/client/proc/Set_Holiday,
/datum/admins/proc/startnow,
- /datum/admins/proc/restart,
/datum/admins/proc/delay,
- /datum/admins/proc/immreboot,
/client/proc/everyone_random,
/client/proc/cmd_admin_delete, //delete an instance/object/mob/etc,
/client/proc/cmd_debug_del_all,
diff --git a/code/modules/admin/modify_robot.dm b/code/modules/admin/modify_robot.dm
index a7732c644d..8d69612bb5 100644
--- a/code/modules/admin/modify_robot.dm
+++ b/code/modules/admin/modify_robot.dm
@@ -390,7 +390,7 @@
target.radio.syndie = 1
target.module.channels += list("[selected_radio_channel]" = 1)
target.radio.channels[selected_radio_channel] = target.module.channels[selected_radio_channel]
- target.radio.secure_radio_connections[selected_radio_channel] = radio_controller.add_object(target.radio, radiochannels[selected_radio_channel], RADIO_CHAT)
+ target.radio.secure_radio_connections[selected_radio_channel] = SSradio.add_object(target.radio, radiochannels[selected_radio_channel], RADIO_CHAT)
return TRUE
if("rem_channel")
var/selected_radio_channel = params["channel"]
@@ -404,7 +404,7 @@
target.radio.channels = list()
for(var/n_chan in target.module.channels)
target.radio.channels[n_chan] = target.module.channels[n_chan]
- radio_controller.remove_object(target.radio, radiochannels[selected_radio_channel])
+ SSradio.remove_object(target.radio, radiochannels[selected_radio_channel])
target.radio.secure_radio_connections -= selected_radio_channel
return TRUE
if("add_component")
diff --git a/code/modules/admin/player_panel.dm b/code/modules/admin/player_panel.dm
index b10ea3ada6..0f2efe3283 100644
--- a/code/modules/admin/player_panel.dm
+++ b/code/modules/admin/player_panel.dm
@@ -390,9 +390,9 @@
/datum/admins/proc/check_antagonists()
- if (ticker && ticker.current_state >= GAME_STATE_PLAYING)
+ if (SSticker && SSticker.current_state >= GAME_STATE_PLAYING)
var/dat = "Round Status
" + span_bold("Round Status") + "
"
- dat += "Current Game Mode: " + span_bold("[ticker.mode.name]") + " "
+ dat += "Current Game Mode: " + span_bold("[SSticker.mode.name]") + " "
dat += "Round Duration: " + span_bold("[roundduration2text()]") + " "
dat += span_bold("Emergency shuttle") + " "
if (!emergency_shuttle.online())
@@ -410,7 +410,7 @@
if (emergency_shuttle.shuttle.moving_status == SHUTTLE_WARMUP)
dat += "Launching now..."
- dat += "[ticker.delay_end ? "End Round Normally" : "Delay Round End"] "
+ dat += "[SSticker.delay_end ? "End Round Normally" : "Delay Round End"] "
dat += ""
for(var/antag_type in GLOB.all_antag_types)
var/datum/antagonist/A = GLOB.all_antag_types[antag_type]
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index 92b19d150d..8298b96a32 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -25,7 +25,7 @@
if(!CheckAdminHref(href, href_list))
return
- if(ticker.mode && ticker.mode.check_antagonists_topic(href, href_list))
+ if(SSticker.mode && SSticker.mode.check_antagonists_topic(href, href_list))
check_antagonists()
return
@@ -66,13 +66,13 @@
else if(href_list["call_shuttle"])
if(!check_rights(R_ADMIN|R_EVENT)) return
- if( ticker.mode.name == "blob" )
+ if( SSticker.mode.name == "blob" )
tgui_alert_async(usr, "You can't call the shuttle during blob!")
return
switch(href_list["call_shuttle"])
if("1")
- if ((!( ticker ) || !emergency_shuttle.location()))
+ if ((!( SSticker ) || !emergency_shuttle.location()))
return
if (emergency_shuttle.can_call())
emergency_shuttle.call_evac()
@@ -80,7 +80,7 @@
message_admins(span_blue("[key_name_admin(usr)] called the Emergency Shuttle to the station."), 1)
if("2")
- if (!( ticker ) || !emergency_shuttle.location())
+ if (!( SSticker ) || !emergency_shuttle.location())
return
if (emergency_shuttle.can_call())
emergency_shuttle.call_evac()
@@ -119,9 +119,9 @@
else if(href_list["delay_round_end"])
if(!check_rights(R_SERVER)) return
- ticker.delay_end = !ticker.delay_end
- log_admin("[key_name(usr)] [ticker.delay_end ? "delayed the round end" : "has made the round end normally"].")
- message_admins(span_blue("[key_name(usr)] [ticker.delay_end ? "delayed the round end" : "has made the round end normally"]."), 1)
+ 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(span_blue("[key_name(usr)] [SSticker.delay_end ? "delayed the round end" : "has made the round end normally"]."), 1)
href_list["secretsadmin"] = "check_antagonist"
else if(href_list["simplemake"])
@@ -832,7 +832,7 @@
else if(href_list["c_mode"])
if(!check_rights(R_ADMIN|R_EVENT)) return
- if(ticker && ticker.mode)
+ if(SSticker && SSticker.mode)
return tgui_alert_async(usr, "The game has already started.")
var/dat = {"What mode do you wish to play?"}
for(var/mode in config.modes)
@@ -845,7 +845,7 @@
else if(href_list["f_secret"])
if(!check_rights(R_ADMIN|R_EVENT)) return
- if(ticker && ticker.mode)
+ if(SSticker && SSticker.mode)
return tgui_alert_async(usr, "The game has already started.")
if(GLOB.master_mode != "secret")
return tgui_alert_async(usr, "The game mode has to be secret!")
@@ -859,7 +859,7 @@
else if(href_list["c_mode2"])
if(!check_rights(R_ADMIN|R_SERVER|R_EVENT)) return
- if (ticker && ticker.mode)
+ if (SSticker && SSticker.mode)
return tgui_alert_async(usr, "The game has already started.")
GLOB.master_mode = href_list["c_mode2"]
log_admin("[key_name(usr)] set the mode as [config.mode_names[GLOB.master_mode]].")
@@ -872,7 +872,7 @@
else if(href_list["f_secret2"])
if(!check_rights(R_ADMIN|R_SERVER|R_EVENT)) return
- if(ticker && ticker.mode)
+ if(SSticker && SSticker.mode)
return tgui_alert_async(usr, "The game has already started.")
if(GLOB.master_mode != "secret")
return tgui_alert_async(usr, "The game mode has to be secret!")
@@ -1495,7 +1495,7 @@
else if(href_list["traitor"])
if(!check_rights(R_ADMIN|R_MOD|R_EVENT)) return
- if(!ticker || !ticker.mode)
+ if(!SSticker|| !SSticker.mode)
tgui_alert_async(usr, "The game hasn't started yet!")
return
diff --git a/code/modules/admin/verbs/cinematic.dm b/code/modules/admin/verbs/cinematic.dm
index db6e44be73..c0a5095f55 100644
--- a/code/modules/admin/verbs/cinematic.dm
+++ b/code/modules/admin/verbs/cinematic.dm
@@ -1,28 +1,10 @@
-/client/proc/cinematic(var/cinematic as anything in list("explosion",null))
- set name = "Cinematic"
- set category = "Fun.Do Not"
- set desc = "Shows a cinematic." // Intended for testing but I thought it might be nice for events on the rare occasion Feel free to comment it out if it's not wanted.
-
- if(!check_rights(R_FUN))
+ADMIN_VERB(cinematic, R_FUN, "Cinematic", "Show a cinematic to all players.", ADMIN_CATEGORY_FUN)
+ var/datum/cinematic/choice = tgui_input_list(
+ user,
+ "Chose a cinematic to play to everyone in the server.",
+ "Choose Cinematic",
+ sortList(subtypesof(/datum/cinematic), GLOBAL_PROC_REF(cmp_typepaths_asc)),
+ )
+ if(!choice || !ispath(choice, /datum/cinematic))
return
-
- if(tgui_alert(usr, "Are you sure you want to run [cinematic]?","Confirmation",list("Yes","No")) != "Yes") return
- if(!ticker) return
- switch(cinematic)
- if("explosion")
- var/input = tgui_alert(usr, "The game will be over. Are you really sure?", "Confirmation", list("Continue","Cancel"))
- if(!input || input == "Cancel")
- return
- var/parameter = tgui_input_number(src,"station_missed = ?","Enter Parameter",0,1,0)
- var/override
- switch(parameter)
- if(1)
- override = tgui_input_list(src,"mode = ?","Enter Parameter", list("mercenary","no override"))
- if(0)
- override = tgui_input_list(src,"mode = ?","Enter Parameter", list("blob","mercenary","AI malfunction","no override"))
- ticker.station_explosion_cinematic(parameter,override)
-
- log_admin("[key_name(src)] launched cinematic \"[cinematic]\"")
- message_admins("[key_name_admin(src)] launched cinematic \"[cinematic]\"", 1)
-
- return
+ play_cinematic(choice, world)
diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm
index 6db2128153..45b5b52d2a 100644
--- a/code/modules/admin/verbs/debug.dm
+++ b/code/modules/admin/verbs/debug.dm
@@ -97,7 +97,7 @@
set category = "Fun.Event Kit"
set name = "Make Robot"
- if(!ticker)
+ if(!SSticker)
tgui_alert_async(usr, "Wait until the game starts")
return
if(ishuman(M))
@@ -112,7 +112,7 @@
set category = "Fun.Event Kit"
set name = "Make Simple Animal"
- if(!ticker)
+ if(!SSticker)
tgui_alert_async(usr, "Wait until the game starts")
return
@@ -164,7 +164,7 @@
set category = "Fun.Event Kit"
set name = "Make Alien"
- if(!ticker)
+ if(!SSticker)
tgui_alert_async(usr, "Wait until the game starts")
return
if(ishuman(M))
@@ -277,7 +277,7 @@
set category = "Admin.Events"
set name = "Grant Full Access"
- if (!ticker)
+ if (!SSticker)
tgui_alert_async(usr, "Wait until the game starts")
return
if (ishuman(M))
@@ -610,7 +610,7 @@ ADMIN_VERB(cmd_assume_direct_control, (R_DEBUG|R_ADMIN|R_EVENT), "Assume Direct
// DNA2 - Admin Hax
/client/proc/cmd_admin_toggle_block(var/mob/M,var/block)
- if(!ticker)
+ if(!SSticker)
tgui_alert_async(usr, "Wait until the game starts")
return
if(istype(M, /mob/living/carbon))
diff --git a/code/modules/admin/verbs/diagnostics.dm b/code/modules/admin/verbs/diagnostics.dm
index 44f369422d..3573d274c1 100644
--- a/code/modules/admin/verbs/diagnostics.dm
+++ b/code/modules/admin/verbs/diagnostics.dm
@@ -79,9 +79,9 @@
set name = "Radio report"
var/output = "Radio Report"
- for (var/fq in radio_controller.frequencies)
+ for (var/fq in SSradio.frequencies)
output += "Freq: [fq] "
- var/datum/radio_frequency/fqs = radio_controller.frequencies[fq]
+ var/datum/radio_frequency/fqs = SSradio.frequencies[fq]
if (!fqs)
output += " ERROR "
continue
diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm
index 83326dbff2..8db00ad4ed 100644
--- a/code/modules/admin/verbs/randomverbs.dm
+++ b/code/modules/admin/verbs/randomverbs.dm
@@ -867,7 +867,7 @@ ADMIN_VERB(respawn_character, (R_ADMIN|R_REJUVINATE), "Spawn Character", "(Re)Sp
set category = "Admin.Events"
set name = "Call Shuttle"
- if ((!( ticker ) || !emergency_shuttle.location()))
+ if ((!( SSticker ) || !emergency_shuttle.location()))
return
if(!check_rights(R_ADMIN)) return
@@ -876,7 +876,7 @@ ADMIN_VERB(respawn_character, (R_ADMIN|R_REJUVINATE), "Spawn Character", "(Re)Sp
if(confirm != "Yes") return
var/choice
- if(ticker.mode.auto_recall_shuttle)
+ if(SSticker.mode.auto_recall_shuttle)
choice = tgui_input_list(usr, "The shuttle will just return if you call it. Call anyway?", "Shuttle Call", list("Confirm", "Cancel"))
if(choice == "Confirm")
emergency_shuttle.auto_recall = 1 //enable auto-recall
@@ -903,7 +903,7 @@ ADMIN_VERB(respawn_character, (R_ADMIN|R_REJUVINATE), "Spawn Character", "(Re)Sp
if(tgui_alert(src, "You sure?", "Confirm", list("Yes", "No")) != "Yes") return
- if(!ticker || !emergency_shuttle.can_recall())
+ if(!SSticker || !emergency_shuttle.can_recall())
return
emergency_shuttle.recall()
@@ -917,7 +917,7 @@ ADMIN_VERB(respawn_character, (R_ADMIN|R_REJUVINATE), "Spawn Character", "(Re)Sp
set category = "Admin.Events"
set name = "Toggle Deny Shuttle"
- if (!ticker)
+ if (!SSticker)
return
if(!check_rights(R_ADMIN)) return
@@ -944,12 +944,12 @@ ADMIN_VERB(respawn_character, (R_ADMIN|R_REJUVINATE), "Spawn Character", "(Re)Sp
if(!check_rights(R_FUN)) return
- if (ticker && ticker.mode)
+ if (SSticker && SSticker.mode)
to_chat(usr, "Nope you can't do this, the game's already started. This only works before rounds!")
return
- if(ticker.random_players)
- ticker.random_players = 0
+ if(CONFIG_GET(flag/force_random_names))
+ CONFIG_SET(flag/force_random_names, FALSE)
message_admins("Admin [key_name_admin(usr)] has disabled \"Everyone is Special\" mode.", 1)
to_chat(usr, "Disabled.")
return
@@ -967,7 +967,7 @@ ADMIN_VERB(respawn_character, (R_ADMIN|R_REJUVINATE), "Spawn Character", "(Re)Sp
to_chat(usr, "Remember: you can always disable the randomness by using the verb again, assuming the round hasn't started yet.")
- ticker.random_players = 1
+ CONFIG_SET(flag/force_random_names, TRUE)
feedback_add_details("admin_verb","MER") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
diff --git a/code/modules/admin/verbs/secrets.dm b/code/modules/admin/verbs/secrets.dm
index 329c89f1bf..1b12176fe3 100644
--- a/code/modules/admin/verbs/secrets.dm
+++ b/code/modules/admin/verbs/secrets.dm
@@ -79,7 +79,7 @@ ADMIN_VERB(secrets, R_HOLDER, "Secrets", "Abuse harder than you ever have before
if("show_traitors_and_objectives") // Not implemented in the UI
holder.holder.check_antagonists()
if("show_game_mode")
- if (ticker.mode) tgui_alert_async(holder, "The game mode is [ticker.mode.name]")
+ if (SSticker.mode) tgui_alert_async(holder, "The game mode is [SSticker.mode.name]")
else tgui_alert_async(holder, "For some reason there's a ticker, but not a game mode")
//Buttons for debug.
diff --git a/code/modules/admin/verbs/striketeam.dm b/code/modules/admin/verbs/striketeam.dm
index a0eadc712d..3d2511ddfe 100644
--- a/code/modules/admin/verbs/striketeam.dm
+++ b/code/modules/admin/verbs/striketeam.dm
@@ -10,7 +10,7 @@ var/const/commandos_possible = 6 //if more Commandos are needed in the future
to_chat(src, "Only administrators may use this command.")
return
- if(!ticker)
+ if(!SSticker)
to_chat(usr, span_red("The game hasn't started yet!"))
return
@@ -54,3 +54,153 @@ var/const/commandos_possible = 6 //if more Commandos are needed in the future
return
team.attempt_random_spawn()
+
+//STRIKE TEAMS
+//Thanks to Kilakk for the admin-button portion of this code.
+
+GLOBAL_VAR_INIT(send_emergency_team, 0) // Used for automagic response teams; 'admin_emergency_team' for admin-spawned response teams
+
+GLOBAL_VAR_INIT(ert_base_chance, 10) // Default base chance. Will be incremented by increment ERT chance.
+GLOBAL_VAR(can_call_ert)
+GLOBAL_VAR_INIT(silent_ert, 0)
+
+/client/proc/response_team()
+ set name = "Dispatch Emergency Response Team"
+ set category = "Fun.Event Kit"
+ set desc = "Send an emergency response team to the station"
+
+ if(!check_rights_for(src, R_HOLDER))
+ to_chat(usr, span_danger("Only administrators may use this command."))
+ return
+ if(!SSticker)
+ to_chat(usr, span_danger("The game hasn't started yet!"))
+ return
+ if(SSticker.current_state == 1)
+ to_chat(usr, span_danger("The round hasn't started yet!"))
+ return
+ if(GLOB.send_emergency_team)
+ to_chat(usr, span_danger("[using_map.boss_name] has already dispatched an emergency response team!"))
+ return
+ if(tgui_alert(usr, "Do you want to dispatch an Emergency Response Team?","ERT",list("Yes","No")) != "Yes")
+ return
+ if(tgui_alert(usr, "Do you want this Response Team to be announced?","ERT",list("Yes","No")) != "Yes")
+ GLOB.silent_ert = 1
+ if(get_security_level() != "red") // Allow admins to reconsider if the alert level isn't Red
+ if(tgui_alert(usr, "The station is not in red alert. Do you still want to dispatch a response team?","ERT",list("Yes","No")) != "Yes")
+ return
+ if(GLOB.send_emergency_team)
+ to_chat(usr, span_danger("Looks like somebody beat you to it!"))
+ return
+
+ message_admins("[key_name_admin(usr)] is dispatching an Emergency Response Team.", 1)
+ admin_chat_message(message = "[key_name(usr)] is dispatching an Emergency Response Team", color = "#CC2222") //VOREStation Add
+ log_admin("[key_name(usr)] used Dispatch Response Team.")
+ trigger_armed_response_team(1)
+
+/client/verb/JoinResponseTeam()
+
+ set name = "Join Response Team"
+ set category = "IC.Event"
+
+ if(!MayRespawn(1))
+ to_chat(usr, span_warning("You cannot join the response team at this time."))
+ return
+
+ if(isobserver(usr) || isnewplayer(usr))
+ if(!GLOB.send_emergency_team)
+ to_chat(usr, "No emergency response team is currently being sent.")
+ return
+ if(jobban_isbanned(usr, JOB_SYNDICATE) || jobban_isbanned(usr, JOB_EMERGENCY_RESPONSE_TEAM) || jobban_isbanned(usr, JOB_SECURITY_OFFICER))
+ to_chat(usr, span_danger("You are jobbanned from the emergency reponse team!"))
+ return
+ if(ert.current_antagonists.len >= ert.hard_cap)
+ to_chat(usr, "The emergency response team is already full!")
+ return
+ ert.create_default(usr)
+ else
+ to_chat(usr, "You need to be an observer or new player to use this.")
+
+// returns a number of dead players in %
+/proc/percentage_dead()
+ var/total = 0
+ var/deadcount = 0
+ for(var/mob/living/carbon/human/H in GLOB.mob_list)
+ if(H.client) // Monkeys and mice don't have a client, amirite?
+ if(H.stat == 2) deadcount++
+ total++
+
+ if(total == 0) return 0
+ else return round(100 * deadcount / total)
+
+// counts the number of antagonists in %
+/proc/percentage_antagonists()
+ var/total = 0
+ var/antagonists = 0
+ for(var/mob/living/carbon/human/H in GLOB.mob_list)
+ if(is_special_character(H) >= 1)
+ antagonists++
+ total++
+
+ if(total == 0) return 0
+ else return round(100 * antagonists / total)
+
+// Increments the ERT chance automatically, so that the later it is in the round,
+// the more likely an ERT is to be able to be called.
+/proc/increment_ert_chance()
+ while(GLOB.send_emergency_team == 0) // There is no ERT at the time.
+ if(get_security_level() == "green")
+ GLOB.ert_base_chance += 1
+ if(get_security_level() == "yellow")
+ GLOB.ert_base_chance += 1
+ if(get_security_level() == "violet")
+ GLOB.ert_base_chance += 2
+ if(get_security_level() == "orange")
+ GLOB.ert_base_chance += 2
+ if(get_security_level() == "blue")
+ GLOB.ert_base_chance += 2
+ if(get_security_level() == "red")
+ GLOB.ert_base_chance += 3
+ if(get_security_level() == "delta")
+ GLOB.ert_base_chance += 10 // Need those big guns
+ sleep(600 * 3) // Minute * Number of Minutes
+
+
+/proc/trigger_armed_response_team(var/force = 0)
+ if(!GLOB.can_call_ert && !force)
+ return
+ if(GLOB.send_emergency_team)
+ return
+
+ var/send_team_chance = GLOB.ert_base_chance // Is incremented by increment_ert_chance.
+ send_team_chance += 2*percentage_dead() // the more people are dead, the higher the chance
+ send_team_chance += percentage_antagonists() // the more antagonists, the higher the chance
+ send_team_chance = min(send_team_chance, 100)
+
+ if(force) send_team_chance = 100
+
+ // there's only a certain chance a team will be sent
+ if(!prob(send_team_chance))
+ command_announcement.Announce("It would appear that an emergency response team was requested for [station_name()]. Unfortunately, we were unable to send one at this time.", "[using_map.boss_name]")
+ GLOB.can_call_ert = 0 // Only one call per round, ladies.
+ return
+ if(GLOB.silent_ert == 0)
+ command_announcement.Announce("It would appear that an emergency response team was requested for [station_name()]. We will prepare and send one as soon as possible.", "[using_map.boss_name]")
+
+ GLOB.can_call_ert = 0 // Only one call per round, gentleman.
+ GLOB.send_emergency_team = 1
+ consider_ert_load() //VOREStation Add
+
+ sleep(600 * 5)
+ GLOB.send_emergency_team = 0 // Can no longer join the ERT.
+
+GLOBAL_VAR(ert_loaded)
+
+/proc/consider_ert_load()
+ if(!GLOB.ert_loaded)
+ GLOB.ert_loaded = TRUE
+ var/datum/map_template/MT = SSmapping.map_templates["Special Area - ERT"]
+ if(!istype(MT))
+ error("ERT Area is not a valid map template!")
+ else
+ MT.load_new_z(centered = TRUE)
+ log_and_message_admins("Loaded the ERT shuttle just now.")
diff --git a/code/game/trader_visit.dm b/code/modules/admin/verbs/trader.dm
similarity index 82%
rename from code/game/trader_visit.dm
rename to code/modules/admin/verbs/trader.dm
index 3fbd8b250a..08a63b4bd8 100644
--- a/code/game/trader_visit.dm
+++ b/code/modules/admin/verbs/trader.dm
@@ -11,10 +11,10 @@ GLOBAL_VAR_INIT(can_call_traders, 1)
if(!holder)
to_chat(usr, span_danger("Only administrators may use this command."))
return
- if(!ticker)
+ if(!SSticker)
to_chat(usr, span_danger("The game hasn't started yet!"))
return
- if(ticker.current_state == 1)
+ if(SSticker.current_state == 1)
to_chat(usr, span_danger("The round hasn't started yet!"))
return
if(GLOB.send_beruang)
@@ -67,3 +67,15 @@ GLOBAL_VAR_INIT(can_call_traders, 1)
sleep(600 * 5)
GLOB.send_beruang = 0 // Can no longer join the traders.
+
+GLOBAL_VAR(trader_loaded)
+
+/proc/consider_trader_load()
+ if(!GLOB.trader_loaded)
+ GLOB.trader_loaded = TRUE
+ var/datum/map_template/MT = SSmapping.map_templates["Special Area - Salamander Trader"] //was: "Special Area - Trader"
+ if(!istype(MT))
+ error("Trader is not a valid map template!")
+ else
+ MT.load_new_z(centered = TRUE)
+ log_and_message_admins("Loaded the trade shuttle just now.")
diff --git a/code/modules/admin/verbs/tripAI.dm b/code/modules/admin/verbs/tripAI.dm
index 2e31e7418b..7a9f0d31d0 100644
--- a/code/modules/admin/verbs/tripAI.dm
+++ b/code/modules/admin/verbs/tripAI.dm
@@ -2,21 +2,21 @@
set category = "Fun.Event Kit"
set name = "Create AI Triumvirate"
- if(ticker.current_state > GAME_STATE_PREGAME)
+ if(SSticker.current_state > GAME_STATE_PREGAME)
to_chat(usr, "This option is currently only usable during pregame. This may change at a later date.")
return
- if(job_master && ticker)
+ if(job_master && SSticker)
var/datum/job/job = job_master.GetJob(JOB_AI)
if(!job)
to_chat(usr, "Unable to locate the AI job")
return
- if(ticker.triai)
- ticker.triai = 0
+ if(GLOB.triai)
+ GLOB.triai = 0
to_chat(usr, "Only one AI will be spawned at round start.")
message_admins(span_blue("[key_name_admin(usr)] has toggled off triple AIs at round start."), 1)
else
- ticker.triai = 1
+ GLOB.triai = 1
to_chat(usr, "There will be an AI Triumvirate at round start.")
message_admins(span_blue("[key_name_admin(usr)] has toggled on triple AIs at round start."), 1)
return
diff --git a/code/modules/ai/__readme.dm b/code/modules/ai/README.md
similarity index 98%
rename from code/modules/ai/__readme.dm
rename to code/modules/ai/README.md
index dc0346e58a..d78ef03f41 100644
--- a/code/modules/ai/__readme.dm
+++ b/code/modules/ai/README.md
@@ -1,5 +1,4 @@
-/*
-[Summary]
+## Summary
This module contains an AI implementation designed to be (at the base level) mobtype-agnostic,
by being held inside a datum instead of being written into the mob directly. More specialized
@@ -11,7 +10,7 @@ When designing a new mob, all that is needed to give a mob an AI is to set
its 'ai_holder_type' variable to the path of the AI that is desired.
-[Seperation]
+## Seperation
In previous iterations of AI systems, the AI is generally written into the mob's code directly,
which has some advantages, but often makes the code rigid, and also tied the speed of the AI
@@ -46,7 +45,7 @@ in the future.
delay, as the ai_holder might not exist yet.
-[Flow of Processing]
+## Flow of Processing
Terrible visual representation here;
AI Subsystem -> Every 0.5s -> /datum/ai_holder/handle_tactics() -> switch(stance)...
@@ -92,7 +91,7 @@ with each other, as opposed to having individual tick counters inside all of
the ai_holder instances. It should be noted that handle_tactics() is always
called first, before handle_strategicals() every two seconds.
-[Process Skipping]
+## Process Skipping
An ai_holder object can choose to enter a 'busy' state, or a 'sleep' state,
in order to avoid processing.
@@ -117,7 +116,7 @@ from processing the other ai_holders until the sleep() finishes.
Delays on the mob typically have set waitfor = FALSE, or spawn() is used.
-[Stances]
+## Stances
The AI has a large number of states that it can be in, called stances.
The AI will act in a specific way depending on which stance it is in,
@@ -138,7 +137,7 @@ module folder and are mostly self contained, however some files instead
deal with general things that other stances may require, such as targeting
or movement.
-[Interfaces]
+## Interfaces
Interfaces are a concept that is used to help bridge the gap between
the ai_holder, and its mob. Because the (base) ai_holder is explicitly
@@ -168,7 +167,7 @@ ranged attack. For simple_mobs, they can if a ranged projectile type was set,
where as for a human mob, it could check if a gun is in a hand. For a borg,
it could check if a gun is inside their current module.
-[Say List]
+## Say List
A /datum/say_list is a very light datum that holds a list of strings for the
AI to have their mob say based on certain conditions, such as when threatening
@@ -181,7 +180,7 @@ mercenaries and fake piloted mecha mobs.
The say_list datum is applied to the mob itself and not held inside the AI datum.
-[Subtypes]
+## Subtypes
Some subtypes of ai_holder are more specialized, but remain compatible with
most mob types. There are many different subtypes that make the AI act different
@@ -196,6 +195,3 @@ To use a specific subtype on a mob, all that is needed is setting the mob's
ai_holder_type to the subtype desired, and it will create that subtype when
the mob is initialize()d. Switching to a subtype 'live' will require additional
effort on the coder.
-
-
-*/
diff --git a/code/modules/assembly/signaler.dm b/code/modules/assembly/signaler.dm
index b3bef467db..916858e340 100644
--- a/code/modules/assembly/signaler.dm
+++ b/code/modules/assembly/signaler.dm
@@ -122,14 +122,14 @@
/obj/item/assembly/signaler/proc/set_frequency(new_frequency)
if(!frequency)
return
- if(!radio_controller)
+ if(!SSradio)
addtimer(CALLBACK(src, PROC_REF(radio_checkup), new_frequency), 2 SECONDS)
return
set_radio(new_frequency)
/obj/item/assembly/signaler/proc/radio_checkup(new_frequency)
PROTECTED_PROC(TRUE)
- if(!radio_controller)
+ if(!SSradio)
return
set_radio(new_frequency)
@@ -137,12 +137,12 @@
/obj/item/assembly/signaler/proc/set_radio(new_frequency)
PROTECTED_PROC(TRUE)
SHOULD_NOT_OVERRIDE(TRUE)
- radio_controller.remove_object(src, frequency)
+ SSradio.remove_object(src, frequency)
frequency = new_frequency
- radio_connection = radio_controller.add_object(src, frequency, RADIO_CHAT)
+ radio_connection = SSradio.add_object(src, frequency, RADIO_CHAT)
/obj/item/assembly/signaler/Destroy()
- if(radio_controller)
- radio_controller.remove_object(src,frequency)
+ if(SSradio)
+ SSradio.remove_object(src,frequency)
frequency = 0
. = ..()
diff --git a/code/modules/asset_cache/assets/spritesheets/robot_icons.dm b/code/modules/asset_cache/assets/spritesheets/robot_icons.dm
index 7297883337..d7b34163aa 100644
--- a/code/modules/asset_cache/assets/spritesheets/robot_icons.dm
+++ b/code/modules/asset_cache/assets/spritesheets/robot_icons.dm
@@ -1,107 +1,90 @@
GLOBAL_LIST_EMPTY(robot_sprite_sheets)
/datum/asset/spritesheet_batched/robot_icons
+ _abstract = /datum/asset/spritesheet_batched/robot_icons
name = "robot_icons"
- fully_generated = TRUE
var/module_type
/datum/asset/spritesheet_batched/robot_icons/standard
name = "robot_icons_standard"
- fully_generated = FALSE
module_type = "Standard"
/datum/asset/spritesheet_batched/robot_icons/service
name = "robot_icons_service"
- fully_generated = FALSE
module_type = "Service"
/datum/asset/spritesheet_batched/robot_icons/clerical
name = "robot_icons_clerical"
- fully_generated = FALSE
module_type = "Clerical"
/datum/asset/spritesheet_batched/robot_icons/clown
name = "robot_icons_clown"
- fully_generated = FALSE
module_type = "Clown"
/datum/asset/spritesheet_batched/robot_icons/command
name = "robot_icons_command"
- fully_generated = FALSE
module_type = "Command"
/datum/asset/spritesheet_batched/robot_icons/research
name = "robot_icons_research"
- fully_generated = FALSE
module_type = "Research"
/datum/asset/spritesheet_batched/robot_icons/miner
name = "robot_icons_miner"
- fully_generated = FALSE
module_type = "Miner"
/datum/asset/spritesheet_batched/robot_icons/crisis
name = "robot_icons_crisis"
- fully_generated = FALSE
module_type = "Crisis"
+/* Modul not in use
/datum/asset/spritesheet_batched/robot_icons/surgeon
name = "robot_icons_surgeon"
module_type = "Surgeon"
+*/
/datum/asset/spritesheet_batched/robot_icons/security
name = "robot_icons_security"
- fully_generated = FALSE
module_type = "Security"
/datum/asset/spritesheet_batched/robot_icons/combat
name = "robot_icons_combat"
- fully_generated = FALSE
module_type = "Combat"
/datum/asset/spritesheet_batched/robot_icons/exploration
name = "robot_icons_exploration"
- fully_generated = FALSE
module_type = "Exploration"
/datum/asset/spritesheet_batched/robot_icons/engineering
name = "robot_icons_engineering"
- fully_generated = FALSE
module_type = "Engineering"
/datum/asset/spritesheet_batched/robot_icons/janitor
name = "robot_icons_janitor"
- fully_generated = FALSE
module_type = "Janitor"
/datum/asset/spritesheet_batched/robot_icons/gravekeeper
name = "robot_icons_gravekeeper"
- fully_generated = FALSE
module_type = "Gravekeeper"
/datum/asset/spritesheet_batched/robot_icons/lost
name = "robot_icons_lost"
- fully_generated = FALSE
module_type = "Lost"
/datum/asset/spritesheet_batched/robot_icons/protector
name = "robot_icons_protector"
- fully_generated = FALSE
module_type = "Protector"
/datum/asset/spritesheet_batched/robot_icons/mechanist
name = "robot_icons_mechanist"
- fully_generated = FALSE
module_type = "Mechanist"
/datum/asset/spritesheet_batched/robot_icons/combat_medic
name = "robot_icons_combat_medic"
- fully_generated = FALSE
module_type = "Combat Medic"
/datum/asset/spritesheet_batched/robot_icons/ninja
name = "robot_icons_ninja"
- fully_generated = FALSE
module_type = "Ninja"
/datum/asset/spritesheet_batched/robot_icons/create_spritesheets()
diff --git a/code/modules/asset_cache/iconforge/batched_spritesheet.dm b/code/modules/asset_cache/iconforge/batched_spritesheet.dm
index db4a0fd5cf..f50370fb22 100644
--- a/code/modules/asset_cache/iconforge/batched_spritesheet.dm
+++ b/code/modules/asset_cache/iconforge/batched_spritesheet.dm
@@ -28,8 +28,6 @@
var/load_immediately = FALSE
/// If we should avoid propogating 'invalid dir' errors from rust-g. Because sometimes, you just don't know what dirs are valid.
var/ignore_dir_errors = FALSE
- /// Avoid propogating 'Could not find associated icon state' errors because we know our input data fucking sucks
- var/ignore_associated_icon_state_errors = FALSE
/// Forces use of the smart cache. This is for unit tests, please respect the config <3
var/force_cache = FALSE
@@ -48,6 +46,7 @@
var/cache_dmi_hashes_json = null
/// Used to prevent async cache refresh jobs from looping on failure.
var/cache_result = null
+ var/getting_genned = FALSE
/datum/asset/spritesheet_batched/proc/should_load_immediately()
#ifdef DO_NOT_DEFER_ASSETS
@@ -78,7 +77,7 @@
if(cached_rustg_version != rustg_version)
log_asset("Invalidated cache for spritesheet_[name] due to rustg updating from [cached_rustg_version] to [rustg_version].")
return CACHE_INVALID
- // Invalidate cache if the DM version changes
+ // Invalidate cache if the DM version changes
var/cached_dm_version = cache_json["dm_version"]
if(isnull(cached_dm_version))
log_asset("Cache for spritesheet_[name] did not contain a dm_version!")
@@ -108,8 +107,8 @@
if (data_out == RUSTG_JOB_ERROR)
CRASH("Spritesheet [name] cache JOB PANIC")
else if(!findtext(data_out, "{", 1, 2))
- rustg_file_write(cache_data, "[GLOB.log_directory]-spritesheet_cache_debug.[name].json")
- rustg_file_write(entries_json, "[GLOB.log_directory]-spritesheet_debug_[name].json")
+ rustg_file_write(cache_data, "[GLOB.log_directory]/spritesheet_cache_debug.[name].json")
+ rustg_file_write(entries_json, "[GLOB.log_directory]/spritesheet_debug_[name].json")
CRASH("Spritesheet [name] cache check UNKNOWN ERROR: [data_out]")
var/result = json_decode(data_out)
var/fail = result["fail_reason"]
@@ -127,6 +126,8 @@
/datum/asset/spritesheet_batched/proc/insert_icon(sprite_name, datum/universal_icon/entry)
if(!istext(sprite_name) || !length(sprite_name))
CRASH("Invalid sprite_name \"[sprite_name]\" given to insert_icon()! Providing non-strings will break icon generation.")
+ if(!istype(entry))
+ CRASH("Invalid type provided to insert_icon()! Value: [entry] (type: [entry?.type])")
entries[sprite_name] = entry.to_list()
/datum/asset/spritesheet_batched/register()
@@ -148,7 +149,6 @@
/// Call insert_icon or insert_all_icons here, building a spritesheet!
/datum/asset/spritesheet_batched/proc/create_spritesheets()
- SHOULD_CALL_PARENT(FALSE)
CRASH("create_spritesheets() not implemented for [type]!")
/datum/asset/spritesheet_batched/proc/insert_all_icons(prefix, icon/I, list/directions, prefix_with_dirs = TRUE)
@@ -159,8 +159,6 @@
directions = list(SOUTH)
for (var/icon_state_name in icon_states(I))
- if(icon_state_name == "")
- icon_state_name = "byond-default"
for (var/direction in directions)
var/prefix2 = (directions.len > 1 && prefix_with_dirs) ? "[dir2text(direction)]-" : ""
insert_icon("[prefix][prefix2][icon_state_name]", uni_icon(I, icon_state_name, direction))
@@ -171,6 +169,8 @@
if(!length(entries))
CRASH("Spritesheet [name] ([type]) is empty! What are you doing?")
+ if(getting_genned)
+ stack_trace("Spritesheet batching has been called twice. This is illegal!")
if(isnull(entries_json))
entries_json = json_encode(entries)
@@ -192,15 +192,16 @@
var/data_out
if(yield || !isnull(job_id))
if(isnull(job_id))
+ getting_genned = TRUE
job_id = rustg_iconforge_generate_async("data/spritesheets/", name, entries_json, do_cache, FALSE, TRUE)
UNTIL((data_out = rustg_iconforge_check(job_id)) != RUSTG_JOB_NO_RESULTS_YET)
+ getting_genned = FALSE
else
- //rustg_file_write(entries_json, "fuckoff.json")
data_out = rustg_iconforge_generate("data/spritesheets/", name, entries_json, do_cache, FALSE, TRUE)
if (data_out == RUSTG_JOB_ERROR)
CRASH("Spritesheet [name] JOB PANIC")
else if(!findtext(data_out, "{", 1, 2))
- rustg_file_write(entries_json, "[GLOB.log_directory]-spritesheet_debug_[name].json")
+ rustg_file_write(entries_json, "[GLOB.log_directory]/spritesheet_debug_[name].json")
CRASH("Spritesheet [name] UNKNOWN ERROR: [data_out]")
var/data = json_decode(data_out)
sizes = data["sizes"]
@@ -209,30 +210,31 @@
var/dmi_hashes = data["dmi_hashes"] // this only contains values if do_cache is TRUE.
for(var/size_id in sizes)
- var/file_path = "data/spritesheets/[name]_[size_id].png"
- var/file_hash = rustg_hash_file("md5", file_path)
- SSassets.transport.register_asset("[name]_[size_id].png", fcopy_rsc(file_path), file_hash)
- var/res_name = "spritesheet_[name].css"
- var/fname = "data/spritesheets/[res_name]"
+ var/png_name = "[name]_[size_id].png"
+ var/file_directory = "data/spritesheets/[png_name]"
+ var/file_hash = rustg_hash_file(RUSTG_HASH_MD5, file_directory)
+ SSassets.transport.register_asset(png_name, fcopy_rsc(file_directory), file_hash)
+ if(CONFIG_GET(flag/save_spritesheets))
+ save_to_logs(file_name = png_name, file_location = file_directory)
+ var/css_name = "spritesheet_[name].css"
+ var/file_directory = "data/spritesheets/[css_name]"
- fdel(fname)
+ fdel(file_directory)
var/css = generate_css()
- rustg_file_write(css, fname)
- var/css_hash = rustg_hash_string("md5", css)
- SSassets.transport.register_asset(res_name, fcopy_rsc(fname), file_hash=css_hash)
+ rustg_file_write(css, file_directory)
+ var/css_hash = rustg_hash_string(RUSTG_HASH_MD5, css)
+ SSassets.transport.register_asset(css_name, fcopy_rsc(file_directory), file_hash=css_hash)
+
+ if(CONFIG_GET(flag/save_spritesheets))
+ save_to_logs(file_name = css_name, file_location = file_directory)
if (do_cache)
write_cache_meta(input_hash, dmi_hashes)
fully_generated = TRUE
// If we were ever in there, remove ourselves
SSasset_loading.dequeue_asset(src)
- if(data["error"])
- var/err = data["error"]
- if(ignore_dir_errors && findtext(err, "is not in the set of valid dirs"))
- return
- if(ignore_associated_icon_state_errors && findtext(err, "Could not find associated icon state"))
- return
- CRASH("Error during spritesheet generation for [name]: [err]")
+ if(data["error"] && !(ignore_dir_errors && findtext(data["error"], "is not in the set of valid dirs")))
+ CRASH("Error during spritesheet generation for [name]: [data["error"]]")
/datum/asset/spritesheet_batched/queued_generation()
realize_spritesheets(yield = TRUE)
@@ -266,7 +268,7 @@
var/size_split = splittext(size_id, "x")
var/width = text2num(size_split[1])
var/height = text2num(size_split[2])
- out += ".[name][size_id]{display:inline-block;width:[width]px;height:[height]px;background-image:url('[get_background_url("[name]_[size_id].png")]');background-repeat: no-repeat;}"
+ out += ".[name][size_id]{display:inline-block;width:[width]px;height:[height]px;background-image:url('[get_background_url("[name]_[size_id].png")]');background-repeat:no-repeat;}"
for (var/sprite_id in sprites)
var/sprite = sprites[sprite_id]
@@ -286,7 +288,8 @@
if(!CONFIG_GET(flag/smart_cache_assets) && !force_cache)
return FALSE
// this is already guaranteed to exist.
- var/css_fname = "data/spritesheets/spritesheet_[name].css"
+ var/css_name = "spritesheet_[name].css"
+ var/css_file_directory = "data/spritesheets/[css_name]"
// sizes gets filled during should_refresh()
for(var/size_id in sizes)
@@ -294,13 +297,16 @@
if(!fexists(fname))
return FALSE
- var/css_hash = rustg_hash_file("md5", css_fname)
- SSassets.transport.register_asset("spritesheet_[name].css", fcopy_rsc(css_fname), file_hash=css_hash)
+ var/css_hash = rustg_hash_file(RUSTG_HASH_MD5, css_file_directory)
+ SSassets.transport.register_asset(css_name, fcopy_rsc(css_file_directory), file_hash=css_hash)
for(var/size_id in sizes)
var/fname = "data/spritesheets/[name]_[size_id].png"
- var/hash = rustg_hash_file("md5", fname)
+ var/hash = rustg_hash_file(RUSTG_HASH_MD5, fname)
SSassets.transport.register_asset("[name]_[size_id].png", fcopy_rsc(fname), file_hash=hash)
+ if(CONFIG_GET(flag/save_spritesheets))
+ save_to_logs(file_name = css_name, file_location = css_file_directory)
+
return TRUE
/// Returns the URL to put in the background:url of the CSS asset
diff --git a/code/modules/asset_cache/iconforge/universal_icon.dm b/code/modules/asset_cache/iconforge/universal_icon.dm
index dda41928d8..1052c105e3 100644
--- a/code/modules/asset_cache/iconforge/universal_icon.dm
+++ b/code/modules/asset_cache/iconforge/universal_icon.dm
@@ -11,9 +11,9 @@
/// Don't instantiate these yourself, use uni_icon.
/datum/universal_icon/New(icon/icon_file, icon_state="", dir=null, frame=null, datum/icon_transformer/transform=null, color=null)
- #ifdef UNIT_TEST
+ #ifdef UNIT_TESTS
// This check is kinda slow and shouldn't fail unless a developer makes a mistake. So it'll get caught in unit tests.
- if(!isicon(icon_file) || !isfile(icon_file) || "[icon_file]" == "/icon")
+ if(!isicon(icon_file) || !isfile(icon_file) || "[icon_file]" == "/icon" || !length("[icon_file]"))
// bad! use 'icons/path_to_dmi.dmi' format only
CRASH("FATAL: universal_icon was provided icon_file: [icon_file] - icons provided to batched spritesheets MUST be DMI files, they cannot be /image, /icon, or other runtime generated icons.")
#endif
@@ -44,10 +44,10 @@
transform.blend_color(color, blend_mode)
return src
-/datum/universal_icon/proc/blend_icon(datum/universal_icon/icon_object, blend_mode)
+/datum/universal_icon/proc/blend_icon(datum/universal_icon/icon_object, blend_mode, x=1, y=1)
if(!transform)
transform = new
- transform.blend_icon(icon_object, blend_mode)
+ transform.blend_icon(icon_object, blend_mode, x, y)
return src
/datum/universal_icon/proc/scale(width, height)
@@ -62,14 +62,116 @@
transform.crop(x1, y1, x2, y2)
return src
-/// Internally performs a crop.
-/datum/universal_icon/proc/shift(dir, amount, icon_width, icon_height)
+/datum/universal_icon/proc/flip(dir)
if(!transform)
transform = new
- var/list/offsets = dir2offset(dir)
- var/shift_x = -offsets[1] * amount
- var/shift_y = -offsets[2] * amount
- transform.crop(1 + shift_x, 1 + shift_y, icon_width + shift_x, icon_height + shift_y)
+ transform.flip(dir)
+ return src
+
+/datum/universal_icon/proc/rotate(angle)
+ if(!transform)
+ transform = new
+ transform.rotate(angle)
+ return src
+
+/datum/universal_icon/proc/shift(dir, offset, wrap=0)
+ if(!transform)
+ transform = new
+ transform.shift(dir, offset, wrap)
+ return src
+
+/datum/universal_icon/proc/swap_color(src_color, dst_color)
+ if(!transform)
+ transform = new
+ transform.swap_color(src_color, dst_color)
+ return src
+
+/datum/universal_icon/proc/draw_box(color, x1, y1, x2=x1, y2=y1)
+ if(!transform)
+ transform = new
+ transform.draw_box(color, x1, y1, x2, y2)
+ return src
+
+/datum/universal_icon/proc/map_colors_inferred(list/color_args)
+ var/num_args = length(color_args)
+ if(num_args <= 20 || num_args >= 16)
+ src.map_colors_rgba(arglist(color_args))
+ else if(num_args <= 12 || num_args >= 9)
+ src.map_colors_rgb(arglist(color_args))
+ else if(num_args == 5)
+ src.map_colors_rgba_hex(arglist(color_args))
+ else if(num_args == 4)
+ // is there alpha in the hex?
+ if(length(color_args[3]) == 7 || length(color_args[3]) == 4)
+ src.map_colors_rgb_hex(arglist(color_args))
+ else
+ src.map_colors_rgba_hex(arglist(color_args))
+ else if(num_args == 3)
+ src.map_colors_rgb_hex(arglist(color_args))
+
+/datum/universal_icon/proc/map_colors_rgba(rr, rg, rb, ra, gr, gg, gb, ga, br, bg, bb, ba, ar, ag, ab, aa, r0=0, g0=0, b0=0, a0=0)
+ if(!transform)
+ transform = new
+ transform.map_colors(rr, rg, rb, ra, gr, gg, gb, ga, br, bg, bb, ba, ar, ag, ab, aa, r0, g0, b0, a0)
+ return src
+
+/datum/universal_icon/proc/map_colors_rgb(rr, rg, rb, gr, gg, gb, br, bg, bb, r0=0, g0=0, b0=0)
+ if(!transform)
+ transform = new
+ transform.map_colors(rr, rg, rb, 0, gr, gg, gb, 0, br, bg, bb, 0, 0, 0, 0, 1, r0, g0, b0, 0)
+ return src
+
+/datum/universal_icon/proc/map_colors_rgb_hex(r_rgb, g_rgb, b_rgb, rgb0=rgb(0,0,0))
+ if(!transform)
+ transform = new
+ var/rr = hex2num(copytext(r_rgb, 2, 4)) / 255
+ var/rg = hex2num(copytext(r_rgb, 4, 6)) / 255
+ var/rb = hex2num(copytext(r_rgb, 6, 8)) / 255
+
+ var/gr = hex2num(copytext(g_rgb, 2, 4)) / 255
+ var/gg = hex2num(copytext(g_rgb, 4, 6)) / 255
+ var/gb = hex2num(copytext(g_rgb, 6, 8)) / 255
+
+ var/br = hex2num(copytext(b_rgb, 2, 4)) / 255
+ var/bg = hex2num(copytext(b_rgb, 4, 6)) / 255
+ var/bb = hex2num(copytext(b_rgb, 6, 8)) / 255
+
+ var/r0 = hex2num(copytext(rgb0, 2, 4)) / 255
+ var/b0 = hex2num(copytext(rgb0, 4, 6)) / 255
+ var/g0 = hex2num(copytext(rgb0, 6, 8)) / 255
+
+ transform.map_colors(rr, rg, rb, 0, gr, gg, gb, 0, br, bg, bb, 0, 0, 0, 0, 1, r0, b0, g0, 0)
+ return src
+
+/datum/universal_icon/proc/map_colors_rgba_hex(r_rgba, g_rgba, b_rgba, a_rgba, rgba0="#00000000")
+ if(!transform)
+ transform = new
+ var/rr = hex2num(copytext(r_rgba, 2, 4)) / 255
+ var/rg = hex2num(copytext(r_rgba, 4, 6)) / 255
+ var/rb = hex2num(copytext(r_rgba, 6, 8)) / 255
+ var/ra = hex2num(copytext(r_rgba, 8, 10)) / 255
+
+ var/gr = hex2num(copytext(g_rgba, 2, 4)) / 255
+ var/gg = hex2num(copytext(g_rgba, 4, 6)) / 255
+ var/gb = hex2num(copytext(g_rgba, 6, 8)) / 255
+ var/ga = hex2num(copytext(g_rgba, 8, 10)) / 255
+
+ var/br = hex2num(copytext(b_rgba, 2, 4)) / 255
+ var/bg = hex2num(copytext(b_rgba, 4, 6)) / 255
+ var/bb = hex2num(copytext(b_rgba, 6, 8)) / 255
+ var/ba = hex2num(copytext(b_rgba, 8, 10)) / 255
+
+ var/ar = hex2num(copytext(a_rgba, 2, 4)) / 255
+ var/ag = hex2num(copytext(a_rgba, 4, 6)) / 255
+ var/ab = hex2num(copytext(a_rgba, 6, 8)) / 255
+ var/aa = hex2num(copytext(a_rgba, 8, 10)) / 255
+
+ var/r0 = hex2num(copytext(rgba0, 2, 4)) / 255
+ var/b0 = hex2num(copytext(rgba0, 4, 6)) / 255
+ var/g0 = hex2num(copytext(rgba0, 6, 8)) / 255
+ var/a0 = hex2num(copytext(rgba0, 8, 10)) / 255
+
+ transform.map_colors(rr, rg, rb, ra, gr, gg, gb, ga, br, bg, bb, ba, ar, ag, ab, aa, r0, b0, g0, a0)
return src
/// Internally performs a color blend.
@@ -118,11 +220,29 @@
if(!istype(icon_object))
stack_trace("Invalid icon found in icon transformer during apply()! [icon_object]")
continue
- target.Blend(icon_object.to_icon(), transform["blend_mode"])
+ target.Blend(icon_object.to_icon(), transform["blend_mode"], transform["x"], transform["y"])
if(RUSTG_ICONFORGE_SCALE)
target.Scale(transform["width"], transform["height"])
if(RUSTG_ICONFORGE_CROP)
target.Crop(transform["x1"], transform["y1"], transform["x2"], transform["y2"])
+ if(RUSTG_ICONFORGE_MAP_COLORS)
+ target.MapColors(
+ transform["rr"], transform["rg"], transform["rb"], transform["ra"],
+ transform["gr"], transform["gg"], transform["gb"], transform["ga"],
+ transform["br"], transform["bg"], transform["bb"], transform["ba"],
+ transform["ar"], transform["ag"], transform["ab"], transform["aa"],
+ transform["r0"], transform["g0"], transform["b0"], transform["a0"],
+ )
+ if(RUSTG_ICONFORGE_FLIP)
+ target.Flip(transform["dir"])
+ if(RUSTG_ICONFORGE_TURN)
+ target.Turn(transform["angle"])
+ if(RUSTG_ICONFORGE_SHIFT)
+ target.Shift(transform["dir"], transform["offset"], transform["wrap"])
+ if(RUSTG_ICONFORGE_SWAP_COLOR)
+ target.SwapColor(transform["src_color"], transform["dst_color"])
+ if(RUSTG_ICONFORGE_DRAW_BOX)
+ target.DrawBox(transform["color"], transform["x1"], transform["y1"], transform["x2"], transform["y2"])
return target
/datum/icon_transformer/proc/copy()
@@ -134,7 +254,7 @@
return new_transformer
/datum/icon_transformer/proc/blend_color(color, blend_mode)
- #ifdef UNIT_TEST
+ #ifdef UNIT_TESTS
if(!istext(color))
CRASH("Invalid color provided to blend_color: [color]")
if(!isnum(blend_mode))
@@ -142,28 +262,77 @@
#endif
transforms += list(list("type" = RUSTG_ICONFORGE_BLEND_COLOR, "color" = color, "blend_mode" = blend_mode))
-/datum/icon_transformer/proc/blend_icon(datum/universal_icon/icon_object, blend_mode)
- #ifdef UNIT_TEST
+/datum/icon_transformer/proc/blend_icon(datum/universal_icon/icon_object, blend_mode, x=1, y=1)
+ #ifdef UNIT_TESTS
// icon_object's type is checked later in to_list
if(!isnum(blend_mode))
CRASH("Invalid blend_mode provided to blend_icon: [blend_mode]")
+ if(!isnum(x))
+ CRASH("Invalid x offset provided to blend_icon: [x]")
+ if(!isnum(y))
+ CRASH("Invalid y offset provided to blend_icon: [y]")
#endif
- transforms += list(list("type" = RUSTG_ICONFORGE_BLEND_ICON, "icon" = icon_object, "blend_mode" = blend_mode))
+ transforms += list(list("type" = RUSTG_ICONFORGE_BLEND_ICON, "icon" = icon_object, "blend_mode" = blend_mode, "x" = x, "y" = y))
/datum/icon_transformer/proc/scale(width, height)
- #ifdef UNIT_TEST
+ #ifdef UNIT_TESTS
if(!isnum(width) || !isnum(height))
CRASH("Invalid arguments provided to scale: [width],[height]")
#endif
transforms += list(list("type" = RUSTG_ICONFORGE_SCALE, "width" = width, "height" = height))
/datum/icon_transformer/proc/crop(x1, y1, x2, y2)
- #ifdef UNIT_TEST
+ #ifdef UNIT_TESTS
if(!isnum(x1) || !isnum(y1) || !isnum(x2) || !isnum(y2))
CRASH("Invalid arguments provided to crop: [x1],[y1],[x2],[y2]")
#endif
transforms += list(list("type" = RUSTG_ICONFORGE_CROP, "x1" = x1, "y1" = y1, "x2" = x2, "y2" = y2))
+/datum/icon_transformer/proc/flip(dir)
+ #ifdef UNIT_TESTS
+ if(!isnum(dir))
+ CRASH("Invalid arguments provided to flip: [dir]")
+ #endif
+ transforms += list(list("type" = RUSTG_ICONFORGE_FLIP, "dir" = dir))
+
+/datum/icon_transformer/proc/rotate(angle)
+ #ifdef UNIT_TESTS
+ if(!isnum(angle))
+ CRASH("Invalid arguments provided to rotate: [angle]")
+ #endif
+ transforms += list(list("type" = RUSTG_ICONFORGE_TURN, "angle" = angle))
+
+/datum/icon_transformer/proc/shift(dir, offset, wrap=FALSE)
+ #ifdef UNIT_TESTS
+ if(!isnum(dir) || !isnum(offset) || (wrap != FALSE && wrap != TRUE))
+ CRASH("Invalid arguments provided to shift: [dir],[offset],[wrap]")
+ #endif
+ transforms += list(list("type" = RUSTG_ICONFORGE_SHIFT, "dir" = dir, "offset" = offset, "wrap" = wrap))
+
+/datum/icon_transformer/proc/swap_color(src_color, dst_color)
+ #ifdef UNIT_TESTS
+ if(!istext(src_color) || !istext(dst_color))
+ CRASH("Invalid arguments provided to swap_color: [src_color],[dst_color]")
+ #endif
+ transforms += list(list("type" = RUSTG_ICONFORGE_SWAP_COLOR, "src_color" = src_color, "dst_color" = dst_color))
+
+/datum/icon_transformer/proc/draw_box(color, x1, y1, x2=x1, y2=y1)
+ #ifdef UNIT_TESTS
+ if(!istext(color) || !isnum(x1) || !isnum(y1) || !isnum(x2) || !isnum(y2))
+ CRASH("Invalid arguments provided to draw_box: [color],[x1],[y1],[x2],[y2]")
+ #endif
+ transforms += list(list("type" = RUSTG_ICONFORGE_DRAW_BOX, "color" = color, "x1" = x1, "y1" = y1, "x2" = x2, "y2" = y2))
+
+/datum/icon_transformer/proc/map_colors(rr, rg, rb, ra, gr, gg, gb, ga, br, bg, bb, ba, ar, ag, ab, aa, r0=0, g0=0, b0=0, a0=0)
+ transforms += list(list(
+ "type" = RUSTG_ICONFORGE_MAP_COLORS,
+ "rr" = rr, "rg" = rg, "rb" = rb, "ra" = ra,
+ "gr" = gr, "gg" = gg, "gb" = gb, "ga" = ga,
+ "br" = br, "bg" = bg, "bb" = bb, "ba" = ba,
+ "ar" = ar, "ag" = ag, "ab" = ab, "aa" = aa,
+ "r0" = r0, "g0" = g0, "b0" = b0, "a0" = a0,
+ ))
+
/// Recursively converts all contained [/datum/universal_icon]s and their associated [/datum/icon_transformer]s into list form so the transforms can be JSON encoded.
/datum/icon_transformer/proc/to_list()
RETURN_TYPE(/list)
@@ -204,26 +373,240 @@
return transform
/// Converts a GAGS item to a universal icon by generating blend operations.
-// /proc/gags_to_universal_icon(obj/item/path)
-// RETURN_TYPE(/datum/universal_icon)
-// if(!ispath(path, /obj/item) || !initial(path.greyscale_config) || !initial(path.greyscale_colors))
-// CRASH("gags_to_universal_icon() received an invalid path!")
-// var/datum/greyscale_config/config = initial(path.greyscale_config)
-// var/colors = initial(path.greyscale_colors)
-// var/datum/universal_icon/entry = SSgreyscale.GetColoredIconEntryByType(config, colors, initial(path.icon_state))
-// return entry
+/*
+/proc/gags_to_universal_icon(atom/path)
+ RETURN_TYPE(/datum/universal_icon)
+ if(!ispath(path, /atom) || !initial(path.greyscale_config) || !initial(path.greyscale_colors))
+ CRASH("gags_to_universal_icon() received an invalid path of \"[path]\"!")
+ var/datum/greyscale_config/config = initial(path.greyscale_config)
+ var/colors = initial(path.greyscale_colors)
+ var/datum/universal_icon/entry = SSgreyscale.GetColoredIconByTypeUniversalIcon(config, colors, path::post_init_icon_state || path::icon_state)
+ return entry
+*/
/// Gets the relevant universal icon for an atom, when displayed in TGUI. (see: icon_state_preview)
/// Supports GAGS items and colored items.
-/proc/get_display_icon_for(atom/A)
- if (!ispath(A, /atom))
+/proc/get_display_icon_for(atom/atom_path)
+ if (!ispath(atom_path, /atom))
return FALSE
- var/icon_file = initial(A.icon)
- var/icon_state = initial(A.icon_state)
- // if(ispath(A, /obj/item))
- // var/obj/item/I = A
- // if(initial(I.icon_state_preview))
- // icon_state = initial(I.icon_state_preview)
- // if(initial(I.greyscale_config) && initial(I.greyscale_colors))
- // return gags_to_universal_icon(I)
- return uni_icon(icon_file, icon_state, color=initial(A.color))
+ var/icon_file = atom_path::icon
+ var/icon_state = atom_path::icon_state
+ /*
+ if(atom_path::greyscale_config && atom_path::greyscale_colors)
+ return gags_to_universal_icon(atom_path)
+ if(ispath(atom_path, /obj))
+ var/obj/obj_path = atom_path
+ if(obj_path::icon_state_preview)
+ icon_state = obj_path::icon_state_preview
+ */
+ return uni_icon(icon_file, icon_state, color=atom_path::color)
+
+/// getFlatIcon for [/datum/universal_icon]s
+/// Still fairly slow for complex appearances due to filesystem operations. Try to avoid using it
+/proc/get_flat_uni_icon(image/appearance, defdir, deficon, defstate, defblend, start = TRUE, parentcolor)
+ // Loop through the underlays, then overlays, sorting them into the layers list
+ #define PROCESS_OVERLAYS_OR_UNDERLAYS(flat, process, base_layer) \
+ for (var/i in 1 to process.len) { \
+ var/image/current = process[i]; \
+ if (!current) { \
+ continue; \
+ } \
+ if (current.plane != FLOAT_PLANE && current.plane != appearance.plane) { \
+ continue; \
+ } \
+ var/current_layer = current.layer; \
+ if (current_layer < 0) { \
+ if (current_layer <= -1000) { \
+ return flat; \
+ } \
+ current_layer = base_layer + appearance.layer + current_layer / 1000; \
+ } \
+ /* If we are using topdown rendering, chop that part off so things layer together as expected */ \
+ if((current_layer >= TOPDOWN_LAYER && current_layer < EFFECTS_LAYER) || current_layer > TOPDOWN_LAYER + EFFECTS_LAYER) { \
+ current_layer -= TOPDOWN_LAYER; \
+ } \
+ for (var/index_to_compare_to in 1 to layers.len) { \
+ var/compare_to = layers[index_to_compare_to]; \
+ if (current_layer < layers[compare_to]) { \
+ layers.Insert(index_to_compare_to, current); \
+ break; \
+ } \
+ } \
+ layers[current] = current_layer; \
+ }
+
+ var/datum/universal_icon/flat = uni_icon('icons/blanks/32x32.dmi', "nothing")
+
+ if(!appearance || appearance.alpha <= 0)
+ return flat
+
+ if(start)
+ if(!deficon)
+ deficon = appearance.icon
+ if(!defstate)
+ defstate = appearance.icon_state
+ if(!defblend)
+ defblend = appearance.blend_mode
+
+ var/should_display = TRUE
+ var/curicon = appearance.icon || deficon
+ var/string_curicon = "[curicon]"
+ var/curstate = appearance.icon_state || defstate
+ // Filter out 'runtime' icons (server-generated RSC cache icons)
+ // Write the icon to the filesystem so it can be used by iconforge
+ if(!isfile(curicon) || !length(string_curicon))
+ var/file_path_tmp = "tmp/uni_icon-tmp-[rand(1, 999)].dmi" // this filename is temporary.
+ fcopy(curicon, file_path_tmp)
+ var/file_hash = rustg_hash_file(RUSTG_HASH_MD5, file_path_tmp)
+ // Use the hash as its new filename - this allows the uni_icon to be smart cached, because the filename will be consistent between runs if the content is the same
+ var/file_path = "tmp/uni_icon-[file_hash].dmi"
+ fcopy(file_path_tmp, file_path)
+ fdel(file_path_tmp) // delete the old one
+ curicon = file(file_path)
+
+ if(!icon_exists(curicon, curstate))
+ if("" in icon_states_fast(curicon)) // BYOND defaulting functionality
+ curstate = ""
+ else
+ should_display = FALSE
+
+ var/curdir = (!appearance.dir || appearance.dir == SOUTH) ? defdir : appearance.dir
+ var/base_icon_dir //We'll use this to get the icon state to display if not null BUT NOT pass it to overlays as the dir we have
+
+ if(should_display)
+ //Determines if there're directionals.
+ if (curdir != SOUTH)
+ // icon states either have 1, 4 or 8 dirs. We only have to check
+ // one of NORTH, EAST or WEST to know that this isn't a 1-dir icon_state since they just have SOUTH.
+ var/list/metadata = icon_metadata(curicon)
+ if(islist(metadata))
+ for(var/list/state_data as anything in metadata["states"])
+ var/name = state_data["name"]
+ if(name != curstate)
+ continue
+ var/dir_count = state_data["dirs"]
+ if(dir_count == 1)
+ base_icon_dir = SOUTH
+ else if(!length(icon_states(icon(curicon, curstate, NORTH))))
+ base_icon_dir = SOUTH
+
+ var/list/icon_dimensions = get_icon_dimensions(curicon)
+ var/icon_width = icon_dimensions["width"]
+ var/icon_height = icon_dimensions["height"]
+ if(icon_width != 32 || icon_height != 32)
+ flat.scale(icon_width, icon_height)
+
+ if(!base_icon_dir)
+ base_icon_dir = curdir
+
+ var/curblend = appearance.blend_mode || defblend
+
+
+ if(appearance.overlays.len || appearance.underlays.len)
+ // Layers will be a sorted list of icons/overlays, based on the order in which they are displayed
+ var/list/layers = list()
+ var/image/copy
+ if(should_display)
+ // Add the atom's icon itself, without pixel_x/y offsets.
+ copy = image(icon=curicon, icon_state=curstate, layer=appearance.layer, dir=base_icon_dir)
+ copy.color = appearance.color
+ copy.alpha = appearance.alpha
+ copy.blend_mode = curblend
+ layers[copy] = appearance.layer
+
+ PROCESS_OVERLAYS_OR_UNDERLAYS(flat, appearance.underlays, 0)
+ PROCESS_OVERLAYS_OR_UNDERLAYS(flat, appearance.overlays, 1)
+
+ var/datum/universal_icon/add // Icon of overlay being added
+
+ var/list/flat_dimensions = get_icon_dimensions(flat)
+ var/flatX1 = 1
+ var/flatX2 = flat_dimensions["width"]
+ var/flatY1 = 1
+ var/flatY2 = flat_dimensions["height"]
+
+ var/addX1 = 0
+ var/addX2 = 0
+ var/addY1 = 0
+ var/addY2 = 0
+
+ if(appearance.color)
+ if(islist(appearance.color))
+ flat.map_colors_inferred(appearance.color)
+ else
+ flat.blend_color(appearance.color, ICON_MULTIPLY)
+
+ if(parentcolor && !(appearance.appearance_flags & RESET_COLOR))
+ if(islist(parentcolor))
+ flat.map_colors_inferred(parentcolor)
+ else
+ flat.blend_color(parentcolor, ICON_MULTIPLY)
+
+ var/next_parentcolor = appearance.color || parentcolor
+
+ for(var/image/layer_image as anything in layers)
+ if(layer_image.alpha == 0)
+ continue
+
+ if(layer_image == copy && length("[layer_image.icon]")) // 'layer_image' is an /image based on the object being flattened, and isn't a 'runtime' icon.
+ curblend = BLEND_OVERLAY
+ add = uni_icon(layer_image.icon, layer_image.icon_state, base_icon_dir)
+ if(appearance.color)
+ if(islist(appearance.color))
+ add.map_colors_inferred(appearance.color)
+ else
+ add.blend_color(appearance.color, ICON_MULTIPLY)
+ else // 'layer_image' is an appearance object.
+ add = get_flat_uni_icon(layer_image, curdir, curicon, curstate, curblend, FALSE, next_parentcolor)
+ if(!add || !length(add.icon_file))
+ continue
+
+ // Find the new dimensions of the flat icon to fit the added overlay
+ var/list/add_dimensions = get_icon_dimensions(add)
+ addX1 = min(flatX1, layer_image.pixel_x + layer_image.pixel_w + 1)
+ addX2 = max(flatX2, layer_image.pixel_x + layer_image.pixel_w + add_dimensions["width"]) // assuming 32x32
+ addY1 = min(flatY1, layer_image.pixel_y + layer_image.pixel_z + 1)
+ addY2 = max(flatY2, layer_image.pixel_y + layer_image.pixel_z + add_dimensions["height"])
+
+ if (
+ addX1 != flatX1 \
+ && addX2 != flatX2 \
+ && addY1 != flatY1 \
+ && addY2 != flatY2 \
+ )
+ // Resize the flattened icon so the new icon fits
+ flat.crop(
+ addX1 - flatX1 + 1,
+ addY1 - flatY1 + 1,
+ addX2 - flatX1 + 1,
+ addY2 - flatY1 + 1
+ )
+
+ flatX1 = addX1
+ flatX2 = addY1
+ flatY1 = addX2
+ flatY2 = addY2
+
+ // Blend the overlay into the flattened icon
+ flat.blend_icon(add, blendMode2iconMode(curblend), layer_image.pixel_x + layer_image.pixel_w + 2 - flatX1, layer_image.pixel_y + layer_image.pixel_z + 2 - flatY1)
+
+ if(appearance.alpha < 255)
+ flat.blend_color(rgb(255, 255, 255, appearance.alpha), ICON_MULTIPLY)
+
+ return flat
+
+ else if(should_display) // There's no overlays.
+ var/datum/universal_icon/final_icon = uni_icon(curicon, curstate, base_icon_dir)
+
+ if (appearance.alpha < 255)
+ final_icon.blend_color(rgb(255,255,255, appearance.alpha), ICON_MULTIPLY)
+
+ if (appearance.color)
+ if (islist(appearance.color))
+ final_icon.map_colors_inferred(appearance.color)
+ else
+ final_icon.blend_color(appearance.color, ICON_MULTIPLY)
+
+ return final_icon
+
+ #undef PROCESS_OVERLAYS_OR_UNDERLAYS
diff --git a/code/modules/asset_cache/transports/asset_transport.dm b/code/modules/asset_cache/transports/asset_transport.dm
index e320742235..77daee2a60 100644
--- a/code/modules/asset_cache/transports/asset_transport.dm
+++ b/code/modules/asset_cache/transports/asset_transport.dm
@@ -85,7 +85,7 @@
/// asset_list - A list of asset filenames to be sent to the client. Can optionally be assoicated with the asset's asset_cache_item datum.
/// Returns TRUE if any assets were sent.
/datum/asset_transport/proc/send_assets(client/client, list/asset_list)
-#if defined(UNIT_TEST)
+#if defined(UNIT_TESTS)
return
#endif
if (!istype(client))
diff --git a/code/modules/awaymissions/zlevel.dm b/code/modules/awaymissions/zlevel.dm
index 1dc4bf6e8d..75e76c2923 100644
--- a/code/modules/awaymissions/zlevel.dm
+++ b/code/modules/awaymissions/zlevel.dm
@@ -1,5 +1,5 @@
/proc/createRandomZlevel()
- #ifdef UNIT_TEST
+ #ifdef UNIT_TESTS
return
#endif
if(GLOB.awaydestinations.len) //crude, but it saves another var! //VOREStation Edit - No loading away missions during CI testing
diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm
index 801dc4c672..07c2275f16 100644
--- a/code/modules/client/client procs.dm
+++ b/code/modules/client/client procs.dm
@@ -206,6 +206,14 @@
/client/proc/_Topic(datum/hsrc, href, list/href_list)
return hsrc.Topic(href, href_list)
+/client/proc/is_localhost()
+ var/static/localhost_addresses = list(
+ "127.0.0.1",
+ "::1",
+ null,
+ )
+ return address in localhost_addresses
+
//This stops files larger than UPLOAD_LIMIT being sent from client to server via input(), client.Import() etc.
/client/AllowUpload(filename, filelength)
if(filelength > UPLOAD_LIMIT)
diff --git a/code/modules/client/preference_setup/general/01_basic.dm b/code/modules/client/preference_setup/general/01_basic.dm
index 899946f2d3..a57a147f59 100644
--- a/code/modules/client/preference_setup/general/01_basic.dm
+++ b/code/modules/client/preference_setup/general/01_basic.dm
@@ -49,9 +49,9 @@
var/firstspace = findtext(pref.real_name, " ")
var/name_length = length(pref.real_name)
if(!firstspace) //we need a surname
- pref.real_name += " [pick(last_names)]"
+ pref.real_name += " [pick(GLOB.last_names)]"
else if(firstspace == name_length)
- pref.real_name += "[pick(last_names)]"
+ pref.real_name += "[pick(GLOB.last_names)]"
character.real_name = pref.real_name
character.name = character.real_name
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index b04b0d816f..1ba6b76bdc 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -428,9 +428,9 @@ var/list/preferences_datums = list()
var/firstspace = findtext(real_name, " ")
var/name_length = length(real_name)
if(!firstspace) //we need a surname
- real_name += " [pick(last_names)]"
+ real_name += " [pick(GLOB.last_names)]"
else if(firstspace == name_length)
- real_name += "[pick(last_names)]"
+ real_name += "[pick(GLOB.last_names)]"
character.real_name = real_name
character.name = character.real_name
if(character.dna)
diff --git a/code/modules/client/preferences/types/game/ui.dm b/code/modules/client/preferences/types/game/ui.dm
index 81c24e3f91..27c140b537 100644
--- a/code/modules/client/preferences/types/game/ui.dm
+++ b/code/modules/client/preferences/types/game/ui.dm
@@ -127,3 +127,9 @@
INVOKE_ASYNC(client, TYPE_VERB_REF(/client, refresh_tgui))
client.tgui_say?.load()
+
+/// Enables flashing the window in your task tray for important events
+/datum/preference/toggle/window_flashing
+ category = PREFERENCE_CATEGORY_GAME_PREFERENCES
+ savefile_key = "windowflashing"
+ savefile_identifier = PREFERENCE_PLAYER
diff --git a/code/modules/client/verbs/suicide.dm b/code/modules/client/verbs/suicide.dm
index 4e2585a1aa..c5154e023a 100644
--- a/code/modules/client/verbs/suicide.dm
+++ b/code/modules/client/verbs/suicide.dm
@@ -7,7 +7,7 @@
to_chat(src, "You're already dead!")
return
- if (!ticker)
+ if (!SSticker)
to_chat(src, "You can't commit suicide before the game starts!")
return
@@ -21,7 +21,7 @@
to_chat(src, "You're already dead!")
return
- if (!ticker)
+ if (!SSticker)
to_chat(src, "You can't commit suicide before the game starts!")
return
diff --git a/code/modules/clothing/accessories/accessory_vr.dm b/code/modules/clothing/accessories/accessory_vr.dm
index 158a3c6f6e..4c87c7fbc2 100644
--- a/code/modules/clothing/accessories/accessory_vr.dm
+++ b/code/modules/clothing/accessories/accessory_vr.dm
@@ -136,17 +136,17 @@
/obj/item/clothing/accessory/collar/shock/Initialize(mapload)
. = ..()
- radio_connection = radio_controller.add_object(src, frequency, RADIO_CHAT) // Makes it so you don't need to change the frequency off of default for it to work.
+ radio_connection = SSradio.add_object(src, frequency, RADIO_CHAT) // Makes it so you don't need to change the frequency off of default for it to work.
/obj/item/clothing/accessory/collar/shock/Destroy() //Clean up your toys when you're done.
- radio_controller.remove_object(src, frequency)
+ SSradio.remove_object(src, frequency)
radio_connection = null //Don't delete this, this is a shared object.
return ..()
/obj/item/clothing/accessory/collar/shock/proc/set_frequency(new_frequency)
- radio_controller.remove_object(src, frequency)
+ SSradio.remove_object(src, frequency)
frequency = new_frequency
- radio_connection = radio_controller.add_object(src, frequency, RADIO_CHAT)
+ radio_connection = SSradio.add_object(src, frequency, RADIO_CHAT)
/obj/item/clothing/accessory/collar/shock/attack_self(mob/user as mob, flag1)
if(!ishuman(user))
diff --git a/code/modules/debugging/debugger.dm b/code/modules/debugging/debugger.dm
new file mode 100644
index 0000000000..6ae3939959
--- /dev/null
+++ b/code/modules/debugging/debugger.dm
@@ -0,0 +1,54 @@
+/// The debugger instance.
+/// This is a GLOBAL_REAL because it initializes before the MC or GLOB.
+/// Really only used to check to see if the debugger is enabled or not,
+/// and to separate debugger-related code into its own thing.
+GLOBAL_REAL(Debugger, /datum/debugger)
+
+/datum/debugger
+ /// Is the debugger enabled?
+ VAR_FINAL/enabled = FALSE
+ /// The error text, if initializing the debugger errored.
+ VAR_FINAL/error
+ /// The path to the auxtools debug DLL, if it sets.
+ /// Defaults to the environmental variable AUXTOOLS_DEBUG_DLL.
+ VAR_FINAL/dll_path
+
+/datum/debugger/New(dll_path)
+ if(!isnull(Debugger))
+ CRASH("Attempted to initialize /datum/debugger when global.Debugger is already set!")
+ Debugger = src
+#ifndef OPENDREAM_REAL
+ src.dll_path = dll_path || world.GetConfig("env", "AUXTOOLS_DEBUG_DLL")
+ enable()
+#endif
+
+/datum/debugger/Destroy()
+#ifndef OPENDREAM_REAL
+ if(enabled)
+ call_ext(dll_path, "auxtools_shutdown")()
+#endif
+ return ..()
+
+/// Attempt to enable the debugger.
+/datum/debugger/proc/enable()
+#ifndef OPENDREAM_REAL
+ if(enabled)
+ CRASH("Attempted to enable debugger while its already enabled, somehow.")
+ if(!dll_path)
+ return FALSE
+ var/result = call_ext(dll_path, "auxtools_init")()
+ if(result != "SUCCESS")
+ error = result
+ return FALSE
+ enable_debugging()
+ enabled = TRUE
+ return TRUE
+#else
+ return FALSE
+#endif
+
+/datum/debugger/vv_edit_var(var_name, var_value)
+ return FALSE // no.
+
+/datum/debugger/CanProcCall(procname)
+ return FALSE // double no.
diff --git a/code/modules/debugging/tracy.dm b/code/modules/debugging/tracy.dm
new file mode 100644
index 0000000000..ac661466e9
--- /dev/null
+++ b/code/modules/debugging/tracy.dm
@@ -0,0 +1,65 @@
+/// The byond-tracy instance.
+/// This is a GLOBAL_REAL because it is the VERY FIRST THING to initialize, even before the MC or GLOB.
+GLOBAL_REAL(Tracy, /datum/tracy)
+
+/datum/tracy
+ /// Is byond-tracy enabled and running?
+ VAR_FINAL/enabled = FALSE
+ /// The error text, if initializing byond-tracy errored.
+ VAR_FINAL/error
+ /// A description of what / who enabled byond-tracy.
+ VAR_FINAL/init_reason
+ /// A path to the file containing the output trace, if any.
+ VAR_FINAL/trace_path
+
+/datum/tracy/New()
+ if(!isnull(Tracy))
+ CRASH("Attempted to initialize /datum/tracy when global.Tracy is already set!")
+ Tracy = src
+
+/datum/tracy/Destroy()
+#ifndef OPENDREAM_REAL
+ if(enabled)
+ call_ext(TRACY_DLL_PATH, "destroy")()
+#endif
+ return ..()
+
+/// Tries to initialize byond-tracy.
+/datum/tracy/proc/enable(init_reason)
+#ifndef OPENDREAM_REAL
+ if(enabled)
+ return TRUE
+ src.init_reason = init_reason
+ if(!fexists(TRACY_DLL_PATH))
+ error = "[TRACY_DLL_PATH] not found"
+ SEND_TEXT(world.log, "Error initializing byond-tracy: [error]")
+ return FALSE
+
+ var/init_result = call_ext(TRACY_DLL_PATH, "init")("block")
+ if(length(init_result) != 0 && init_result[1] == ".") // if first character is ., then it returned the output filename
+ SEND_TEXT(world.log, "byond-tracy initialized (logfile: [init_result])")
+ enabled = TRUE
+ trace_path = init_result
+ return TRUE
+ else if(init_result == "already initialized") // not gonna question it.
+ enabled = TRUE
+ SEND_TEXT(world.log, "byond-tracy already initialized ([trace_path ? "logfile: [trace_path]" : "no logfile"])")
+ return TRUE
+ else if(init_result != "0")
+ error = init_result
+ SEND_TEXT(world.log, "Error initializing byond-tracy: [init_result]")
+ return FALSE
+ else
+ enabled = TRUE
+ SEND_TEXT(world.log, "byond-tracy initialized (no logfile)")
+ return TRUE
+#else
+ error = "OpenDream not supported"
+ return FALSE
+#endif
+
+/datum/tracy/vv_edit_var(var_name, var_value)
+ return FALSE // no.
+
+/datum/tracy/CanProcCall(procname)
+ return FALSE // double no.
diff --git a/code/modules/events/money_spam.dm b/code/modules/events/money_spam.dm
index c499a80547..cb05dfcfc9 100644
--- a/code/modules/events/money_spam.dm
+++ b/code/modules/events/money_spam.dm
@@ -60,7 +60,7 @@
if(2)
sender = pick(300;"QuickDatingSystem",200;"Find your russian bride",50;"Tajaran beauties are waiting",50;"Find your secret skrell crush",50;"Beautiful unathi brides")
message = pick("Your profile caught my attention and I wanted to write and say hello (QuickDating).",\
- "If you will write to me on my email [pick(first_names_female)]@[pick(last_names)].[pick("ru","ck","tj","ur","nt")] I shall necessarily send you a photo (QuickDating).",\
+ "If you will write to me on my email [pick(GLOB.first_names_female)]@[pick(GLOB.last_names)].[pick("ru","ck","tj","ur","nt")] I shall necessarily send you a photo (QuickDating).",\
"I want that we write each other and I hope, that you will like my profile and you will answer me (QuickDating).",\
"You have (1) new message!",\
"You have (2) new profile views!")
diff --git a/code/modules/events/supply_demand_vr.dm b/code/modules/events/supply_demand_vr.dm
index d5ccca4fe9..291ab67f8d 100644
--- a/code/modules/events/supply_demand_vr.dm
+++ b/code/modules/events/supply_demand_vr.dm
@@ -75,7 +75,7 @@
// Success!
SSsupply.points += 100 * severity
var/msg = "Great work! With those items you delivered our inventory levels all match up. "
- msg += "[capitalize(pick(first_names_female))] from accounting will have nothing to complain about. "
+ msg += "[capitalize(pick(GLOB.first_names_female))] from accounting will have nothing to complain about. "
msg += "I think you'll find a little something in your supply account."
command_announcement.Announce(msg, my_department)
else
diff --git a/code/modules/gamemaster/event2/events/everyone/pda_spam.dm b/code/modules/gamemaster/event2/events/everyone/pda_spam.dm
index b3df2bde62..65186bffb6 100644
--- a/code/modules/gamemaster/event2/events/everyone/pda_spam.dm
+++ b/code/modules/gamemaster/event2/events/everyone/pda_spam.dm
@@ -88,7 +88,7 @@
if(2)
sender = pick(300;"QuickDatingSystem",200;"Find your russian bride",50;"Tajaran beauties are waiting",50;"Find your secret skrell crush",50;"Beautiful unathi brides")
message = pick("Your profile caught my attention and I wanted to write and say hello (QuickDating).",\
- "If you will write to me on my email [pick(first_names_female)]@[pick(last_names)].[pick("ru","ck","tj","ur","nt")] I shall necessarily send you a photo (QuickDating).",\
+ "If you will write to me on my email [pick(GLOB.first_names_female)]@[pick(GLOB.last_names)].[pick("ru","ck","tj","ur","nt")] I shall necessarily send you a photo (QuickDating).",\
"I want that we write each other and I hope, that you will like my profile and you will answer me (QuickDating).",\
"You have (1) new message!",\
"You have (2) new profile views!")
diff --git a/code/modules/gamemaster/event2/events/security/stowaway.dm b/code/modules/gamemaster/event2/events/security/stowaway.dm
index cdc7344c0f..fdbcb70e5a 100644
--- a/code/modules/gamemaster/event2/events/security/stowaway.dm
+++ b/code/modules/gamemaster/event2/events/security/stowaway.dm
@@ -23,7 +23,7 @@
event_type = /datum/event2/event/ghost_pod_spawner/stowaway/infiltrator
/datum/event2/meta/stowaway/get_weight()
- if(istype(ticker.mode, /datum/game_mode/extended) && !safe_for_extended)
+ if(istype(SSticker.mode, /datum/game_mode/extended) && !safe_for_extended)
return 0
var/security = GLOB.metric.count_people_in_department(DEPARTMENT_SECURITY)
diff --git a/code/modules/gamemaster/event2/events/security/swarm_boarder.dm b/code/modules/gamemaster/event2/events/security/swarm_boarder.dm
index a1e07e21b7..68dd152151 100644
--- a/code/modules/gamemaster/event2/events/security/swarm_boarder.dm
+++ b/code/modules/gamemaster/event2/events/security/swarm_boarder.dm
@@ -9,7 +9,7 @@
var/safe_for_extended = FALSE
/datum/event2/meta/swarm_boarder/get_weight()
- if(istype(ticker.mode, /datum/game_mode/extended) && !safe_for_extended)
+ if(istype(SSticker.mode, /datum/game_mode/extended) && !safe_for_extended)
return 0
var/security = GLOB.metric.count_people_in_department(DEPARTMENT_SECURITY)
diff --git a/code/modules/integrated_electronics/subtypes/input.dm b/code/modules/integrated_electronics/subtypes/input.dm
index 3d6ade30dc..4efca25cf8 100644
--- a/code/modules/integrated_electronics/subtypes/input.dm
+++ b/code/modules/integrated_electronics/subtypes/input.dm
@@ -424,8 +424,8 @@
addtimer(CALLBACK(src, PROC_REF(set_frequency), frequency), 40)
/obj/item/integrated_circuit/input/signaler/Destroy()
- if(radio_controller)
- radio_controller.remove_object(src,frequency)
+ if(SSradio)
+ SSradio.remove_object(src,frequency)
frequency = 0
. = ..()
@@ -452,13 +452,13 @@
/obj/item/integrated_circuit/input/signaler/proc/set_frequency(new_frequency)
if(!frequency)
return
- if(!radio_controller)
+ if(!SSradio)
sleep(20)
- if(!radio_controller)
+ if(!SSradio)
return
- radio_controller.remove_object(src, frequency)
+ SSradio.remove_object(src, frequency)
frequency = new_frequency
- radio_connection = radio_controller.add_object(src, frequency, RADIO_CHAT)
+ radio_connection = SSradio.add_object(src, frequency, RADIO_CHAT)
/obj/item/integrated_circuit/input/signaler/receive_signal(datum/signal/signal)
var/new_code = get_pin_data(IC_INPUT, 2)
diff --git a/code/modules/lighting/lighting_overlay.dm b/code/modules/lighting/lighting_overlay.dm
index 7ba7029a0d..63ec549f6c 100644
--- a/code/modules/lighting/lighting_overlay.dm
+++ b/code/modules/lighting/lighting_overlay.dm
@@ -11,7 +11,7 @@
var/turf/affected_turf
/datum/lighting_object/New(turf/source)
- if(!SSlighting.subsystem_initialized)
+ if(!SSlighting.initialized)
stack_trace("lighting_object created before SSlighting up!")
return
if(!isturf(source))
diff --git a/code/modules/lighting/lighting_turf.dm b/code/modules/lighting/lighting_turf.dm
index 3e1219f617..8a2e060023 100644
--- a/code/modules/lighting/lighting_turf.dm
+++ b/code/modules/lighting/lighting_turf.dm
@@ -127,7 +127,7 @@
/turf/proc/change_area(area/old_area, area/new_area)
- if(SSlighting.subsystem_initialized)
+ if(SSlighting.initialized)
if (new_area.dynamic_lighting != old_area.dynamic_lighting)
if (new_area.dynamic_lighting)
lighting_build_overlay()
diff --git a/code/modules/maps/tg/map_template_vr.dm b/code/modules/maps/tg/map_template_vr.dm
index 67bcd852b6..92acae407f 100644
--- a/code/modules/maps/tg/map_template_vr.dm
+++ b/code/modules/maps/tg/map_template_vr.dm
@@ -10,11 +10,11 @@ GLOBAL_LIST_EMPTY(map_templates_loaded)
/datum/map_template/proc/on_map_loaded(z)
//We missed air init!
- if(SSair.subsystem_initialized)
+ if(SSair.initialized)
for(var/turf/simulated/T in block(locate(1,1,z), locate(world.maxx, world.maxy, z)))
T.update_air_properties()
//We missed sslighting init!
- if(SSlighting.subsystem_initialized)
+ if(SSlighting.initialized)
for(var/Trf in block(locate(1,1,z), locate(world.maxx, world.maxy, z)))
var/turf/T = Trf //faster than implicit istype with typed for loop
T.lighting_build_overlay()
diff --git a/code/modules/mining/mine_turfs.dm b/code/modules/mining/mine_turfs.dm
index 3357d9a035..f4f54277d6 100644
--- a/code/modules/mining/mine_turfs.dm
+++ b/code/modules/mining/mine_turfs.dm
@@ -170,7 +170,7 @@ var/list/mining_overlay_cache = list()
/turf/simulated/mineral/proc/update_general()
recalculate_directional_opacity()
- if(ticker && ticker.current_state == GAME_STATE_PLAYING)
+ if(SSticker && SSticker.current_state == GAME_STATE_PLAYING)
reconsider_lights()
if(SSair)
SSair.mark_for_update(src)
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index 66f63a6fca..96c4b2240a 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -74,9 +74,9 @@
name = M.real_name
else
if(gender == MALE)
- name = capitalize(pick(first_names_male)) + " " + capitalize(pick(last_names))
+ name = capitalize(pick(GLOB.first_names_male)) + " " + capitalize(pick(GLOB.last_names))
else
- name = capitalize(pick(first_names_female)) + " " + capitalize(pick(last_names))
+ name = capitalize(pick(GLOB.first_names_female)) + " " + capitalize(pick(GLOB.last_names))
mind = M.mind //we don't transfer the mind but we keep a reference to it.
@@ -96,7 +96,7 @@
to_chat(src, span_danger("Could not locate an observer spawn point. Use the Teleport verb to jump to the station map."))
if(!name) //To prevent nameless ghosts
- name = capitalize(pick(first_names_male)) + " " + capitalize(pick(last_names))
+ name = capitalize(pick(GLOB.first_names_male)) + " " + capitalize(pick(GLOB.last_names))
real_name = name
animate(src, pixel_y = 2, time = 10, loop = -1)
animate(pixel_y = default_pixel_y, time = 10, loop = -1)
@@ -683,7 +683,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
return 0 //something is terribly wrong
var/ghosts_can_write
- if(ticker.mode.name == "cult")
+ if(SSticker.mode.name == "cult")
if(cult.current_antagonists.len > CONFIG_GET(number/cult_ghostwriter_req_cultists))
ghosts_can_write = 1
diff --git a/code/modules/mob/death.dm b/code/modules/mob/death.dm
index 43b4e00d64..6ba8bbc4c2 100644
--- a/code/modules/mob/death.dm
+++ b/code/modules/mob/death.dm
@@ -116,8 +116,8 @@
handle_regular_hud_updates()
handle_vision()
- if(ticker && ticker.mode)
- ticker.mode.check_win()
+ if(SSticker && SSticker.mode)
+ SSticker.mode.check_win()
return 1
diff --git a/code/modules/mob/freelook/ai/update_triggers.dm b/code/modules/mob/freelook/ai/update_triggers.dm
index 99a8a83734..343abe9feb 100644
--- a/code/modules/mob/freelook/ai/update_triggers.dm
+++ b/code/modules/mob/freelook/ai/update_triggers.dm
@@ -46,7 +46,7 @@
/obj/machinery/camera/Initialize(mapload)
. = ..()
//Camera must be added to global list of all cameras no matter what...
- if(cameranet.cameras_unsorted || !ticker)
+ if(cameranet.cameras_unsorted || !SSticker)
cameranet.cameras += src
cameranet.cameras_unsorted = 1
else
diff --git a/code/modules/mob/freelook/update_triggers.dm b/code/modules/mob/freelook/update_triggers.dm
index f73e3b8d13..aa4cfba60e 100644
--- a/code/modules/mob/freelook/update_triggers.dm
+++ b/code/modules/mob/freelook/update_triggers.dm
@@ -3,7 +3,7 @@
// TURFS
/proc/updateVisibility(atom/A, var/opacity_check = 1)
- if(ticker)
+ if(SSticker)
for(var/datum/visualnet/VN in visual_nets)
VN.updateVisibility(A, opacity_check)
diff --git a/code/modules/mob/freelook/visualnet.dm b/code/modules/mob/freelook/visualnet.dm
index 10f0a6c809..d7b0d39c33 100644
--- a/code/modules/mob/freelook/visualnet.dm
+++ b/code/modules/mob/freelook/visualnet.dm
@@ -92,7 +92,7 @@
/datum/visualnet/proc/updateVisibility(atom/A, var/opacity_check = 1)
- if(!ticker || (opacity_check && !A.opacity))
+ if(!SSticker || (opacity_check && !A.opacity))
return
majorChunkChange(A, 2)
diff --git a/code/modules/mob/language/language.dm b/code/modules/mob/language/language.dm
index afbb6380f7..7aa900d9df 100644
--- a/code/modules/mob/language/language.dm
+++ b/code/modules/mob/language/language.dm
@@ -26,9 +26,9 @@
/datum/language/proc/get_random_name(var/gender, name_count=2, syllable_count=4, syllable_divisor=2)
if(!syllables || !syllables.len)
if(gender==FEMALE)
- return capitalize(pick(first_names_female)) + " " + capitalize(pick(last_names))
+ return capitalize(pick(GLOB.first_names_female)) + " " + capitalize(pick(GLOB.last_names))
else
- return capitalize(pick(first_names_male)) + " " + capitalize(pick(last_names))
+ return capitalize(pick(GLOB.first_names_male)) + " " + capitalize(pick(GLOB.last_names))
var/full_name = ""
var/new_name = ""
diff --git a/code/modules/mob/language/station.dm b/code/modules/mob/language/station.dm
index 575799bfab..685b08463b 100644
--- a/code/modules/mob/language/station.dm
+++ b/code/modules/mob/language/station.dm
@@ -127,8 +127,8 @@
syllables = list("qr","qrr","xuq","qil","quum","xuqm","vol","xrim","zaoo","qu-uu","qix","qoo","zix")
/datum/language/skrell/get_random_name(var/gender)
- var/list/first_names = file2list('config/names/first_name_skrell.txt')
- var/list/last_names = file2list('config/names/last_name_skrell.txt')
+ var/list/first_names = file2list('strings/names/first_name_skrell.txt')
+ var/list/last_names = file2list('strings/names/last_name_skrell.txt')
return "[pick(first_names)] [pick(last_names)]"
/datum/language/human
@@ -152,9 +152,9 @@
/datum/language/human/get_random_name(var/gender)
if (prob(80))
if(gender==FEMALE)
- return capitalize(pick(first_names_female)) + " " + capitalize(pick(last_names))
+ return capitalize(pick(GLOB.first_names_female)) + " " + capitalize(pick(GLOB.last_names))
else
- return capitalize(pick(first_names_male)) + " " + capitalize(pick(last_names))
+ return capitalize(pick(GLOB.first_names_male)) + " " + capitalize(pick(GLOB.last_names))
else
return ..()
@@ -174,7 +174,7 @@
if(prob(70))
return "[pick(list("PBU","HIU","SINA","ARMA","OSI"))]-[rand(100, 999)]"
else
- return pick(ai_names)
+ return pick(GLOB.ai_names)
/datum/language/teshari
name = LANGUAGE_SCHECHI
diff --git a/code/modules/mob/living/carbon/human/species/species_getters.dm b/code/modules/mob/living/carbon/human/species/species_getters.dm
index 03d0575307..3ec5e9bc5b 100644
--- a/code/modules/mob/living/carbon/human/species/species_getters.dm
+++ b/code/modules/mob/living/carbon/human/species/species_getters.dm
@@ -117,11 +117,11 @@
/datum/species/proc/get_random_name(var/gender)
if(!name_language)
if(gender == FEMALE)
- return capitalize(pick(first_names_female)) + " " + capitalize(pick(last_names))
+ return capitalize(pick(GLOB.first_names_female)) + " " + capitalize(pick(GLOB.last_names))
else if(gender == MALE)
- return capitalize(pick(first_names_male)) + " " + capitalize(pick(last_names))
+ return capitalize(pick(GLOB.first_names_male)) + " " + capitalize(pick(GLOB.last_names))
else
- return capitalize(prob(50) ? pick(first_names_male) : pick(first_names_female)) + " " + capitalize(pick(last_names))
+ return capitalize(prob(50) ? pick(GLOB.first_names_male) : pick(GLOB.first_names_female)) + " " + capitalize(pick(GLOB.last_names))
var/datum/language/species_language = GLOB.all_languages[name_language]
if(!species_language)
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index a901fe24a7..0cdabcfaca 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -932,8 +932,8 @@
//check for nuke disks
if(client && stat != DEAD) //if they are clientless and dead don't bother, the parent will treat them as any other container
- if(ticker && istype(ticker.mode, /datum/game_mode/nuclear)) //only really care if the game mode is nuclear
- var/datum/game_mode/nuclear/G = ticker.mode
+ if(SSticker && istype(SSticker.mode, /datum/game_mode/nuclear)) //only really care if the game mode is nuclear
+ var/datum/game_mode/nuclear/G = SSticker.mode
if(G.check_mob(src))
if(x <= TRANSITIONEDGE)
inertia_dir = 4
@@ -1429,7 +1429,7 @@
"})
/mob/living/update_gravity(has_gravity)
- if(!ticker)
+ if(!SSticker)
return
if(has_gravity)
clear_alert("weightless")
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index 1047b13e83..646bcfc4ad 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -113,11 +113,11 @@ var/list/ai_verbs_default = list(
announcement.announcement_type = "A.I. Announcement"
announcement.newscast = 1
- var/list/possibleNames = ai_names
+ var/list/possibleNames = GLOB.ai_names
var/pickedName = null
while(!pickedName)
- pickedName = pick(ai_names)
+ pickedName = pick(GLOB.ai_names)
for (var/mob/living/silicon/ai/A in GLOB.mob_list)
if (A.real_name == pickedName && possibleNames.len > 1) //fixing the theoretically possible infinite loop
possibleNames -= pickedName
diff --git a/code/modules/mob/living/silicon/ai/latejoin.dm b/code/modules/mob/living/silicon/ai/latejoin.dm
index 4524a58e4e..654178a1cb 100644
--- a/code/modules/mob/living/silicon/ai/latejoin.dm
+++ b/code/modules/mob/living/silicon/ai/latejoin.dm
@@ -15,7 +15,7 @@ GLOBAL_LIST_EMPTY(empty_playable_ai_cores)
set category = "OOC.Game"
set desc = "Enter intelligence storage. This is functionally equivalent to cryo or robotic storage, freeing up your job slot."
- if(ticker && ticker.mode && ticker.mode.name == "AI malfunction")
+ if(SSticker && SSticker.mode && SSticker.mode.name == "AI malfunction")
to_chat(src, span_danger("You cannot use this verb in malfunction. If you need to leave, please adminhelp."))
return
diff --git a/code/modules/mob/living/silicon/pai/recruit.dm b/code/modules/mob/living/silicon/pai/recruit.dm
index f2f23f299c..1a6603c78a 100644
--- a/code/modules/mob/living/silicon/pai/recruit.dm
+++ b/code/modules/mob/living/silicon/pai/recruit.dm
@@ -40,7 +40,7 @@ var/datum/paiController/paiController // Global handler for pAI candidates
GLOB.paikeys |= pai.ckey
card.setPersonality(pai)
if(!candidate.name)
- pai.SetName(pick(ninja_names))
+ pai.SetName(pick(GLOB.ninja_names))
else
pai.SetName(candidate.name)
if(candidate.description)
diff --git a/code/modules/mob/living/silicon/robot/drone/drone_manufacturer.dm b/code/modules/mob/living/silicon/robot/drone/drone_manufacturer.dm
index 201d67492b..bbf1f124cb 100644
--- a/code/modules/mob/living/silicon/robot/drone/drone_manufacturer.dm
+++ b/code/modules/mob/living/silicon/robot/drone/drone_manufacturer.dm
@@ -50,7 +50,7 @@
/obj/machinery/drone_fabricator/process()
- if(ticker.current_state < GAME_STATE_PLAYING)
+ if(SSticker.current_state < GAME_STATE_PLAYING)
return
if(stat & NOPOWER || !produce_drones)
diff --git a/code/modules/mob/living/silicon/robot/subtypes/thinktank/thinktank_interactions.dm b/code/modules/mob/living/silicon/robot/subtypes/thinktank/thinktank_interactions.dm
index 4319b9a7fc..1e941602c4 100644
--- a/code/modules/mob/living/silicon/robot/subtypes/thinktank/thinktank_interactions.dm
+++ b/code/modules/mob/living/silicon/robot/subtypes/thinktank/thinktank_interactions.dm
@@ -34,11 +34,11 @@
/mob/living/silicon/robot/platform/attack_ghost(mob/observer/dead/user)
- if(client || key || stat == DEAD || !ticker || !ticker.mode)
+ if(client || key || stat == DEAD || !SSticker || !SSticker.mode)
return ..()
var/confirm = tgui_alert(user, "Do you wish to take control of \the [src]?", "Platform Control", list("No", "Yes"))
- if(confirm != "Yes" || QDELETED(src) || client || key || stat == DEAD || !ticker || !ticker.mode)
+ if(confirm != "Yes" || QDELETED(src) || client || key || stat == DEAD || !SSticker || !SSticker.mode)
return ..()
if(jobban_isbanned(user, "Robot"))
diff --git a/code/modules/mob/logout.dm b/code/modules/mob/logout.dm
index 5b801c5056..c1a1a9ca81 100644
--- a/code/modules/mob/logout.dm
+++ b/code/modules/mob/logout.dm
@@ -9,7 +9,7 @@
var/datum/admins/is_admin = GLOB.admin_datums[src.ckey]
if(is_admin && is_admin.check_for_rights(R_HOLDER))
message_admins("Staff logout: [key_name(src)]") // Staff logout notice displays no matter what
- if (ticker && ticker.current_state == GAME_STATE_PLAYING) //Only report this stuff if we are currently playing.
+ if (SSticker && SSticker.current_state == GAME_STATE_PLAYING) //Only report this stuff if we are currently playing.
var/admins_number = GLOB.admins.len
if(admins_number == 0) //Apparently the admin logging out is no longer an admin at this point, so we have to check this towards 0 and not towards 1. Awell.
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 7dc2adb6d7..ce27989b5f 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -340,7 +340,7 @@
// Try to figure out what time to use
// Special cases, can never respawn
- if(ticker?.mode?.deny_respawn)
+ if(SSticker?.mode?.deny_respawn)
time = -1
else if(!CONFIG_GET(flag/abandon_allowed))
time = -1
@@ -348,7 +348,7 @@
time = -1
// Special case for observing before game start
- else if(ticker?.current_state <= GAME_STATE_SETTING_UP)
+ else if(SSticker?.current_state <= GAME_STATE_SETTING_UP)
time = 1 MINUTE
// Wasn't given a time, use the config time
@@ -377,7 +377,7 @@
to_chat(src, span_boldnotice("You are already in the lobby!"))
return
- if(stat != DEAD || !ticker)
+ if(stat != DEAD || !SSticker)
to_chat(src, span_boldnotice("You must be dead to use this!"))
return
diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm
index bf4f372c97..954738d9a6 100644
--- a/code/modules/mob/mob_movement.dm
+++ b/code/modules/mob/mob_movement.dm
@@ -146,6 +146,9 @@
// Used many times below, faster reference.
var/atom/loc = my_mob.loc
+ if(HAS_TRAIT(my_mob, TRAIT_NO_TRANSFORM))
+ return FALSE //This is sorta the goto stop mobs from moving trait
+
// We're controlling an object which is when admins possess an object.
if(my_mob.control_object)
Move_object(direct)
diff --git a/code/modules/mob/new_player/lobby_browser.dm b/code/modules/mob/new_player/lobby_browser.dm
index 40aeb4d61b..003443c8d2 100644
--- a/code/modules/mob/new_player/lobby_browser.dm
+++ b/code/modules/mob/new_player/lobby_browser.dm
@@ -43,7 +43,7 @@
data["server_name"] = displayed_name
data["map"] = using_map.full_name
data["station_time"] = stationtime2text()
- data["display_loading"] = SSticker.current_state == GAME_STATE_INIT
+ data["display_loading"] = SSticker.current_state == GAME_STATE_STARTUP
data["round_start"] = !SSticker.mode || SSticker.current_state <= GAME_STATE_PREGAME
data["round_time"] = roundduration2text()
data["ready"] = ready
@@ -82,7 +82,7 @@
ViewManifest()
return TRUE
if("late_join")
- if(!ticker || ticker.current_state != GAME_STATE_PLAYING)
+ if(!SSticker || SSticker.current_state != GAME_STATE_PLAYING)
to_chat(usr, span_red("The round is either not ready, or has already finished..."))
return TRUE
@@ -97,7 +97,7 @@
if("observe")
if(QDELETED(src))
return FALSE
- if(!SSticker || SSticker.current_state == GAME_STATE_INIT)
+ if(!SSticker || SSticker.current_state == GAME_STATE_STARTUP)
to_chat(src, span_warning("The game is still setting up, please try again later."))
return TRUE
if(tgui_alert(src,"Are you sure you wish to observe? If you do, make sure to not use any knowledge gained from observing if you decide to join later.","Observe Round?",list("Yes","No")) == "Yes")
@@ -160,7 +160,7 @@
client.changes()
return TRUE
if("keyboard")
- if(!SSsounds.subsystem_initialized)
+ if(!SSsounds.initialized)
return
playsound_local(ui.user, get_sfx("keyboard"), vol = 20)
diff --git a/code/modules/mob/new_player/new_player.dm b/code/modules/mob/new_player/new_player.dm
index df64e99b91..96fb89c228 100644
--- a/code/modules/mob/new_player/new_player.dm
+++ b/code/modules/mob/new_player/new_player.dm
@@ -42,11 +42,11 @@
// if(SSvote.mode)
// . += "Vote: [capitalize(SSvote.mode)] Time Left: [SSvote.time_remaining] s"
- if(SSticker.current_state == GAME_STATE_INIT)
+ if(SSticker.current_state == GAME_STATE_STARTUP)
. += "Time To Start: Server Initializing"
else if(SSticker.current_state == GAME_STATE_PREGAME)
- . += "Time To Start: [round(SSticker.pregame_timeleft,1)][GLOB.round_progressing ? "" : " (DELAYED)"]"
+ . += "Time To Start: [round(SSticker.timeLeft, 1)][GLOB.round_progressing ? "" : " (DELAYED)"]"
. += "Players: [totalPlayers]"
. += "Players Ready: [totalPlayersReady]"
totalPlayers = 0
@@ -223,7 +223,7 @@
/mob/new_player/proc/AttemptLateSpawn(rank,var/spawning_at)
if (src != usr)
return 0
- if(!ticker || ticker.current_state != GAME_STATE_PLAYING)
+ if(!SSticker || SSticker.current_state != GAME_STATE_PLAYING)
to_chat(src, span_red("The round is either not ready, or has already finished..."))
return 0
if(!CONFIG_GET(flag/enter_allowed))
@@ -297,7 +297,7 @@
character = character.AIize(move = FALSE) // Dupe of code in /datum/controller/subsystem/ticker/proc/create_characters() for non-latespawn, unify?
AnnounceCyborg(character, rank, "has been transferred to the empty core in \the [character.loc.loc]")
- ticker.mode.latespawn(character)
+ SSticker.mode.latespawn(character)
qdel(C) //Deletes empty core (really?)
qdel(src) //Deletes new_player
@@ -308,22 +308,22 @@
character.buckled.loc = character.loc
character.buckled.set_dir(character.dir)
- ticker.mode.latespawn(character)
+ SSticker.mode.latespawn(character)
if(rank == JOB_OUTSIDER)
log_and_message_admins("has joined the round as non-crew. (JMP)",character)
if(!(J.mob_type & JOB_SILICON))
- ticker.minds += character.mind
+ SSticker.minds += character.mind
else if(rank == JOB_ANOMALY)
log_and_message_admins("has joined the round as anomaly. (JMP)",character)
if(!(J.mob_type & JOB_SILICON))
- ticker.minds += character.mind
+ SSticker.minds += character.mind
else if(J.mob_type & JOB_SILICON)
AnnounceCyborg(character, rank, join_message, announce_channel, character.z)
else
AnnounceArrival(character, rank, join_message, announce_channel, character.z)
GLOB.data_core.manifest_inject(character)
- ticker.minds += character.mind//Cyborgs and AIs handle this in the transform proc. //TODO!!!!! ~Carn
+ SSticker.minds += character.mind//Cyborgs and AIs handle this in the transform proc. //TODO!!!!! ~Carn
if(ishuman(character))
if(character.client.prefs.auto_backup_implant)
var/obj/item/implant/backup/imp = new(src)
@@ -370,7 +370,7 @@
qdel(src) // Delete new_player mob
/mob/new_player/proc/AnnounceCyborg(var/mob/living/character, var/rank, var/join_message, var/channel, var/zlevel)
- if (ticker.current_state == GAME_STATE_PLAYING)
+ if (SSticker.current_state == GAME_STATE_PLAYING)
var/list/zlevels = zlevel ? using_map.get_map_levels(zlevel, TRUE, om_range = DEFAULT_OVERMAP_RANGE) : null
if(character.mind.role_alt_title)
rank = character.mind.role_alt_title
@@ -402,7 +402,7 @@
if(!new_character)
new_character = new(T)
- if(ticker.random_players)
+ if(CONFIG_GET(flag/force_random_names))
new_character.gender = pick(MALE, FEMALE)
client.prefs.real_name = random_name(new_character.gender)
else
diff --git a/code/modules/overmap/README.dm b/code/modules/overmap/README.md
similarity index 77%
rename from code/modules/overmap/README.dm
rename to code/modules/overmap/README.md
index 7681891059..7929103462 100644
--- a/code/modules/overmap/README.dm
+++ b/code/modules/overmap/README.md
@@ -1,13 +1,11 @@
-/*
The overmap system allows adding new maps to the big 'galaxy' map.
Idea is that new sectors can be added by just ticking in new maps and recompiling.
Not real hot-plugging, but still pretty modular.
It uses the fact that all ticked in .dme maps are melded together into one as different zlevels.
Metaobjects are used to make it not affected by map order in .dme and carry some additional info.
-*************************************************************
-Metaobject
-*************************************************************
+## Metaobject
+
/obj/effect/mapinfo, sectors.dm
Used to build overmap in beginning, has basic information needed to create overmap objects and make shuttles work.
Its name and icon (if non-standard) vars will be applied to resulting overmap object.
@@ -19,9 +17,9 @@ Has two important vars:
Object could be placed anywhere on zlevel. Should only be placed on zlevel that should appear on overmap as a separate entitety.
Right after creation it sends itself to nullspace and creates an overmap object, corresponding to this zlevel.
-*************************************************************
-Overmap object
-*************************************************************
+
+## Overmap object
+
/obj/effect/map, sectors.dm
Represents a zlevel on the overmap. Spawned by metaobjects at the startup.
var/area/shuttle/shuttle_landing - keeps a reference to the area of where inbound shuttles should land
@@ -33,9 +31,9 @@ Remember to call ..() in children, it updates ship's current sector.
subtype /ship of this object represents spacefaring vessels.
It has 'current_sector' var that keeps refernce to, well, sector ship currently in.
-*************************************************************
-Helm console
-*************************************************************
+
+## Helm console
+
/obj/machinery/computer/helm, helm.dm
On creation console seeks a ship overmap object corresponding to this zlevel and links it.
Clicking with empty hand on it starts steering, Cancel-Camera-View stops it.
@@ -43,9 +41,9 @@ Helm console relays movement of mob to the linked overmap object.
Helm console currently has no interface. All travel happens instanceously too.
Sector shuttles are not supported currently, only ship shuttles.
-*************************************************************
-Exploration shuttle terminal
-*************************************************************
+
+## Exploration shuttle terminal
+
A generic shuttle controller.
Has a var landing_type defining type of area shuttle should be landing at.
On initalizing, checks for a shuttle corresponding to this zlevel, and creates one if it's not there.
@@ -53,16 +51,16 @@ Changes desitnation area depending on current sector ship is in.
Currently updating is called in attack_hand(), until a better place is found.
Currently no modifications were made to interface to display availability of landing area in sector.
-*************************************************************
-Landable Ships
-*************************************************************
+
+## Landable Ships
+
Ship - Vessel that can move around on the overmap. It's entire z-level(s) "move" conceptually.
Shuttles - Vessel that can jump to shuttle landmarks. Its areas move by transition_turfs.
Landable Ship - Vessel that can do both. Sits at a special shuttle landmark for overmap movement mode.
-*************************************************************
-Guide to how make new sector
-*************************************************************
+
+## Guide to how make new sector
+
0.Map
Remember to define shuttle areas if you want sector be accessible via shuttles.
Currently there are no other ways to reach sectors from ships.
@@ -73,38 +71,37 @@ Ships need engines to move. Currently there are only thermal engines.
Thermal engines are just a unary atmopheric machine, like a vent. They need high-pressure gas input to produce more thrust.
-1.Metaobject
+1. Metaobject
All vars needed for it to work could be set directly in map editor, so in most cases you won't have to define new in code.
Remember to set landing_area var for sectors.
-2.Overmap object
+2. Overmap object
If you need custom behaviour on entering/leaving this sector, or restricting access to it, you can define your custom map object.
Remember to put this new type into spawn_type var of metaobject.
-3.Shuttle console
+3. Shuttle console
Remember to place one on the actual shuttle too, or it won't be able to return from sector without ship-side recall.
Remember to set landing_type var to ship-side shuttle area type.
shuttle_tag can be set to custom name (it shows up in console interface)
-5.Engines
+5. Engines
Actual engines could be any type of machinery, as long as it creates a ship_engine datum for itself.
-6.Tick map in and compile.
+6. Tick map in and compile.
Sector should appear on overmap (in random place if you didn't set mapx,mapy)
-TODO:
-shuttle console:
- checking occupied pad or not with docking controllers
- ?landing pad size detection
-non-zlevel overmap objects
- field generator
- meteor fields
- speed-based chance for a rock in the ship
- debris fields
- speed-based chance of
- debirs in the ship
- a drone
- EMP
- nebulaes
-*/
+TODO:
+shuttle console:
+ checking occupied pad or not with docking controllers
+ ?landing pad size detection
+non-zlevel overmap objects
+ field generator
+ meteor fields
+ speed-based chance for a rock in the ship
+ debris fields
+ speed-based chance of
+ debirs in the ship
+ a drone
+ EMP
+ nebulaes
diff --git a/code/modules/pda/cart.dm b/code/modules/pda/cart.dm
index 4054717d1f..3a5277df6d 100644
--- a/code/modules/pda/cart.dm
+++ b/code/modules/pda/cart.dm
@@ -279,7 +279,7 @@ var/list/civilian_cartridges = list(
/obj/item/cartridge/proc/post_status(var/command, var/data1, var/data2)
- var/datum/radio_frequency/frequency = radio_controller.return_frequency(1435)
+ var/datum/radio_frequency/frequency = SSradio.return_frequency(1435)
if(!frequency) return
var/datum/signal/status_signal = new
diff --git a/code/modules/pda/cart_apps.dm b/code/modules/pda/cart_apps.dm
index 946fa7e671..1d6da01edb 100644
--- a/code/modules/pda/cart_apps.dm
+++ b/code/modules/pda/cart_apps.dm
@@ -31,7 +31,7 @@
return TRUE
/datum/data/pda/app/status_display/proc/post_status(var/command, var/data1, var/data2)
- var/datum/radio_frequency/frequency = radio_controller.return_frequency(1435)
+ var/datum/radio_frequency/frequency = SSradio.return_frequency(1435)
if(!frequency)
return
diff --git a/code/modules/pda/radio.dm b/code/modules/pda/radio.dm
index fab19f180b..2db4492475 100644
--- a/code/modules/pda/radio.dm
+++ b/code/modules/pda/radio.dm
@@ -28,15 +28,15 @@
add_to_radio(bot_filter)
/obj/item/radio/integrated/Destroy()
- if(radio_controller)
- radio_controller.remove_object(src, control_freq)
+ if(SSradio)
+ SSradio.remove_object(src, control_freq)
hostpda = null
return ..()
/obj/item/radio/integrated/proc/post_signal(var/freq, var/key, var/value, var/key2, var/value2, var/key3, var/value3, s_filter)
//to_world("Post: [freq]: [key]=[value], [key2]=[value2]")
- var/datum/radio_frequency/frequency = radio_controller.return_frequency(freq)
+ var/datum/radio_frequency/frequency = SSradio.return_frequency(freq)
if(!frequency)
return
@@ -90,8 +90,8 @@
botstatus = b.Copy()
/obj/item/radio/integrated/proc/add_to_radio(bot_filter) //Master filter control for bots. Must be placed in the bot's local Initialize(mapload) to support map spawned bots.
- if(radio_controller)
- radio_controller.add_object(src, control_freq, radio_filter = bot_filter)
+ if(SSradio)
+ SSradio.add_object(src, control_freq, radio_filter = bot_filter)
/*
* Radio Cartridge, essentially a signaler.
@@ -101,22 +101,22 @@
var/code = 30.0
/obj/item/radio/integrated/signal/Destroy()
- if(radio_controller)
- radio_controller.remove_object(src, frequency)
+ if(SSradio)
+ SSradio.remove_object(src, frequency)
radio_connection = null
return ..()
/obj/item/radio/integrated/signal/Initialize(mapload)
. = ..()
- if(radio_controller)
+ if(SSradio)
if(src.frequency < PUBLIC_LOW_FREQ || src.frequency > PUBLIC_HIGH_FREQ)
src.frequency = sanitize_frequency(src.frequency)
set_frequency(frequency)
/obj/item/radio/integrated/signal/set_frequency(new_frequency)
- radio_controller.remove_object(src, frequency)
+ SSradio.remove_object(src, frequency)
frequency = new_frequency
- radio_connection = radio_controller.add_object(src, frequency)
+ radio_connection = SSradio.add_object(src, frequency)
/obj/item/radio/integrated/signal/proc/send_signal(message="ACTIVATE")
if(last_transmission && world.time < (last_transmission + 5))
diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm
index cc386e4846..f0d35f4f63 100644
--- a/code/modules/power/apc.dm
+++ b/code/modules/power/apc.dm
@@ -482,7 +482,7 @@ GLOBAL_LIST_EMPTY(apcs)
span_warning("[user.name] has broken the charred power control board inside [name]!"),\
span_notice("You broke the charred power control board and remove the remains."),
"You hear a crack!")
- //ticker.mode:apcs-- //XSI said no and I agreed. -rastaf0
+ //SSticker.mode:apcs-- //XSI said no and I agreed. -rastaf0
else
user.visible_message(\
span_warning("[user.name] has removed the power control board from [name]!"),\
diff --git a/code/modules/projectiles/projectile/change.dm b/code/modules/projectiles/projectile/change.dm
index 5395da0d92..a0103b9883 100644
--- a/code/modules/projectiles/projectile/change.dm
+++ b/code/modules/projectiles/projectile/change.dm
@@ -66,15 +66,15 @@
if(M.gender == MALE)
H.gender = MALE
- H.name = pick(first_names_male)
+ H.name = pick(GLOB.first_names_male)
else if(M.gender == FEMALE)
H.gender = FEMALE
- H.name = pick(first_names_female)
+ H.name = pick(GLOB.first_names_female)
else
H.gender = NEUTER
- H.name = pick(first_names_female|first_names_male)
+ H.name = pick(GLOB.first_names_female|GLOB.first_names_male)
- H.name += " [pick(last_names)]"
+ H.name += " [pick(GLOB.last_names)]"
H.real_name = H.name
H.set_species(randomize)
diff --git a/code/modules/reagents/holder/holder.dm b/code/modules/reagents/holder/holder.dm
index acffe00a5a..cc6cffc741 100644
--- a/code/modules/reagents/holder/holder.dm
+++ b/code/modules/reagents/holder/holder.dm
@@ -101,7 +101,7 @@
while(reaction_occurred)
for(var/decl/chemical_reaction/C as anything in effect_reactions)
C.post_reaction(src)
- #ifdef UNIT_TEST
+ #ifdef UNIT_TESTS
SEND_SIGNAL(src, COMSIG_UNITTEST_DATA, list(C))
#endif
update_total()
diff --git a/code/modules/reagents/reactions/_reactions.dm b/code/modules/reagents/reactions/_reactions.dm
index 604340bd86..5e0a06c028 100644
--- a/code/modules/reagents/reactions/_reactions.dm
+++ b/code/modules/reagents/reactions/_reactions.dm
@@ -103,8 +103,8 @@
var/amt_produced = result_amount * reaction_progress
if(result)
holder.add_reagent(result, amt_produced, data, safety = 1, was_from_belly = belly_reagent)
- // #ifdef UNIT_TEST
- // log_unit_test("[name] - Reagent reaction result: [result] [amt_produced]") // Uncomment for UNIT_TEST debug assistance
+ // #ifdef UNIT_TESTS
+ // log_unit_test("[name] - Reagent reaction result: [result] [amt_produced]") // Uncomment for UNIT_TESTS debug assistance
// #endif
on_reaction(holder, amt_produced)
diff --git a/code/modules/reagents/reactions/instant/instant.dm b/code/modules/reagents/reactions/instant/instant.dm
index ce330c132d..c8108b1816 100644
--- a/code/modules/reagents/reactions/instant/instant.dm
+++ b/code/modules/reagents/reactions/instant/instant.dm
@@ -825,8 +825,8 @@
result_amount = 2
log_is_important = 1
-#ifndef UNIT_TEST
-// If it becomes possible to make this without exploding and clearing reagents, remove the UNIT_TEST wrapper
+#ifndef UNIT_TESTS
+// If it becomes possible to make this without exploding and clearing reagents, remove the UNIT_TESTS wrapper
/decl/chemical_reaction/instant/nitroglycerin/on_reaction(var/datum/reagents/holder, var/created_volume)
var/datum/effect/effect/system/reagents_explosion/e = new()
e.set_up(round (created_volume/2, 1), holder.my_atom, 0, 0)
diff --git a/code/modules/rogueminer_vr/zonemaster.dm b/code/modules/rogueminer_vr/zonemaster.dm
index 0c1a3fc774..64b09ca05b 100644
--- a/code/modules/rogueminer_vr/zonemaster.dm
+++ b/code/modules/rogueminer_vr/zonemaster.dm
@@ -70,8 +70,8 @@
//Add the core to the asteroid's map
rm_controller.dbg("ZM(ga): Starting core generation for [A.coresize] size core..")
- for(var/x = 1; x <= A.coresize, x++)
- for(var/y = 1; y <= A.coresize, y++)
+ for(var/x = 1, x <= A.coresize, x++)
+ for(var/y = 1, y <= A.coresize, y++)
rm_controller.dbg("ZM(ga): Doing core-relative [x],[y] at [A.coresize+x],[A.coresize+y], [A.type_wall].")
A.spot_add(A.coresize+x, A.coresize+y, A.type_wall)
diff --git a/code/modules/scripting/Implementations/Telecomms.dm b/code/modules/scripting/Implementations/Telecomms.dm
index c9c99dafaf..be93c2705f 100644
--- a/code/modules/scripting/Implementations/Telecomms.dm
+++ b/code/modules/scripting/Implementations/Telecomms.dm
@@ -265,7 +265,7 @@
freq = text2num(freq)
newsign.frequency = freq
- var/datum/radio_frequency/connection = radio_controller.return_frequency(freq)
+ var/datum/radio_frequency/connection = SSradio.return_frequency(freq)
newsign.data["connection"] = connection
diff --git a/code/modules/security levels/keycard authentication.dm b/code/modules/security levels/keycard authentication.dm
index f1502cc6b4..0c2a113cd4 100644
--- a/code/modules/security levels/keycard authentication.dm
+++ b/code/modules/security levels/keycard authentication.dm
@@ -183,7 +183,7 @@
/obj/machinery/keycard_auth/proc/is_ert_blocked()
if(CONFIG_GET(flag/ert_admin_call_only)) return 1
- return ticker.mode && ticker.mode.ert_disabled
+ return SSticker.mode && SSticker.mode.ert_disabled
var/global/maint_all_access = 0
diff --git a/code/modules/shuttles/shuttle_emergency.dm b/code/modules/shuttles/shuttle_emergency.dm
index af1998c2f7..15bb884f1f 100644
--- a/code/modules/shuttles/shuttle_emergency.dm
+++ b/code/modules/shuttles/shuttle_emergency.dm
@@ -6,7 +6,7 @@
/datum/shuttle/autodock/ferry/emergency/New()
..()
- radio_connection = radio_controller.add_object(src, frequency, null)
+ radio_connection = SSradio.add_object(src, frequency, null)
if(emergency_shuttle.shuttle)
CRASH("An emergency shuttle has already been defined.")
emergency_shuttle.shuttle = src
diff --git a/code/modules/shuttles/shuttle_supply.dm b/code/modules/shuttles/shuttle_supply.dm
index e030f336cf..616f904377 100644
--- a/code/modules/shuttles/shuttle_supply.dm
+++ b/code/modules/shuttles/shuttle_supply.dm
@@ -17,7 +17,7 @@
var/datum/signal/S = new()
S.source = src
S.data = list("command" = "supply")
- var/datum/radio_frequency/F = radio_controller.return_frequency(1435)
+ var/datum/radio_frequency/F = SSradio.return_frequency(1435)
F.post_signal(src, S)
//it would be cool to play a sound here
diff --git a/code/modules/spells/targeted/targeted.dm b/code/modules/spells/targeted/targeted.dm
index e36d6477e7..847d4cbfa5 100644
--- a/code/modules/spells/targeted/targeted.dm
+++ b/code/modules/spells/targeted/targeted.dm
@@ -86,7 +86,7 @@ Targeted spells have two useful flags: INCLUDEUSER and SELECTABLE. These are exp
possible_targets += target
if(spell_flags & SELECTABLE)
- for(var/i = 1; i<=max_targets, i++)
+ for(var/i = 1, i<=max_targets, i++)
if(!possible_targets.len)
break
var/mob/M = tgui_input_list(user, "Choose the target for the spell.", "Targeting", possible_targets)
diff --git a/code/modules/stockmarket/articles.dm b/code/modules/stockmarket/articles.dm
index c9c5bc1127..1ed89d786f 100644
--- a/code/modules/stockmarket/articles.dm
+++ b/code/modules/stockmarket/articles.dm
@@ -94,11 +94,11 @@ GLOBAL_LIST_EMPTY(FrozenAccounts)
/datum/article/proc/generateAuthorName()
switch(rand(1,3))
if (1)
- return "[consonant()]. [pick(last_names)]"
+ return "[consonant()]. [pick(GLOB.last_names)]"
if (2)
- return "[prob(50) ? pick(first_names_male) : pick(first_names_female)] [consonant()].[prob(50) ? "[consonant()]. " : null] [pick(last_names)]"
+ return "[prob(50) ? pick(GLOB.first_names_male) : pick(GLOB.first_names_female)] [consonant()].[prob(50) ? "[consonant()]. " : null] [pick(GLOB.last_names)]"
if (3)
- return "[prob(50) ? pick(first_names_male) : pick(first_names_female)] \"[prob(50) ? pick(first_names_male) : pick(first_names_female)]\" [pick(last_names)]"
+ return "[prob(50) ? pick(GLOB.first_names_male) : pick(GLOB.first_names_female)] \"[prob(50) ? pick(GLOB.first_names_male) : pick(GLOB.first_names_female)]\" [pick(GLOB.last_names)]"
/datum/article/proc/formatSpacetime()
var/ticksc = round(ticks/100)
diff --git a/code/modules/tables/tables.dm b/code/modules/tables/tables.dm
index d07c5d24a3..e53a435543 100644
--- a/code/modules/tables/tables.dm
+++ b/code/modules/tables/tables.dm
@@ -72,7 +72,7 @@ var/list/table_icon_cache = list()
// reset color/alpha, since they're set for nice map previews
color = "#ffffff"
alpha = 255
- update_connections(ticker && ticker.current_state == GAME_STATE_PLAYING)
+ update_connections(SSticker && SSticker.current_state == GAME_STATE_PLAYING)
update_icon()
update_desc()
update_material()
diff --git a/code/modules/tgchat/to_chat.dm b/code/modules/tgchat/to_chat.dm
index 7b2da90066..6b054ba796 100644
--- a/code/modules/tgchat/to_chat.dm
+++ b/code/modules/tgchat/to_chat.dm
@@ -76,7 +76,7 @@
confidential = FALSE
)
//if(isnull(Master) || !SSchat?.initialized || !MC_RUNNING(SSchat.init_stage))
- if(isnull(Master) || !SSchat?.subsystem_initialized)
+ if(isnull(Master) || !SSchat?.initialized)
to_chat_immediate(target, html, type, text, avoid_highlighting)
return
diff --git a/code/modules/tgui/modules/communications.dm b/code/modules/tgui/modules/communications.dm
index 0379910257..45bd3390db 100644
--- a/code/modules/tgui/modules/communications.dm
+++ b/code/modules/tgui/modules/communications.dm
@@ -175,7 +175,7 @@
return global_message_listener
/proc/post_status(atom/source, command, data1, data2, mob/user = null)
- var/datum/radio_frequency/frequency = radio_controller.return_frequency(1435)
+ var/datum/radio_frequency/frequency = SSradio.return_frequency(1435)
if(!frequency)
return
@@ -381,7 +381,7 @@
PS.allowedtocall = !(PS.allowedtocall)
/proc/call_shuttle_proc(var/mob/user)
- if ((!( ticker ) || !emergency_shuttle.location()))
+ if ((!( SSticker ) || !emergency_shuttle.location()))
return
if(!GLOB.universe.OnShuttleCall(user))
@@ -408,7 +408,7 @@
to_chat(user, "The emergency shuttle is already on its way.")
return
- if(ticker.mode.name == "blob")
+ if(SSticker.mode.name == "blob")
to_chat(user, "Under directive 7-10, [station_name()] is quarantined until further notice.")
return
@@ -420,7 +420,7 @@
return
/proc/init_shift_change(var/mob/user, var/force = 0)
- if ((!( ticker ) || !emergency_shuttle.location()))
+ if ((!( SSticker ) || !emergency_shuttle.location()))
return
if(emergency_shuttle.going_to_centcom())
@@ -445,11 +445,11 @@
to_chat(user, "The shuttle is refueling. Please wait another [round((54000-world.time)/60)] minutes before trying again.")
return
- if(ticker.mode.auto_recall_shuttle)
+ if(SSticker.mode.auto_recall_shuttle)
//New version pretends to call the shuttle but cause the shuttle to return after a random duration.
emergency_shuttle.auto_recall = 1
- if(ticker.mode.name == "blob" || ticker.mode.name == "epidemic")
+ if(SSticker.mode.name == "blob" || SSticker.mode.name == "epidemic")
to_chat(user, "Under directive 7-10, [station_name()] is quarantined until further notice.")
return
@@ -467,9 +467,9 @@
return
/proc/cancel_call_proc(var/mob/user)
- if (!( ticker ) || !emergency_shuttle.can_recall())
+ if (!( SSticker ) || !emergency_shuttle.can_recall())
return
- if((ticker.mode.name == "blob")||(ticker.mode.name == "Meteor"))
+ if((SSticker.mode.name == "blob")||(SSticker.mode.name == "Meteor"))
return
if(!emergency_shuttle.going_to_centcom()) //check that shuttle isn't already heading to CentCom
diff --git a/code/modules/tgui/modules/late_choices.dm b/code/modules/tgui/modules/late_choices.dm
index 15d9b6ba80..14751393a7 100644
--- a/code/modules/tgui/modules/late_choices.dm
+++ b/code/modules/tgui/modules/late_choices.dm
@@ -121,7 +121,7 @@
if(!CONFIG_GET(flag/enter_allowed))
to_chat(new_user, span_notice("There is an administrative lock on entering the game!"))
return
- else if(ticker && ticker.mode && ticker.mode.explosion_in_progress)
+ else if(SSticker && SSticker.mode && SSticker.mode.explosion_in_progress)
to_chat(new_user, span_danger("The station is currently exploding. Joining would go poorly."))
return
diff --git a/code/modules/unit_tests/README.md b/code/modules/unit_tests/README.md
new file mode 100644
index 0000000000..1f8bc271b3
--- /dev/null
+++ b/code/modules/unit_tests/README.md
@@ -0,0 +1,83 @@
+# Unit Tests
+
+## What is unit testing?
+
+Unit tests are automated code to verify that parts of the game work exactly as they should. For example, [a test to make sure that the amputation surgery actually amputates the limb](https://github.com/tgstation/tgstation/blob/e416283f162b86345a8623125ab866839b1ac40d/code/modules/unit_tests/surgeries.dm#L1-L13). These are ran every time a PR is made, and thus are very helpful for preventing bugs from cropping up in your code that would've otherwise gone unnoticed. For example, would you have thought to check [that beach boys would still work the same after editing pizza](https://github.com/tgstation/tgstation/pull/53641#issuecomment-691384934)? If you value your time, probably not.
+
+On their most basic level, when `UNIT_TESTS` is defined, all subtypes of `/datum/unit_test` will have their `Run` proc executed. From here, if `Fail` is called at any point, then the tests will report as failed.
+
+## How do I write one?
+
+1. Find a relevant file.
+
+All unit test related code is in `code/modules/unit_tests`. If you are adding a new test for a surgery, for example, then you'd open `surgeries.dm`. If a relevant file does not exist, simply create one in this folder, then `#include` it in `_unit_tests.dm`.
+
+2. Create the unit test.
+
+To make a new unit test, you simply need to define a `/datum/unit_test`.
+
+For example, let's suppose that we are creating a test to make sure a proc `square` correctly raises inputs to the power of two. We'd start with first:
+
+```
+/datum/unit_test/square/Run()
+```
+
+This defines our new unit test, `/datum/unit_test/square`. Inside this function, we're then going to run through whatever we want to check. Tests provide a few assertion functions to make this easy. For now, we're going to use `TEST_ASSERT_EQUAL`.
+
+```
+/datum/unit_test/square/Run()
+ TEST_ASSERT_EQUAL(square(3), 9, "square(3) did not return 9")
+ TEST_ASSERT_EQUAL(square(4), 16, "square(4) did not return 16")
+```
+
+As you can hopefully tell, we're simply checking if the output of `square` matches the output we are expecting. If the test fails, it'll report the error message given as well as whatever the actual output was.
+
+3. Run the unit test
+
+Open `code/_compile_options.dm` and uncomment the following line.
+
+```
+//#define UNIT_TESTS //If this is uncommented, we do a single run though of the game setup and tear down process with unit tests in between
+```
+
+There are 3 ways to run unit tests
+
+- Run tgstation.dmb in Dream Daemon. Don't bother trying to connect, you won't need to. You'll be able to see the outputs of all the tests. You'll get to see which tests failed and for what reason. If they all pass, you're set!
+
+- Launch game from VS Code. Launch the game as normal & you will see the output of your unit tests in your fancy chat window. This is preferred as you can use the debugger to step through each line of your unit test & can use the games inbuilt debugging tools to further aid in testing
+
+- Use VS Code Tgstation Test Explorer Extension. This allows you to run tests without launching the game & can also run focused tests(either a single or a selected group)
+
+## How to think about tests
+
+Unit tests exist to prevent bugs that would happen in a real game. Thus, they should attempt to emulate the game world wherever possible. For example, the [quick swap sanity test](https://github.com/tgstation/tgstation/blob/e416283f162b86345a8623125ab866839b1ac40d/code/modules/unit_tests/quick_swap_sanity.dm) emulates a _real_ scenario of the bug it fixed occurring by creating a character and giving it real items. The unrecommended alternative would be to create special test-only items. This isn't a hard rule, the [reagent method exposure tests](https://github.com/tgstation/tgstation/blob/e416283f162b86345a8623125ab866839b1ac40d/code/modules/unit_tests/reagent_mod_expose.dm) create a test-only reagent for example, but do keep it in mind.
+
+Unit tests should also be just that--testing _units_ of code. For example, instead of having one massive test for reagents, there are instead several smaller tests for testing exposure, metabolization, etc.
+
+## The unit testing API
+
+You can find more information about all of these from their respective doc comments, but for a brief overview:
+
+`/datum/unit_test` - The base for all tests to be ran. Subtypes must override `Run()`. `New()` and `Destroy()` can be used for setup and teardown. To fail, use `TEST_FAIL(reason)`.
+
+`/datum/unit_test/proc/allocate(type, ...)` - Allocates an instance of the provided type with the given arguments. Is automatically destroyed when the test is over. Commonly seen in the form of `var/mob/living/carbon/human/human = allocate(/mob/living/carbon/human/consistent)`.
+
+`TEST_FAIL(reason)` - Marks a failure at this location, but does not stop the test.
+
+`TEST_ASSERT(assertion, reason)` - Stops the unit test and fails if the assertion is not met. For example: `TEST_ASSERT(powered(), "Machine is not powered")`.
+
+`TEST_ASSERT_NOTNULL(a, message)` - Same as `TEST_ASSERT`, but checks if `!isnull(a)`. For example: `TEST_ASSERT_NOTNULL(myatom, "My atom was never set!")`.
+
+`TEST_ASSERT_NULL(a, message)` - Same as `TEST_ASSERT`, but checks if `isnull(a)`. If not, gives a helpful message showing what `a` was. For example: `TEST_ASSERT_NULL(delme, "Delme was never cleaned up!")`.
+
+`TEST_ASSERT_EQUAL(a, b, message)` - Same as `TEST_ASSERT`, but checks if `a == b`. If not, gives a helpful message showing what both `a` and `b` were. For example: `TEST_ASSERT_EQUAL(2 + 2, 4, "The universe is falling apart before our eyes!")`.
+
+`TEST_ASSERT_NOTEQUAL(a, b, message)` - Same as `TEST_ASSERT_EQUAL`, but reversed.
+
+`TEST_FOCUS(test_path)` - _Only_ run the test provided within the parameters. Useful for reducing noise. For example, if we only want to run our example square test, we can add `TEST_FOCUS(/datum/unit_test/square)`. Should _never_ be pushed in a pull request--you will be laughed at.
+
+## Final Notes
+
+- Writing tests before you attempt to fix the bug can actually speed up development a lot! It means you don't have to go in game and folllow the same exact steps manually every time. This process is known as "TDD" (test driven development). Write the test first, make sure it fails, _then_ start work on the fix/feature, and you'll know you're done when your tests pass. If you do try this, do make sure to confirm in a non-testing environment just to double check.
+- Make sure that your tests don't accidentally call RNG functions like `prob`. Since RNG is seeded during tests, you may not realize you have until someone else makes a PR and the tests fail!
+- Do your best not to change the behavior of non-testing code during tests. While it may sometimes be necessary in the case of situations such as the above, it is still a slippery slope that can lead to the code you're testing being too different from the production environment to be useful.
diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm
new file mode 100644
index 0000000000..b582dd8973
--- /dev/null
+++ b/code/modules/unit_tests/_unit_tests.dm
@@ -0,0 +1,129 @@
+//include unit test files in this module in this ifdef
+//Keep this sorted alphabetically
+
+#if defined(UNIT_TESTS) || defined(SPACEMAN_DMM)
+
+/// For advanced cases, fail unconditionally but don't return (so a test can return multiple results)
+#define TEST_FAIL(reason) (Fail(reason || "No reason", __FILE__, __LINE__))
+
+/// Asserts that a condition is true
+/// If the condition is not true, fails the test
+#define TEST_ASSERT(assertion, reason) if (!(assertion)) { return Fail("Assertion failed: [reason || "No reason"]", __FILE__, __LINE__) }
+
+/// Asserts that a parameter is not null
+#define TEST_ASSERT_NOTNULL(a, reason) if (isnull(a)) { return Fail("Expected non-null value: [reason || "No reason"]", __FILE__, __LINE__) }
+
+/// Asserts that a parameter is null
+#define TEST_ASSERT_NULL(a, reason) if (!isnull(a)) { return Fail("Expected null value but received [a]: [reason || "No reason"]", __FILE__, __LINE__) }
+
+/// Asserts that the two parameters passed are equal, fails otherwise
+/// Optionally allows an additional message in the case of a failure
+#define TEST_ASSERT_EQUAL(a, b, message) do { \
+ var/lhs = ##a; \
+ var/rhs = ##b; \
+ if (lhs != rhs) { \
+ return Fail("Expected [isnull(lhs) ? "null" : lhs] to be equal to [isnull(rhs) ? "null" : rhs].[message ? " [message]" : ""]", __FILE__, __LINE__); \
+ } \
+} while (FALSE)
+
+/// Asserts that the two parameters passed are not equal, fails otherwise
+/// Optionally allows an additional message in the case of a failure
+#define TEST_ASSERT_NOTEQUAL(a, b, message) do { \
+ var/lhs = ##a; \
+ var/rhs = ##b; \
+ if (lhs == rhs) { \
+ return Fail("Expected [isnull(lhs) ? "null" : lhs] to not be equal to [isnull(rhs) ? "null" : rhs].[message ? " [message]" : ""]", __FILE__, __LINE__); \
+ } \
+} while (FALSE)
+
+/// *Only* run the test provided within the parentheses
+/// This is useful for debugging when you want to reduce noise, but should never be pushed
+/// Intended to be used in the manner of `TEST_FOCUS(/datum/unit_test/math)`
+#define TEST_FOCUS(test_path) ##test_path { focus = TRUE; }
+
+/// Logs a noticable message on GitHub, but will not mark as an error.
+/// Use this when something shouldn't happen and is of note, but shouldn't block CI.
+/// Does not mark the test as failed.
+#define TEST_NOTICE(source, message) source.log_for_test((##message), "notice", __FILE__, __LINE__)
+
+/// Constants indicating unit test completion status
+#define UNIT_TEST_PASSED 0
+#define UNIT_TEST_FAILED 1
+#define UNIT_TEST_SKIPPED 2
+
+#define TEST_PRE 0
+#define TEST_DEFAULT 1
+/// After most test steps, used for tests that run long so shorter issues can be noticed faster
+#define TEST_LONGER 10
+/// This must be the one of last tests to run due to the inherent nature of the test iterating every single tangible atom in the game and qdeleting all of them (while taking long sleeps to make sure the garbage collector fires properly) taking a large amount of time.
+#define TEST_CREATE_AND_DESTROY 9001
+/**
+ * For tests that rely on create and destroy having iterated through every (tangible) atom so they don't have to do something similar.
+ * Keep in mind tho that create and destroy will absolutely break the test platform, anything that relies on its shape cannot come after it.
+ */
+#define TEST_AFTER_CREATE_AND_DESTROY INFINITY
+
+/// Change color to red on ANSI terminal output, if enabled with -DANSICOLORS.
+#ifdef ANSICOLORS
+#define TEST_OUTPUT_RED(text) "\x1B\x5B1;31m[text]\x1B\x5B0m"
+#else
+#define TEST_OUTPUT_RED(text) (text)
+#endif
+/// Change color to green on ANSI terminal output, if enabled with -DANSICOLORS.
+#ifdef ANSICOLORS
+#define TEST_OUTPUT_GREEN(text) "\x1B\x5B1;32m[text]\x1B\x5B0m"
+#else
+#define TEST_OUTPUT_GREEN(text) (text)
+#endif
+/// Change color to yellow on ANSI terminal output, if enabled with -DANSICOLORS.
+#ifdef ANSICOLORS
+#define TEST_OUTPUT_YELLOW(text) "\x1B\x5B1;33m[text]\x1B\x5B0m"
+#else
+#define TEST_OUTPUT_YELLOW(text) (text)
+#endif
+/// A trait source when adding traits through unit tests
+#define TRAIT_SOURCE_UNIT_TESTS "unit_tests"
+/// Helper to allocate a new object with the implied type (the type of the variable it's assigned to) in the corner of the test room
+#define EASY_ALLOCATE(arguments...) allocate(__IMPLIED_TYPE__, run_loc_floor_bottom_left, ##arguments)
+
+// BEGIN_INCLUDE
+#include "asset_smart_cache.dm"
+//#include "clothing_tests.dm" // FIXME
+#include "component_tests.dm"
+#include "cosmetic_tests.dm"
+#include "dcs_check_list_arguments.dm"
+#include "dcs_get_id_from_elements.dm"
+#include "decl_tests.dm"
+#include "disease_tests.dm"
+#include "focus_only_tests.dm"
+#include "font_awesome_icons.dm"
+#include "genetics_tests.dm"
+#include "language_tests.dm"
+#include "loadout_tests.dm"
+#include "map_tests.dm"
+#include "material_tests.dm"
+// #include "nuke_cinematic.dm" // TODO: This is probably fixed later on
+#include "poster_tests.dm"
+// #include "preferences.dm" // This unit test is missing some other stuff
+#include "reagent_tests.dm"
+#include "recipe_tests.dm"
+#include "recycler_vendor_tests.dm"
+#include "robot_tests.dm"
+#include "spritesheets.dm"
+#include "sqlite_tests.dm"
+#include "subsystem_init.dm"
+#include "tgui_create_message.dm"
+#include "timer_sanity.dm"
+#include "trait_tests.dm"
+#include "unit_test.dm"
+// #include "vore_tests.dm" // FIXME: REWRITE OR FIX THIS
+// END_INCLUDE
+#ifdef REFERENCE_TRACKING_DEBUG //Don't try and parse this file if ref tracking isn't turned on. IE: don't parse ref tracking please mr linter
+#include "find_reference_sanity.dm"
+#endif
+
+#undef TEST_ASSERT
+#undef TEST_ASSERT_EQUAL
+#undef TEST_ASSERT_NOTEQUAL
+//#undef TEST_FOCUS - This define is used by vscode unit test extension to pick specific unit tests to run and appended later so needs to be used out of scope here
+#endif
diff --git a/code/modules/unit_tests/asset_smart_cache.dm b/code/modules/unit_tests/asset_smart_cache.dm
new file mode 100644
index 0000000000..48d1506b10
--- /dev/null
+++ b/code/modules/unit_tests/asset_smart_cache.dm
@@ -0,0 +1,63 @@
+/datum/asset/spritesheet_batched/test
+ name = "test"
+ load_immediately = TRUE
+ force_cache = TRUE
+ // Don't let the asset subsystem load this. This is how we trick it.
+ _abstract = /datum/asset/spritesheet_batched/test
+ var/static/list/items = list(/obj/item/binoculars, /obj/item/camera, /obj/item/clothing/under/color/blue, /obj/item/clothing/under/color/black)
+
+/datum/asset/spritesheet_batched/test/create_spritesheets()
+ for(var/atom/item as anything in items)
+ if (!ispath(item, /atom))
+ return FALSE
+ var/imgid = replacetext(replacetext("[item]", "/obj/item/", ""), "/", "-")
+ insert_icon(imgid, get_display_icon_for(item))
+ // Get some coverage on each operation.
+ var/datum/universal_icon/I = uni_icon('icons/effects/effects.dmi', "nothing")
+ I.blend_icon(uni_icon('icons/effects/effects.dmi', "sparks"), ICON_OVERLAY)
+ I.blend_color("#ff0000", ICON_MULTIPLY)
+ I.scale(64, 64)
+ I.crop(1, 1, 128, 64) // we'll test for the scale later.
+ insert_icon("test", I)
+
+/datum/asset/spritesheet_batched/test/unregister()
+ SSassets.transport.unregister_asset("spritesheet_[name].css")
+ if(length(sizes))
+ for(var/size_id in sizes)
+ SSassets.transport.unregister_asset("[name]_[size_id].png")
+
+/datum/unit_test/test_asset_smart_cache/Run()
+ fdel("[ASSET_CROSS_ROUND_SMART_CACHE_DIRECTORY]/spritesheet_cache.test.json")
+ fdel("data/spritesheets/spritesheet_test.css")
+ var/datum/asset/spritesheet_batched/test/sheet = new()
+ TEST_ASSERT(sheet.fully_generated, "Spritesheet not generated!")
+ // Cache should be invalid initially.
+ TEST_ASSERT(sheet.cache_result, "Spritesheet smart cache was VALID when it should be INVALID!")
+ for(var/item in sheet.items)
+ var/imgid = replacetext(replacetext("[item]", "/obj/item/", ""), "/", "-")
+ // All items should be in sprites list.
+ TEST_ASSERT(imgid in sheet.sprites, "Item [item] not present in spritesheet result!")
+ TEST_ASSERT("test" in sheet.sprites, "Item test not present in spritesheet result!")
+ TEST_ASSERT("128x64" in sheet.sizes, "Test icon was not output as 128x64!")
+ // cache wrote properly
+ TEST_ASSERT(fexists("[ASSET_CROSS_ROUND_SMART_CACHE_DIRECTORY]/spritesheet_cache.test.json"), "Smart cache entry did not write!")
+ // Clear it out and get ready to do it again, this time loading from cache
+ sheet.unregister()
+ sheet.entries = list()
+ sheet.sprites = list()
+ sheet.sizes = list()
+ sheet.job_id = null
+ sheet.cache_result = null
+ sheet.cache_data = null
+ sheet.cache_job_id = null
+ sheet.fully_generated = FALSE
+
+ sheet.register()
+ TEST_ASSERT(sheet.fully_generated, "Spritesheet did not load from smart cache properly!")
+ // Check for CACHE_VALID
+ TEST_ASSERT(!sheet.cache_result, "Spritesheet did not load from smart cache, it was invalid despite having the same input data!")
+ // Cleanup files.
+ fdel("[ASSET_CROSS_ROUND_SMART_CACHE_DIRECTORY]/spritesheet_cache.test.json")
+ fdel("data/spritesheets/spritesheet_test.css")
+ for(var/size in sheet.sizes)
+ fdel("data/spritesheets/test_[size].png")
diff --git a/code/unit_tests/clothing_tests.dm b/code/modules/unit_tests/clothing_tests.dm
similarity index 68%
rename from code/unit_tests/clothing_tests.dm
rename to code/modules/unit_tests/clothing_tests.dm
index f905d9072c..258caecb85 100644
--- a/code/unit_tests/clothing_tests.dm
+++ b/code/modules/unit_tests/clothing_tests.dm
@@ -1,8 +1,12 @@
+/// converted unit test, maybe should be fully refactored
+/// MIGHT REQUIRE BIGGER REWORK
+
+/// Test that checks if all clothing is valid
/datum/unit_test/all_clothing_shall_be_valid
- name = "CLOTHING: All clothing shall be valid"
var/signal_failed = FALSE
-/datum/unit_test/all_clothing_shall_be_valid/start_test()
+
+/datum/unit_test/all_clothing_shall_be_valid/Run()
var/failed = 0
var/obj/storage = new()
@@ -22,8 +26,8 @@
failed += test_clothing(C)
if(i > tenths * a_tenth)
- log_unit_test("Clothing - Progress [tenths * 10]% - [i]/[scan.len]")
- log_unit_test("---------------------------------------------------")
+ //TEST_NOTICE("Clothing - Progress [tenths * 10]% - [i]/[scan.len]")
+ //TEST_NOTICE("---------------------------------------------------")
tenths++
if(istype(C,/obj/item/clothing/suit/storage/hooded))
@@ -36,10 +40,7 @@
qdel(storage)
if(failed)
- fail("One or more /obj/item/clothing items had invalid flags or icons")
- else
- pass("All /obj/item/clothing are valid.")
- return 1
+ TEST_FAIL("One or more /obj/item/clothing items had invalid flags or icons")
/datum/unit_test/all_clothing_shall_be_valid/proc/test_clothing(var/obj/item/clothing/C,var/obj/storage)
var/failed = FALSE
@@ -49,20 +50,15 @@
return FALSE
// ID
- if(!C.name)
- log_unit_test("[C.type]: Clothing - Missing name.")
- failed = TRUE
-
- if(C.name == "")
- log_unit_test("[C.type]: Clothing - Empty name.")
- failed = TRUE
+ TEST_ASSERT(C.name, "[C.type]: Clothing - Missing name.")
+ TEST_ASSERT(C.name != "", "[C.type]: Clothing - Empty name.")
// Icons
if(!("[C.icon_state]" in cached_icon_states(C.icon)))
if(C.icon == initial(C.icon) && C.icon_state == initial(C.icon_state))
- log_unit_test("[C.type]: Clothing - Icon_state \"[C.icon_state]\" is not present in [C.icon].")
+ TEST_NOTICE("[C.type]: Clothing - Icon_state \"[C.icon_state]\" is not present in [C.icon].")
else
- log_unit_test("[C.type]: Clothing - Icon_state \"[C.icon_state]\" is not present in [C.icon]. This icon/state was changed by init. Initial icon \"[initial(C.icon)]\". initial icon_state \"[initial(C.icon_state)]\". Check code.")
+ TEST_NOTICE("[C.type]: Clothing - Icon_state \"[C.icon_state]\" is not present in [C.icon]. This icon/state was changed by init. Initial icon \"[initial(C.icon)]\". initial icon_state \"[initial(C.icon_state)]\". Check code.")
failed = TRUE
// Disabled, as currently not working in a presentable way, spams the CI hard, do not enable unless fixed
@@ -104,47 +100,45 @@
#endif
// Temps
- if(C.min_cold_protection_temperature < 0)
- log_unit_test("[C.type]: Clothing - Cold protection was lower than 0.")
- failed = TRUE
+ TEST_ASSERT(C.min_cold_protection_temperature > 0, "[C.type]: Clothing - Cold protection was lower than 0.")
if(C.max_heat_protection_temperature && C.min_cold_protection_temperature && C.max_heat_protection_temperature < C.min_cold_protection_temperature)
- log_unit_test("[C.type]: Clothing - Maximum heat protection was greater than minimum cold protection.")
+ TEST_NOTICE("[C.type]: Clothing - Maximum heat protection was greater than minimum cold protection.")
failed = TRUE
//var/valid_range = HEAD|UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS
if(C.cold_protection)
if(islist(C.cold_protection))
- log_unit_test("[C.type]: Clothing - cold_protection was defined as a list, when it is a bitflag.")
+ TEST_NOTICE("[C.type]: Clothing - cold_protection was defined as a list, when it is a bitflag.")
failed = TRUE
else if(!isnum(C.cold_protection))
- log_unit_test("[C.type]: Clothing - cold_protection was defined as something other than a number, when it is a bitflag.")
+ TEST_NOTICE("[C.type]: Clothing - cold_protection was defined as something other than a number, when it is a bitflag.")
failed = TRUE
else
if(C.cold_protection && C.cold_protection != FULL_BODY)
// Check flags that should be unused
if(C.cold_protection & FACE)
- log_unit_test("[C.type]: Clothing - cold_protection uses FACE bitflag, this provides no protection, use HEAD.")
+ TEST_NOTICE("[C.type]: Clothing - cold_protection uses FACE bitflag, this provides no protection, use HEAD.")
failed = TRUE
if(C.cold_protection & EYES)
- log_unit_test("[C.type]: Clothing - cold_protection uses EYES bitflag, this provides no protection, use HEAD.")
+ TEST_NOTICE("[C.type]: Clothing - cold_protection uses EYES bitflag, this provides no protection, use HEAD.")
failed = TRUE
if(C.heat_protection)
if(islist(C.heat_protection))
- log_unit_test("[C.type]: Clothing - heat_protection was defined as a list, when it is a bitflag.")
+ TEST_NOTICE("[C.type]: Clothing - heat_protection was defined as a list, when it is a bitflag.")
failed = TRUE
else if(!isnum(C.heat_protection))
- log_unit_test("[C.type]: Clothing - heat_protection was defined as something other than a number, when it is a bitflag.")
+ TEST_NOTICE("[C.type]: Clothing - heat_protection was defined as something other than a number, when it is a bitflag.")
failed = TRUE
else
if(C.heat_protection && C.heat_protection != FULL_BODY)
// Check flags that should be unused
if(C.heat_protection & FACE)
- log_unit_test("[C.type]: Clothing - heat_protection uses FACE bitflag, this provides no protection, use HEAD.")
+ TEST_NOTICE("[C.type]: Clothing - heat_protection uses FACE bitflag, this provides no protection, use HEAD.")
failed = TRUE
if(C.heat_protection & EYES)
- log_unit_test("[C.type]: Clothing - heat_protection uses EYES bitflag, this provides no protection, use HEAD.")
+ TEST_NOTICE("[C.type]: Clothing - heat_protection uses EYES bitflag, this provides no protection, use HEAD.")
failed = TRUE
return failed
@@ -172,6 +166,6 @@
// All that matters
if(!("[set_state]" in cached_icon_states(set_icon)))
- log_unit_test("[item_path]: Clothing - Testing \"[species]\" state \"[set_state]\" for slot \"[slot_name]\", but it was not in dmi \"[set_icon]\"")
+ TEST_NOTICE("[item_path]: Clothing - Testing \"[species]\" state \"[set_state]\" for slot \"[slot_name]\", but it was not in dmi \"[set_icon]\"")
signal_failed = TRUE
return
diff --git a/code/modules/unit_tests/component_tests.dm b/code/modules/unit_tests/component_tests.dm
new file mode 100644
index 0000000000..1fae398251
--- /dev/null
+++ b/code/modules/unit_tests/component_tests.dm
@@ -0,0 +1,7 @@
+/datum/unit_test/component_duping/Run()
+ var/list/bad_dms = list()
+ for(var/t in typesof(/datum/component))
+ var/datum/component/comp = t
+ if(!isnum(initial(comp.dupe_mode)))
+ bad_dms += t
+ TEST_ASSERT(!length(bad_dms), "Components with invalid dupe modes: ([bad_dms.Join(",")])")
diff --git a/code/modules/unit_tests/cosmetic_tests.dm b/code/modules/unit_tests/cosmetic_tests.dm
new file mode 100644
index 0000000000..ef60de0217
--- /dev/null
+++ b/code/modules/unit_tests/cosmetic_tests.dm
@@ -0,0 +1,60 @@
+/// converted unit test, maybe should be fully refactored
+/// MIGHT REQUIRE BIGGER REWORK
+
+/// Test that tests that all cosmetics have unique name entries
+/datum/unit_test/sprite_accessories_shall_be_unique
+
+/datum/unit_test/sprite_accessories_shall_be_unique/Run()
+ validate_accessory_list(/datum/sprite_accessory/ears)
+ validate_accessory_list(/datum/sprite_accessory/facial_hair)
+ validate_accessory_list(/datum/sprite_accessory/hair)
+ validate_accessory_list(/datum/sprite_accessory/hair_accessory)
+ validate_accessory_list(/datum/sprite_accessory/marking)
+ validate_accessory_list(/datum/sprite_accessory/tail)
+ validate_accessory_list(/datum/sprite_accessory/wing)
+
+/datum/unit_test/sprite_accessories_shall_be_unique/proc/validate_accessory_list(var/path)
+ var/list/collection = list()
+ for(var/SP in subtypesof(path))
+ var/datum/sprite_accessory/A = new SP()
+ TEST_ASSERT(A, "[SP]: Cosmetic - Path resolved to null in list.")
+ if(!A)
+ continue
+
+ TEST_ASSERT(A.name, "[A] - [A.type]: Cosmetic - Missing name.")
+
+ if(A.name == DEVELOPER_WARNING_NAME)
+ continue
+
+ TEST_ASSERT(!collection[A.name], "[A] - [A.type]: Cosmetic - Name defined twice. Original def [collection[A.name]]")
+ if(!collection[A.name])
+ collection[A.name] = A.type
+
+ if(istype(A,text2path("[path]/invisible")))
+ TEST_ASSERT(!A.icon_state, "[A] - [A.type]: Cosmetic - Invisible subtype has icon_state.")
+ else if(!A.icon_state)
+ TEST_ASSERT(A.icon_state, "[A] - [A.type]: Cosmetic - Has no icon_state.")
+ else
+ // Check if valid icon
+ validate_icons(A)
+
+ qdel(A)
+
+/datum/unit_test/sprite_accessories_shall_be_unique/proc/validate_icons(datum/sprite_accessory/A)
+ var/actual_icon_state = A.icon_state
+
+ if(istype(A,/datum/sprite_accessory/hair))
+ actual_icon_state = "[A.icon_state]_s"
+ TEST_ASSERT(actual_icon_state in cached_icon_states(A.icon), "[A] - [A.type]: Cosmetic - Icon_state \"[actual_icon_state]\" is not present in [A.icon].")
+
+ if(istype(A,/datum/sprite_accessory/facial_hair))
+ actual_icon_state = "[A.icon_state]_s"
+ TEST_ASSERT(actual_icon_state in cached_icon_states(A.icon), "[A] - [A.type]: Cosmetic - Icon_state \"[actual_icon_state]\" is not present in [A.icon].")
+
+ if(istype(A,/datum/sprite_accessory/marking))
+ var/datum/sprite_accessory/marking/MA = A
+ for(var/BP in MA.body_parts)
+ TEST_ASSERT(BP in BP_ALL, "[A] - [A.type]: Cosmetic - Has an illegal bodypart \"[BP]\". ONLY use parts listed in BP_ALL.")
+
+ actual_icon_state = "[A.icon_state]-[BP]"
+ TEST_ASSERT(actual_icon_state in cached_icon_states(A.icon), "[A] - [A.type]: Cosmetic - Icon_state \"[actual_icon_state]\" is not present in [A.icon].")
diff --git a/code/modules/unit_tests/dcs_check_list_arguments.dm b/code/modules/unit_tests/dcs_check_list_arguments.dm
new file mode 100644
index 0000000000..769574cf95
--- /dev/null
+++ b/code/modules/unit_tests/dcs_check_list_arguments.dm
@@ -0,0 +1,55 @@
+/**
+ * list arguments for bespoke elements are treated as a text ref in the ID, like any other datum.
+ * Which means that, unless cached, using lists as arguments will lead to multiple instance of the same element
+ * being created over and over.
+ *
+ * Because of how it works, this unit test checks that these list datum args
+ * do not share similar contents (when rearranged in descending alpha-numerical order), to ensure that
+ * the least necessary amount of elements is created. So, using static lists may not be enough,
+ * for example, in the case of two different critters using the death_drops element to drop ectoplasm on death, since,
+ * despite being static lists, the two are different instances assigned to different mob types.
+ *
+ * Most of the time, you won't encounter two different static lists with similar contents used as element args,
+ * meaning using static lists is accepted. However, should that happen, it's advised to replace the instances
+ * with either string_list(), string_assoc_list(), string_assoc_nested_list() or string_numbers_list(), depending on the contents of the list.
+ *
+ * In the case of an element where the position of the contents of each datum list argument is important,
+ * ELEMENT_DONT_SORT_LIST_ARGS should be added to its flags, to prevent such issues where the contents are similar
+ * when sorted, but the element instances are not.
+ *
+ * In the off-chance the element is not compatible with this unit test (such as for connect_loc et simila),
+ * you can also use ELEMENT_NO_LIST_UNIT_TEST so that they won't be processed by this unit test at all.
+ */
+/datum/unit_test/dcs_check_list_arguments
+ /**
+ * This unit test requires every (unless ignored) atom to have been created at least once
+ * for a more accurate search, which is why it's run after create_and_destroy is done running.
+ */
+ priority = TEST_AFTER_CREATE_AND_DESTROY
+
+/datum/unit_test/dcs_check_list_arguments/Run()
+ var/we_failed = FALSE
+ for(var/element_type in SSdcs.arguments_that_are_lists_by_element)
+ // Keeps track of the lists that shouldn't be compared with again.
+ var/list/to_ignore = list()
+ var/list/superlist = SSdcs.arguments_that_are_lists_by_element[element_type]
+ for(var/list/current as anything in superlist)
+ to_ignore[current] = TRUE
+ var/list/bad_lists
+ for(var/list/compare as anything in superlist)
+ if(to_ignore[compare])
+ continue
+ if(deep_compare_list(current, compare))
+ if(!bad_lists)
+ bad_lists = list(list(current))
+ bad_lists += list(compare)
+ to_ignore[compare] = TRUE
+ if(bad_lists)
+ we_failed = TRUE
+ //Include the original, unsorted list in the report. It should be easier to find by the contributor.
+ var/list/unsorted_list = superlist[current]
+ TEST_FAIL("Found [length(bad_lists)] datum list arguments with similar contents for [element_type]. Contents: [json_encode(unsorted_list)].")
+ ///Let's avoid sending the same instructions over and over, as it's just going to clutter the CI and confuse someone.
+ if(we_failed)
+ TEST_FAIL("Ensure that each list is static or cached. string_list() (as well as similar procs) is your friend here.\n\
+ Check the documentation from dcs_check_list_arguments.dm for more information!")
diff --git a/code/modules/unit_tests/dcs_get_id_from_elements.dm b/code/modules/unit_tests/dcs_get_id_from_elements.dm
new file mode 100644
index 0000000000..5e6c7b7eaa
--- /dev/null
+++ b/code/modules/unit_tests/dcs_get_id_from_elements.dm
@@ -0,0 +1,46 @@
+/// Tests that DCS' GetIdFromArguments works as expected with standard and odd cases
+/datum/unit_test/dcs_get_id_from_arguments
+
+/datum/unit_test/dcs_get_id_from_arguments/Run()
+ assert_equal(list(1), list(1))
+ assert_equal(list(1, 2), list(1, 2))
+ assert_equal(list(src), list(src))
+
+ assert_equal(
+ list(a = "x", b = "y", c = "z"),
+ list(b = "y", a = "x", c = "z"),
+ list(c = "z", a = "x", b = "y"),
+ )
+
+ TEST_ASSERT_NOTEQUAL(get_id_from_arguments(list(1, 2)), get_id_from_arguments(list(2, 1)), "Swapped arguments should not return the same id")
+ TEST_ASSERT_NOTEQUAL(get_id_from_arguments(list(1, a = "x")), get_id_from_arguments(list(1)), "Named arguments were ignored when creating ids")
+ TEST_ASSERT_NOTEQUAL(get_id_from_arguments(list(1, a = "x")), get_id_from_arguments(list(a = "x")), "Unnamed arguments were ignored when creating ids")
+ TEST_ASSERT_NOTEQUAL(get_id_from_arguments(list(src)), get_id_from_arguments(list(world)), "References to different datums should not return the same id")
+
+ TEST_ASSERT_NOTEQUAL(get_id_from_arguments(list()), SSdcs.GetIdFromArguments(list(/datum/element/dcs_get_id_from_arguments_mock_element2)), "Different elements should not match the same id")
+
+/datum/unit_test/dcs_get_id_from_arguments/proc/assert_equal(reference, ...)
+ var/result = get_id_from_arguments(reference)
+
+ // Start at 1 so the 2nd argument is 2
+ var/index = 1
+
+ for (var/other_case in args)
+ index += 1
+
+ var/other_result = get_id_from_arguments(other_case)
+
+ if (other_result == result)
+ continue
+
+ TEST_FAIL("Case #[index] produces a different GetIdFromArguments result from the first. [other_result] != [result]")
+
+/datum/unit_test/dcs_get_id_from_arguments/proc/get_id_from_arguments(list/arguments)
+ return SSdcs.GetIdFromArguments(list(/datum/element/dcs_get_id_from_arguments_mock_element) + arguments)
+
+// Necessary because GetIdFromArguments uses argument_hash_start_idx from an element type
+/datum/element/dcs_get_id_from_arguments_mock_element
+ argument_hash_start_idx = 2
+
+/datum/element/dcs_get_id_from_arguments_mock_element2
+ argument_hash_start_idx = 2
diff --git a/code/unit_tests/decl_tests.dm b/code/modules/unit_tests/decl_tests.dm
similarity index 54%
rename from code/unit_tests/decl_tests.dm
rename to code/modules/unit_tests/decl_tests.dm
index 41eb7d48e1..8883f0f0dd 100644
--- a/code/unit_tests/decl_tests.dm
+++ b/code/modules/unit_tests/decl_tests.dm
@@ -1,8 +1,6 @@
-/datum/unit_test/emotes_shall_have_unique_keys
- name = "DECLS: Emotes Shall Have Unique Keys"
-
-/datum/unit_test/emotes_shall_have_unique_keys/start_test()
+/// converted unit test, maybe should be fully refactored
+/datum/unit_test/emotes_shall_have_unique_keys/Run()
var/list/keys = list()
var/list/duplicates = list()
@@ -18,9 +16,4 @@
else
keys += emote.key
- if(length(duplicates))
- fail("[length(duplicates)] emote\s had overlapping keys: [english_list(duplicates)].")
- else
- pass("All emotes had unique keys.")
-
- return TRUE
+ TEST_ASSERT(!length(duplicates), "[length(duplicates)] emote\s had overlapping keys: [english_list(duplicates)].")
diff --git a/code/modules/unit_tests/disease_tests.dm b/code/modules/unit_tests/disease_tests.dm
new file mode 100644
index 0000000000..3d4ad4936f
--- /dev/null
+++ b/code/modules/unit_tests/disease_tests.dm
@@ -0,0 +1,17 @@
+/// converted unit test, maybe should be fully refactored
+
+/datum/unit_test/disease_tests/Run()
+ var/list/used_ids = list()
+
+ for(var/datum/disease/D as anything in subtypesof(/datum/disease))
+ if(initial(D.name) == DEVELOPER_WARNING_NAME)
+ continue
+
+ TEST_ASSERT(!(initial(D.medical_name) in used_ids), "[D]: Disease - Had a reused medical name, this is used as an ID and must be unique.")
+ used_ids.Add(initial(D.medical_name))
+
+ TEST_ASSERT_NOTNULL(initial(D.name), "[D]: Disease - Lacks a name.")
+ TEST_ASSERT_NOTEQUAL(initial(D.name), "", "[D]: Disease - Lacks a name.")
+
+ TEST_ASSERT_NOTNULL(initial(D.desc), "[D]: Disease - Lacks a description.")
+ TEST_ASSERT_NOTEQUAL(initial(D.desc), "", "[D]: Disease - Lacks a description.")
diff --git a/code/modules/unit_tests/find_reference_sanity.dm b/code/modules/unit_tests/find_reference_sanity.dm
new file mode 100644
index 0000000000..4bc6445f02
--- /dev/null
+++ b/code/modules/unit_tests/find_reference_sanity.dm
@@ -0,0 +1,152 @@
+///Used to test the completeness of the reference finder proc.
+/datum/unit_test/find_reference_sanity
+
+/atom/movable/ref_holder
+ var/static/atom/movable/ref_test/static_test
+ var/atom/movable/ref_test/test
+ var/list/test_list = list()
+ var/list/test_assoc_list = list()
+
+/atom/movable/ref_holder/Destroy()
+ test = null
+ static_test = null
+ test_list.Cut()
+ test_assoc_list.Cut()
+ return ..()
+
+/atom/movable/ref_test
+ // Gotta make sure we do a full check
+ references_to_clear = INFINITY
+ var/atom/movable/ref_test/self_ref
+
+/atom/movable/ref_test/Destroy(force)
+ self_ref = null
+ return ..()
+
+/datum/unit_test/find_reference_sanity/Run()
+ var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test)
+ var/atom/movable/ref_holder/testbed = allocate(/atom/movable/ref_holder)
+ SSgarbage.should_save_refs = TRUE
+
+ //Sanity check
+ var/refcount = refcount(victim)
+ TEST_ASSERT_EQUAL(refcount, 3, "Should be: test references: 0 + baseline references: 3 (victim var,loc,allocated list)")
+ victim.DoSearchVar(testbed, "Sanity Check") //We increment search time to get around an optimization
+
+ TEST_ASSERT(!LAZYLEN(victim.found_refs), "The ref-tracking tool found a ref where none existed")
+ SSgarbage.should_save_refs = FALSE
+
+/datum/unit_test/find_reference_baseline/Run()
+ var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test)
+ var/atom/movable/ref_holder/testbed = allocate(/atom/movable/ref_holder)
+ SSgarbage.should_save_refs = TRUE
+
+ //Set up for the first round of tests
+ testbed.test = victim
+ testbed.test_list += victim
+ testbed.test_assoc_list["baseline"] = victim
+
+ var/refcount = refcount(victim)
+ TEST_ASSERT_EQUAL(refcount, 6, "Should be: test references: 3 + baseline references: 3 (victim var,loc,allocated list)")
+ victim.DoSearchVar(testbed, "First Run")
+
+ TEST_ASSERT(LAZYACCESS(victim.found_refs, "test"), "The ref-tracking tool failed to find a regular value")
+ TEST_ASSERT(LAZYACCESS(victim.found_refs, testbed.test_list), "The ref-tracking tool failed to find a list entry")
+ TEST_ASSERT(LAZYACCESS(victim.found_refs, testbed.test_assoc_list), "The ref-tracking tool failed to find an assoc list value")
+ SSgarbage.should_save_refs = FALSE
+
+/datum/unit_test/find_reference_exotic/Run()
+ var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test)
+ var/atom/movable/ref_holder/testbed = allocate(/atom/movable/ref_holder)
+ SSgarbage.should_save_refs = TRUE
+
+ //Second round, bit harder this time
+ testbed.overlays += victim
+ testbed.vis_contents += victim
+ testbed.test_assoc_list[victim] = TRUE
+
+ var/refcount = refcount(victim)
+ TEST_ASSERT_EQUAL(refcount, 6, "Should be: test references: 3 + baseline references: 3 (victim var,loc,allocated list)")
+ victim.DoSearchVar(testbed, "Second Run")
+
+ //This is another sanity check
+ TEST_ASSERT(!LAZYACCESS(victim.found_refs, testbed.overlays), "The ref-tracking tool found an overlays entry? That shouldn't be possible")
+ TEST_ASSERT(LAZYACCESS(victim.found_refs, testbed.vis_contents), "The ref-tracking tool failed to find a vis_contents entry")
+ TEST_ASSERT(LAZYACCESS(victim.found_refs, testbed.test_assoc_list), "The ref-tracking tool failed to find an assoc list key")
+ SSgarbage.should_save_refs = FALSE
+
+/datum/unit_test/find_reference_esoteric/Run()
+ var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test)
+ var/atom/movable/ref_holder/testbed = allocate(/atom/movable/ref_holder)
+ SSgarbage.should_save_refs = TRUE
+
+ //Let's get a bit esoteric
+ victim.self_ref = victim
+ var/list/to_find = list(victim)
+ testbed.test_list += list(to_find)
+ var/list/to_find_assoc = list(victim)
+ testbed.test_assoc_list["Nesting"] = to_find_assoc
+
+ var/refcount = refcount(victim)
+ TEST_ASSERT_EQUAL(refcount, 6, "Should be: test references: 3 + baseline references: 3 (victim var,loc,allocated list)")
+ victim.DoSearchVar(victim, "Third Run Self")
+ victim.DoSearchVar(testbed, "Third Run Testbed")
+
+ TEST_ASSERT(LAZYACCESS(victim.found_refs, "self_ref"), "The ref-tracking tool failed to find a self reference")
+ TEST_ASSERT(LAZYACCESS(victim.found_refs, to_find), "The ref-tracking tool failed to find a nested list entry")
+ TEST_ASSERT(LAZYACCESS(victim.found_refs, to_find_assoc), "The ref-tracking tool failed to find a nested assoc list entry")
+ SSgarbage.should_save_refs = FALSE
+
+/datum/unit_test/find_reference_null_key_entry/Run()
+ var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test)
+ var/atom/movable/ref_holder/testbed = allocate(/atom/movable/ref_holder)
+ SSgarbage.should_save_refs = TRUE
+
+ //Calm before the storm
+ testbed.test_assoc_list = list(null = victim)
+ var/refcount = refcount(victim)
+ TEST_ASSERT_EQUAL(refcount, 4, "Should be: test references: 1 + baseline references: 3 (victim var,loc,allocated list)")
+ victim.DoSearchVar(testbed, "Fourth Run")
+
+ TEST_ASSERT(LAZYACCESS(victim.found_refs, testbed.test_assoc_list), "The ref-tracking tool failed to find a null key'd assoc list entry")
+
+/datum/unit_test/find_reference_assoc_investigation/Run()
+ var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test)
+ var/atom/movable/ref_holder/testbed = allocate(/atom/movable/ref_holder)
+ SSgarbage.should_save_refs = TRUE
+
+ //Let's do some more complex assoc list investigation
+ var/list/to_find_in_key = list(victim)
+ testbed.test_assoc_list[to_find_in_key] = list("memes")
+ var/list/to_find_null_assoc_nested = list(victim)
+ testbed.test_assoc_list[null] = to_find_null_assoc_nested
+
+ var/refcount = refcount(victim)
+ TEST_ASSERT_EQUAL(refcount, 5, "Should be: test references: 2 + baseline references: 3 (victim var,loc,allocated list)")
+ victim.DoSearchVar(testbed, "Fifth Run")
+
+ TEST_ASSERT(LAZYACCESS(victim.found_refs, to_find_in_key), "The ref-tracking tool failed to find a nested assoc list key")
+ TEST_ASSERT(LAZYACCESS(victim.found_refs, to_find_null_assoc_nested), "The ref-tracking tool failed to find a null key'd nested assoc list entry")
+ SSgarbage.should_save_refs = FALSE
+
+/datum/unit_test/find_reference_static_investigation/Run()
+ var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test)
+ var/atom/movable/ref_holder/testbed = allocate(/atom/movable/ref_holder)
+ pass(testbed)
+ SSgarbage.should_save_refs = TRUE
+
+ //Lets check static vars now, since those can be a real headache
+ testbed.static_test = victim
+
+ //Yes we do actually need to do this. The searcher refuses to read weird lists
+ //And global.vars is a really weird list
+ var/global_vars = list()
+ for(var/key in global.vars)
+ global_vars[key] = global.vars[key]
+
+ var/refcount = refcount(victim)
+ TEST_ASSERT_EQUAL(refcount, 5, "Should be: test references: 2 + baseline references: 3 (victim var,loc,allocated list)")
+ victim.DoSearchVar(global_vars, "Sixth Run")
+
+ TEST_ASSERT(LAZYACCESS(victim.found_refs, global_vars), "The ref-tracking tool failed to find a natively global variable")
+ SSgarbage.should_save_refs = FALSE
diff --git a/code/modules/unit_tests/focus_only_tests.dm b/code/modules/unit_tests/focus_only_tests.dm
new file mode 100644
index 0000000000..bd2596e16d
--- /dev/null
+++ b/code/modules/unit_tests/focus_only_tests.dm
@@ -0,0 +1,61 @@
+/// These tests perform no behavior of their own, and have their tests offloaded onto other procs.
+/// This is useful in cases like in build_appearance_list where we want to know if any fail,
+/// but is not useful to right a test for.
+/// This file exists so that you can change any of these to TEST_FOCUS and only check for that test.
+/// For example, change /datum/unit_test/focus_only/invalid_overlays to TEST_FOCUS(/datum/unit_test/focus_only/invalid_overlays),
+/// and you will only test the check for invalid overlays in appearance building.
+/datum/unit_test/focus_only
+
+/// Checks that every created emissive has a valid icon_state
+/datum/unit_test/focus_only/invalid_emissives
+
+/// Checks that every overlay passed into build_appearance_list exists in the icon
+/datum/unit_test/focus_only/invalid_overlays
+
+/// Checks that every icon sent to the research_designs spritesheet is valid
+/datum/unit_test/focus_only/invalid_research_designs
+
+/// Checks that every icon sent to vending machines is valid
+/datum/unit_test/focus_only/invalid_vending_machine_icon_states
+
+/// Checks that space does not initialize multiple times
+/datum/unit_test/focus_only/multiple_space_initialization
+
+/// Checks that smoothing_groups and canSmoothWith are properly sorted in /atom/Initialize
+/datum/unit_test/focus_only/sorted_smoothing_groups
+
+/// Checks that floor tiles are properly mapped to broken/burnt
+/datum/unit_test/focus_only/valid_turf_states
+
+/// Checks that nightvision eyes have a full set of color lists
+/datum/unit_test/focus_only/nightvision_color_cutoffs
+
+/// Checks that no light shares a tile/pixel offsets with another
+/datum/unit_test/focus_only/stacked_lights
+
+/// Checks for bad icon / icon state setups in cooking crafting menu
+/datum/unit_test/focus_only/bad_cooking_crafting_icons
+
+/// Ensures openspace never spawns on the bottom of a z stack
+/datum/unit_test/focus_only/openspace_clear
+
+/// Checks to ensure that variables expected to exist in a job datum (for config reasons) actually exist
+/datum/unit_test/focus_only/missing_job_datum_variables
+
+/// Checks that the contents of the fish_counts list are also present in fish_table
+/datum/unit_test/focus_only/fish_sources_tables
+
+/// Checks that maploaded mobs with either the `atmos_requirements` or `body_temp_sensitive`
+/datum/unit_test/focus_only/atmos_and_temp_requirements
+
+/// Ensures only whitelisted planes can have TOPDOWN_LAYERing, and vis versa
+/datum/unit_test/focus_only/topdown_filtering
+
+/// Catches any invalid footstep types set for humans
+/datum/unit_test/focus_only/humanstep_validity
+
+/// Checks icon states generated at runtime are valid
+/datum/unit_test/focus_only/runtime_icon_states
+
+/// Checks that foodtypes are the same for food whether it's spawned or crafted (with the exact required types)
+/datum/unit_test/focus_only/check_foodtypes
diff --git a/code/modules/unit_tests/font_awesome_icons.dm b/code/modules/unit_tests/font_awesome_icons.dm
new file mode 100644
index 0000000000..6ee4372dc6
--- /dev/null
+++ b/code/modules/unit_tests/font_awesome_icons.dm
@@ -0,0 +1,99 @@
+/**
+ * This unit test verifies that all Font Awesome icons are present in code, and that all quirk icons are valid.
+ */
+/datum/unit_test/font_awesome_icons
+ var/font_awesome_css
+ var/list/allowed_icons
+
+/datum/unit_test/font_awesome_icons/Run()
+ var/font_awesome_file = file('html/font-awesome/css/all.min.css')
+ if(isnull(font_awesome_file))
+ TEST_FAIL("Font Awesome CSS file could not be found!")
+ return
+
+ font_awesome_css = file2text(font_awesome_file)
+ if(isnull(font_awesome_css))
+ TEST_NOTICE(src, "Font Awesome CSS file could not be loaded.")
+ return
+
+ load_parse_verify()
+ //verify_quirk_icons()
+ generate_helper_dm_file()
+
+/**
+ * Loads the Font Awesome CSS file, parses it into a list of icon names, and compares it to the list of icons in code.
+ * If there are any differences, note them.
+ */
+/datum/unit_test/font_awesome_icons/proc/load_parse_verify()
+ //log_test("CSS Actual: [length(font_awesome_css)]")
+ log_unit_test("CSS Actual: [length(font_awesome_css)]")
+ allowed_icons = parse_fa_css_into_icon_list(font_awesome_css)
+
+/**
+ * Verifies that all quirk icons are valid.
+ */
+/* NOT IMPLEMENTED
+/datum/unit_test/font_awesome_icons/proc/verify_quirk_icons()
+ for(var/datum/quirk/quirk as anything in subtypesof(/datum/quirk))
+ if(quirk == initial(quirk.abstract_parent_type))
+ continue
+
+ var/quirk_icon = initial(quirk.icon)
+ if(findtext(quirk_icon, "tg-") == 1) // TODO: Validate these as well
+ continue
+
+ if(findtext(quirk_icon, " "))
+ var/list/split = splittext(quirk_icon, " ")
+ quirk_icon = split[length(split)] // respect modifier classes
+
+ if(!(quirk_icon in allowed_icons))
+ TEST_FAIL("Quirk [initial(quirk.name)]([quirk]) has invalid icon: [quirk_icon]")
+*/
+
+/// Parses the given Font Awesome CSS file into a list of icon names.
+/datum/unit_test/font_awesome_icons/proc/parse_fa_css_into_icon_list(css)
+ css = replacetext(css, "\n", "")
+ var/list/css_entries = splittext(css, "}")
+ var/list/icons = list()
+ for(var/entry in css_entries)
+ entry = replacetext(entry, "\t", "")
+ if(!length(entry))
+ continue
+
+ var/entry_contents = splittext(entry, "{")
+ var/list/entry_names = splittext(entry_contents[1], ",")
+ for(var/entry_name in entry_names)
+ entry_names -= entry_name
+
+ if(!findtext(entry_name, ":"))
+ continue
+
+ entry_name = splittext(entry_name, ":")[1]
+ if(!findtext(entry_name, ".fa-"))
+ continue
+
+ entry_name = replacetext(entry_name, ".fa-", "fa-")
+ entry_names |= entry_name
+ icons |= entry_names
+
+ return sortList(icons)
+
+/datum/unit_test/font_awesome_icons/proc/generate_helper_dm_file()
+ var/list/output = list()
+ output += "/* This file is automatically generated by the unit test. Do not edit it manually."
+ output += " * Generating this file is done by running the unit test locally, see the fail message for more details."
+ output += " * All valid font awesome icons should be here."
+ output += " */"
+ output += ""
+
+ for(var/icon in allowed_icons)
+ var/icon_name = replacetext(icon, "fa-", "")
+ output += "#define FA_ICON_[uppertext(replacetext(icon_name, "-", "_"))] \"[icon]\"" // #undef FA_ICON_ // we have this here to avoid define_sanity throwing a fit
+
+ var/output_file = "[output.Join("\n")]\n"
+ rustg_file_write(output_file, "data/font_awesome_icons.dm")
+ var/current = file2text('code/__DEFINES/font_awesome_icons.dm')
+ if(current == output_file)
+ return
+
+ TEST_FAIL("Font Awesome helper file is out of date. Run locally by enabling unit tests, (see _compile_options.dm) and copy 'data/font_awesome_icons.dm' to 'code/__DEFINES/font_awesome_icons.dm'")
diff --git a/code/modules/unit_tests/genetics_tests.dm b/code/modules/unit_tests/genetics_tests.dm
new file mode 100644
index 0000000000..2c39d20ea9
--- /dev/null
+++ b/code/modules/unit_tests/genetics_tests.dm
@@ -0,0 +1,69 @@
+/// converted unit test, maybe should be fully refactored
+
+/// Test that there are enough free gene slots available
+/datum/unit_test/enough_free_gene_slots_must_be_available
+
+/datum/unit_test/enough_free_gene_slots_must_be_available/Run()
+ // Based off of traitgenes scanned on startup
+ TEST_ASSERT(!(GLOB.dna_genes.len > (DNA_SE_LENGTH - 10)), "Too few geneslots are empty, minimum 10. Increase DNA_SE_LENGTH.")
+
+/// Test that there is at least one positive gene
+/datum/unit_test/enough_positive_genes_must_exist
+
+/datum/unit_test/enough_positive_genes_must_exist/Run()
+ // Based off of traitgenes scanned on startup
+ TEST_ASSERT(!(GLOB.dna_genes_good.len < 1), "Must have at least one positive gene.")
+
+/// Test that there is at least one neutral gene
+/datum/unit_test/enough_neutral_genes_must_exist
+
+/datum/unit_test/enough_neutral_genes_must_exist/Run()
+ // Based off of traitgenes scanned on startup
+ TEST_ASSERT(!(GLOB.dna_genes_neutral.len < 1), "Must have at least one neutral gene.")
+
+/// Test that there is at least one bad gene
+/datum/unit_test/enough_bad_genes_must_exist
+
+/datum/unit_test/enough_bad_genes_must_exist/Run()
+ // Based off of traitgenes scanned on startup
+ TEST_ASSERT(!(GLOB.dna_genes_bad.len < 1), "Must have at least one bad gene.")
+
+/// Test that all dna injectors are valid
+/datum/unit_test/all_dna_injectors_must_be_valid
+
+/datum/unit_test/all_dna_injectors_must_be_valid/Run()
+ for(var/injector_path in subtypesof(/obj/item/dnainjector/set_trait))
+ var/obj/item/dnainjector/D = new injector_path()
+ TEST_ASSERT(D.block, "[injector_path]: Genetics - Injector could not resolve geneblock for trait. Missing traitgene?")
+ qdel(D)
+
+/// Test that all genes have unique names
+/datum/unit_test/all_genes_shall_have_unique_name
+
+/datum/unit_test/all_genes_shall_have_unique_name/Run()
+ var/collection = list()
+ for(var/datum/gene/G in GLOB.dna_genes)
+ TEST_ASSERT(!collection[G.name], "[G.name]: Genetics - Gene name was already in use.")
+ collection[G.name] = G.name
+
+/// Test that all genes should have valid activation bounds
+/datum/unit_test/genetraits_should_have_valid_dna_bounds
+
+/datum/unit_test/genetraits_should_have_valid_dna_bounds/Run()
+ for(var/datum/gene/trait/G in GLOB.trait_to_dna_genes)
+ TEST_ASSERT(G.linked_trait, "[G.name]: Genetics - Has missing linked trait.")
+ TEST_ASSERT(G.linked_trait.activity_bounds, "[G.name]: Genetics - Has no activation bounds.")
+ TEST_ASSERT(G.linked_trait.activity_bounds.len, "[G.name]: Genetics - Has empty activation bounds.")
+
+ // DNA activation bounds. Usually they are in a list as follows:
+ // [1]DNA_OFF_LOWERBOUND = 1, begining of the threshold where a gene turns off.
+ // [2]DNA_OFF_UPPERBOUND = a number above 1, end of the treshold where a gene turns off.
+ // [3]DNA_ON_LOWERBOUND = a number above DNA_OFF_UPPERBOUND(even if just by 1), threshold where a gene turns on.
+ // [4]DNA_ON_UPPERBOUND = 4095, end of the threshold where a gene turns on.
+
+ var/list/bounds = G.linked_trait.activity_bounds
+ TEST_ASSERT(bounds[1] > 1, "[G.name]: Genetics - DNA_OFF_LOWERBOUND, was smaller than 1.") // lowest value a gene can be to turn off
+ TEST_ASSERT(bounds[2] > bounds[1], "[G.name]: Genetics - DNA_OFF_UPPERBOUND must be larger than DNA_OFF_LOWERBOUND, and never equal.")
+ TEST_ASSERT(bounds[2] <= bounds[3], "[G.name]: Genetics - DNA_OFF_UPPERBOUND must be smaller than DNA_ON_LOWERBOUND, and never equal.")
+ TEST_ASSERT(bounds[3] < bounds[4], "[G.name]: Genetics - DNA_ON_LOWERBOUND must be smaller than DNA_ON_UPPERBOUND, and never equal.")
+ TEST_ASSERT(bounds[4] < 4095, "[G.name]: Genetics - DNA_ON_UPPERBOUND, was larger than 4095.") // highest value a gene can be to turn on
diff --git a/code/unit_tests/language_tests.dm b/code/modules/unit_tests/language_tests.dm
similarity index 60%
rename from code/unit_tests/language_tests.dm
rename to code/modules/unit_tests/language_tests.dm
index a45add0342..6d22e66e5b 100644
--- a/code/unit_tests/language_tests.dm
+++ b/code/modules/unit_tests/language_tests.dm
@@ -1,29 +1,25 @@
-/datum/unit_test/language_test_shall_have_distinct_names
- name = "LANGUAGES: Entries shall have distinct names"
+/// converted unit test, maybe should be fully refactored
-/datum/unit_test/language_test_shall_have_distinct_names/start_test()
+/// Test that language entries have distinct names
+/datum/unit_test/language_test_shall_have_distinct_names
+
+/datum/unit_test/language_test_shall_have_distinct_names/Run()
if(length(GLOB.language_name_conflicts) != 0)
var/list/name_conflict_log = list()
for(var/conflicted_name in GLOB.language_name_conflicts)
name_conflict_log += "+[length(GLOB.language_name_conflicts[conflicted_name])] languages with name \"[conflicted_name]\"!"
for(var/datum/language/L in GLOB.language_name_conflicts[conflicted_name])
name_conflict_log += "+-+[L.type]"
- fail("Some names are used by more than one language:\n" + name_conflict_log.Join("\n"))
- else
- pass("All languages have distinct names")
- return 1
+ TEST_FAIL("Some names are used by more than one language:\n" + name_conflict_log.Join("\n"))
+/// Test that language entries have distinct keys
/datum/unit_test/language_test_shall_have_distinct_keys
- name = "LANGUAGES: Entries shall have distinct keys"
-/datum/unit_test/language_test_shall_have_distinct_keys/start_test()
+/datum/unit_test/language_test_shall_have_distinct_keys/Run()
if(length(GLOB.language_key_conflicts) != 0)
var/list/key_conflict_log = list()
for(var/conflicted_key in GLOB.language_key_conflicts)
key_conflict_log += "+[length(GLOB.language_key_conflicts[conflicted_key])] languages with key \"[conflicted_key]\"!"
for(var/datum/language/L in GLOB.language_key_conflicts[conflicted_key])
key_conflict_log += "+-+[L]([L.type])"
- fail("Some keys are used by more than one language:\n" + key_conflict_log.Join("\n"))
- else
- pass("All languages in GLOB.all_languages have distinct keys")
- return 1
+ TEST_FAIL("Some keys are used by more than one language:\n" + key_conflict_log.Join("\n"))
diff --git a/code/modules/unit_tests/loadout_tests.dm b/code/modules/unit_tests/loadout_tests.dm
new file mode 100644
index 0000000000..04c8e60280
--- /dev/null
+++ b/code/modules/unit_tests/loadout_tests.dm
@@ -0,0 +1,7 @@
+/// converted unit test, maybe should be fully refactored
+
+/datum/unit_test/loadout_tests/Run()
+ for(var/datum/gear/G as anything in subtypesof(/datum/gear))
+ TEST_ASSERT(initial(G.display_name), "[G]: Loadout - Missing display name.")
+ TEST_ASSERT_NOTNULL(initial(G.cost), "[G]: Loadout - Missing cost.")
+ TEST_ASSERT(initial(G.path), "[G]: Loadout - Missing path definition.")
diff --git a/code/unit_tests/map_tests.dm b/code/modules/unit_tests/map_tests.dm
similarity index 60%
rename from code/unit_tests/map_tests.dm
rename to code/modules/unit_tests/map_tests.dm
index be51e6f9f4..b4ca7ef251 100644
--- a/code/unit_tests/map_tests.dm
+++ b/code/modules/unit_tests/map_tests.dm
@@ -1,9 +1,10 @@
-/datum/unit_test/apc_area_test
- name = "MAP: Area Test APC / Scrubbers / Vents (Defined Z-Levels)"
+/// converted unit test, maybe should be fully refactored
+/// MIGHT REQUIRE BIGGER REWORK
-/datum/unit_test/apc_area_test/start_test()
- var/list/bad_areas = list()
- var/area_test_count = 0
+/// Test that tests the apcs, scrubbers and vents of the defined z-levels
+/datum/unit_test/apc_area_test
+
+/datum/unit_test/apc_area_test/Run()
var/list/exempt_areas = typesof(/area/space,
/area/syndicate_station,
/area/skipjack_station,
@@ -14,7 +15,8 @@
/area/mine,
/area/vacant/vacant_shop,
/area/turbolift,
- /area/submap )
+ /area/submap
+ )
var/list/exempt_from_atmos = typesof(/area/maintenance,
/area/storage,
@@ -43,58 +45,38 @@
for(var/area/A in world)
if((A.z in zs_to_test) && !(A.type in exempt_areas))
- area_test_count++
- var/area_good = 1
var/bad_msg = "--------------- [A.name]([A.type])"
// Scan for areas with extra APCs
if(!(A.type in exempt_from_apc))
- if(isnull(A.apc))
- log_unit_test("[bad_msg] lacks an APC. (X[A.x]|Y[A.y]) - Z[A.z])")
- area_good = 0
- else
+ TEST_ASSERT_NOTNULL(A.apc, "[bad_msg] lacks an APC. (X[A.x]|Y[A.y]) - Z[A.z])")
+
+ if(!isnull(A.apc))
var/list/apc_list = list()
for(var/turf/T in get_current_area_turfs(A))
for(var/atom/S in T.contents)
if(istype(S,/obj/machinery/power/apc))
apc_list.Add(S)
if(apc_list.len > 1)
- area_good = 0
for(var/obj/machinery/power/P in apc_list)
- log_unit_test("[bad_msg] has too many APCs. (X[P.x]|Y[P.y]) - Z[P.z])")
+ TEST_FAIL("[bad_msg] has too many APCs. (X[P.x]|Y[P.y]) - Z[P.z])")
- if(!A.air_scrub_info.len && !(A.type in exempt_from_atmos))
- log_unit_test("[bad_msg] lacks an Air scrubber. (X[A.x]|Y[A.y]) - (Z[A.z])")
- area_good = 0
-
- if(!A.air_vent_info.len && !(A.type in exempt_from_atmos))
- log_unit_test("[bad_msg] lacks an Air vent. (X[A.x]|Y[A.y]) - (Z[A.z])")
- area_good = 0
-
- if(!area_good)
- bad_areas.Add(A)
-
- if(bad_areas.len)
- fail("\[[bad_areas.len]/[area_test_count]\]Some areas lacked APCs, Air Scrubbers, or Air vents.")
- else
- pass("All \[[area_test_count]\] areas contained APCs, Air scrubbers, and Air vents.")
-
- return 1
+ TEST_ASSERT(!(!A.air_scrub_info.len && !(A.type in exempt_from_atmos)), "[bad_msg] lacks an Air scrubber. (X[A.x]|Y[A.y]) - (Z[A.z])")
+ TEST_ASSERT(!(!A.air_vent_info.len && !(A.type in exempt_from_atmos)), "[bad_msg] lacks an Air vent. (X[A.x]|Y[A.y]) - (Z[A.z])")
+/// Test that tests cables on defined z-levels
/datum/unit_test/wire_test
- name = "MAP: Cable Test (Defined Z-Levels)"
-
-/datum/unit_test/wire_test/start_test()
- set background=1
-
var/wire_test_count = 0
- var/bad_tests = 0
var/turf/T = null
var/obj/structure/cable/C = null
var/list/cable_turfs = list()
var/list/dirs_checked = list()
var/list/exempt_from_wires = list()
+
+/datum/unit_test/wire_test/Run()
+ set background = 1
+
exempt_from_wires += using_map.unit_test_exempt_from_wires.Copy()
var/list/zs_to_test = using_map.unit_test_z_levels || list(1) //Either you set it, or you just get z1
@@ -117,31 +99,16 @@
for(C in T)
wire_test_count++
var/combined_dir = "[C.d1]-[C.d2]"
- if(combined_dir in dirs_checked)
- bad_tests++
- log_unit_test("[bad_msg] Contains multiple wires with same direction on top of each other.")
- if(C.dir != SOUTH)
- bad_tests++
- log_unit_test("[bad_msg] Contains wire with dir set, wires MUST face south, use icon_states.")
+ TEST_ASSERT(!(combined_dir in dirs_checked), "[bad_msg] Contains multiple wires with same direction on top of each other.")
+ TEST_ASSERT(C.dir == SOUTH, "[bad_msg] Contains wire with dir set, wires MUST face south, use icon_states.")
dirs_checked.Add(combined_dir)
- log_unit_test("[color] wires checked.")
-
- if(bad_tests)
- fail("\[[bad_tests] / [wire_test_count]\] Some turfs had overlapping wires going the same direction.")
- else
- pass("All \[[wire_test_count]\] wires had no overlapping cables going the same direction.")
-
- return 1
-
+/// Test template no-ops on all maps
/datum/unit_test/template_noops
- name = "MAP: Template no-ops (all maps)"
-
-/datum/unit_test/template_noops/start_test()
-
var/list/log = list()
-
var/turf_noop_count = 0
+
+/datum/unit_test/template_noops/Run()
for(var/turf/template_noop/T in world)
turf_noop_count++
log += "+-- Template Turf @ [T.x], [T.y], [T.z] ([T.loc])"
@@ -152,19 +119,15 @@
log += "+-- Template Area"
if(turf_noop_count || area_noop_count)
- fail("Map contained [turf_noop_count] template turfs and [area_noop_count] template areas at round-start.\n" + log.Join("\n"))
- else
- pass("No template turfs or areas.")
-
- return 1
+ TEST_FAIL("Map contained [turf_noop_count] template turfs and [area_noop_count] template areas at round-start.\n" + log.Join("\n"))
+/// Test active edges on all maps
/datum/unit_test/active_edges
- name = "MAP: Active edges (all maps)"
-
-/datum/unit_test/active_edges/start_test()
+/datum/unit_test/active_edges/Run()
var/active_edges = SSair.active_edges.len
var/list/edge_log = list()
+
if(active_edges)
for(var/connection_edge/E in SSair.active_edges)
var/a_temp = E.A.air.temperature
@@ -205,38 +168,22 @@
edge_log += "+--- Connecting Turf [T] ([T.type]) @ [T.x], [T.y], [T.z] ([T.loc])"
if(active_edges)
- fail("Maps contained [active_edges] active edges at round-start.\n" + edge_log.Join("\n"))
- else
- pass("No active edges.")
-
- return 1
+ TEST_FAIL("Maps contained [active_edges] active edges at round-start.\n" + edge_log.Join("\n"))
+/// Test the ladders on the maps
/datum/unit_test/ladder_test
- name = "MAP: Ladder Test"
-
-/datum/unit_test/ladder_test/start_test()
var/failed = FALSE
+/datum/unit_test/ladder_test/Run()
for(var/obj/structure/ladder/L in world)
var/turf/T = get_turf(L)
+ TEST_ASSERT(T, "[L.x].[L.y].[L.z]: Map - Ladder on invalid turf")
if(!T)
- log_unit_test("[L.x].[L.y].[L.z]: Map - Ladder on invalid turf")
- failed = TRUE
continue
- if(L.allowed_directions & UP)
- if(!L.target_up)
- log_unit_test("[T.x].[T.y].[T.z]: Map - Ladder allows upward movement, but had no ladder above it")
- failed = TRUE
- if(L.allowed_directions & DOWN)
- if(!L.target_down)
- log_unit_test("[T.x].[T.y].[T.z]: Map - Ladder allows downward movement, but had no ladder beneath it")
- failed = TRUE
- if(T.density)
- log_unit_test("[L.x].[L.y].[L.z]: Map - Ladder is inside a wall")
- failed = TRUE
- if(failed)
- fail("Ladders were incorrectly placed, or missing connections.")
- else
- pass("All ladders were correctly placed and had connections.")
- return failed
+ if(L.allowed_directions & UP)
+ TEST_ASSERT(L.target_up, "[T.x].[T.y].[T.z]: Map - Ladder allows upward movement, but had no ladder above it")
+ if(L.allowed_directions & DOWN)
+ TEST_ASSERT(L.target_down, "[T.x].[T.y].[T.z]: Map - Ladder allows downward movement, but had no ladder beneath it")
+
+ TEST_ASSERT(!T.density, "[L.x].[L.y].[L.z]: Map - Ladder is inside a wall")
diff --git a/code/unit_tests/material_tests.dm b/code/modules/unit_tests/material_tests.dm
similarity index 60%
rename from code/unit_tests/material_tests.dm
rename to code/modules/unit_tests/material_tests.dm
index e3c80b6cd3..d276f2ff07 100644
--- a/code/unit_tests/material_tests.dm
+++ b/code/modules/unit_tests/material_tests.dm
@@ -1,7 +1,9 @@
-/datum/unit_test/materials_shall_have_names
- name = "MATERIALS: Materials Shall Have All Names"
+/// converted unit test, maybe should be fully refactored
-/datum/unit_test/materials_shall_have_names/start_test()
+/// Test that a material should have all the name variables set
+/datum/unit_test/materials_shall_have_names
+
+/datum/unit_test/materials_shall_have_names/Run()
var/list/failures = list()
populate_material_list()
for(var/name in global.name_to_material)
@@ -12,8 +14,4 @@
failures[name] = mat.type
if(length(failures))
- fail("[length(failures)] material\s had missing name strings: [english_list(failures)].")
- else
- pass("All materials had all their name strings.")
-
- return TRUE
+ TEST_FAIL("[length(failures)] material\s had missing name strings: [english_list(failures)].")
diff --git a/code/modules/unit_tests/nuke_cinematic.dm b/code/modules/unit_tests/nuke_cinematic.dm
new file mode 100644
index 0000000000..067a11dccc
--- /dev/null
+++ b/code/modules/unit_tests/nuke_cinematic.dm
@@ -0,0 +1,68 @@
+// Some defines for tracking if the correct cinematic / animation is playing.
+#define PLAYING_CORRECT_ANIMATION 2
+#define PLAYING_INCORRECT_NUKE_ANIMATION 1
+#define NOT_PLAYING_ANIMATION 0
+
+/**
+ * Unit tests that a nuke going off plays a cinematic,
+ * and that it actually kills people.
+ */
+/datum/unit_test/nuke_cinematic
+ /// Used to track via signal if the correct cinematic / animation is playing.
+ var/cinematic_playing = NOT_PLAYING_ANIMATION
+ /// Tracks what typepath of cinematic is being played.
+ var/cinematic_playing_type
+
+/datum/unit_test/nuke_cinematic/Run()
+ var/obj/machinery/nuclearbomb/syndicate/nuke = allocate(/obj/machinery/nuclearbomb/syndicate)
+ var/mob/living/carbon/human/nuked = allocate(/mob/living/carbon/human/consistent)
+ var/datum/client_interface/mock_client = new
+ nuked.mock_client = mock_client
+ mock_client.mob = nuked
+
+ var/obj/effect/landmark/observer_start/observer_point = locate(/obj/effect/landmark/observer_start) in landmarks_list
+ TEST_ASSERT_NOTNULL(observer_point, "Nuke cinematic test couldn't find observer spawn to place the nuke.")
+
+ var/turf/turf_on_station = get_turf(observer_point)
+ TEST_ASSERT(is_station_level(turf_on_station.z), "Nuke cinematic test didn't get a turf which was located on the station.")
+
+ nuke.forceMove(turf_on_station)
+ nuked.forceMove(turf_on_station)
+
+ // Pause the check so we don't, y'know, end the round
+ SSticker.roundend_check_paused = TRUE
+ RegisterSignal(SSdcs, COMSIG_GLOB_PLAY_CINEMATIC, PROC_REF(check_cinematic))
+ // actually_explode calls really_actually_explode which sleeps, so this will take a moment.
+ var/nuke_result = nuke.actually_explode()
+
+ TEST_ASSERT_EQUAL(nuke_result, DETONATION_HIT_STATION, "A nuke went off on station, but didn't return DETONATION_HIT_STATION (4). (Got: [nuke_result])")
+ TEST_ASSERT(GLOB.station_was_nuked, "A nuke went off on station, but didn't set station_was_nuked.")
+ // Reset the nuke var back so we don't end the round
+ GLOB.station_was_nuked = FALSE
+ SSticker.roundend_check_paused = FALSE
+
+ switch(cinematic_playing)
+ if(NOT_PLAYING_ANIMATION)
+ TEST_FAIL("No nuke cinematic was played when a nuke was detonated.")
+
+ if(PLAYING_INCORRECT_NUKE_ANIMATION)
+ TEST_FAIL("An incorrect cinematic was played on nuke detonation. (Expected: /datum/cinematic/nuke/self_destruct, Got: [cinematic_playing_type])")
+
+ TEST_ASSERT(QDELETED(nuked), "The nuke victim next to the nuke wasn't gibbed by the nuke.")
+ TEST_ASSERT(QDELETED(nuke), "The nuke itself was not deleted after successfully exploding.")
+ mock_client.mob = null
+
+/// Used to track whenever a cinematic starts playing, so we can check if it's the right one.
+/datum/unit_test/nuke_cinematic/proc/check_cinematic(datum/source, datum/cinematic/playing)
+ SIGNAL_HANDLER
+
+ cinematic_playing_type = playing.type
+ if(istype(playing, /datum/cinematic/nuke/self_destruct))
+ cinematic_playing = PLAYING_CORRECT_ANIMATION
+
+ else if(istype(playing, /datum/cinematic/nuke))
+ cinematic_playing = PLAYING_INCORRECT_NUKE_ANIMATION
+
+#undef PLAYING_CORRECT_ANIMATION
+#undef PLAYING_INCORRECT_NUKE_ANIMATION
+#undef NOT_PLAYING_ANIMATION
diff --git a/code/modules/unit_tests/poster_tests.dm b/code/modules/unit_tests/poster_tests.dm
new file mode 100644
index 0000000000..64e1ca88d6
--- /dev/null
+++ b/code/modules/unit_tests/poster_tests.dm
@@ -0,0 +1,13 @@
+/// converted unit test, maybe should be fully refactored
+
+/datum/unit_test/posters_shall_have_legal_states/Run()
+ var/list/all_posters = decls_repository.get_decls_of_type(/decl/poster)
+ all_posters -= decls_repository.get_decl(/decl/poster/lewd) // Dumb exclusion for now. This really needs to become a valid poster instead of an illegaly made base type
+
+ for(var/path in all_posters)
+ var/decl/poster/D = all_posters[path]
+ var/obj/structure/sign/poster/P = /obj/structure/sign/poster // The base poster shows ALL subtypes except /lewd, so all posters should function here regardless!
+ var/icon/I = initial(P.icon)
+ if(D.icon_override)
+ I = D.icon_override
+ TEST_ASSERT(D.icon_state in cached_icon_states(I), "[D.type]: Poster - missing icon_state \"[D.icon_state]\" in \"[I]\", as [D.icon_override ? "override" : "base"] dmi.")
diff --git a/code/modules/unit_tests/preferences.dm b/code/modules/unit_tests/preferences.dm
new file mode 100644
index 0000000000..32a8542be2
--- /dev/null
+++ b/code/modules/unit_tests/preferences.dm
@@ -0,0 +1,77 @@
+/// Requires all preferences to implement required methods.
+/datum/unit_test/preferences_implement_everything
+
+/datum/unit_test/preferences_implement_everything/Run()
+ var/datum/preferences/preferences = new(new /datum/client_interface)
+ var/mob/living/carbon/human/human = allocate(/mob/living/carbon/human/consistent)
+
+ for (var/preference_type in GLOB.preference_entries)
+ var/datum/preference/preference = GLOB.preference_entries[preference_type]
+ if (preference.savefile_identifier == PREFERENCE_CHARACTER)
+ preference.apply_to_human(human, preference.create_informed_default_value(preferences))
+
+ if (istype(preference, /datum/preference/choiced))
+ var/datum/preference/choiced/choiced_preference = preference
+ choiced_preference.init_possible_values()
+
+ // Smoke-test is_valid
+ preference.is_valid(TRUE)
+ preference.is_valid("string")
+ preference.is_valid(100)
+ preference.is_valid(list(1, 2, 3))
+
+/// Requires all preferences to have a valid, unique savefile_identifier.
+/datum/unit_test/preferences_valid_savefile_key
+
+/datum/unit_test/preferences_valid_savefile_key/Run()
+ var/list/known_savefile_keys = list()
+
+ for (var/preference_type in GLOB.preference_entries)
+ var/datum/preference/preference = GLOB.preference_entries[preference_type]
+ if (!istext(preference.savefile_key))
+ TEST_FAIL("[preference_type] has an invalid savefile_key.")
+
+ if (preference.savefile_key in known_savefile_keys)
+ TEST_FAIL("[preference_type] has a non-unique savefile_key `[preference.savefile_key]`!")
+
+ known_savefile_keys += preference.savefile_key
+
+/// Requires all main features have a main_feature_name
+/datum/unit_test/preferences_valid_main_feature_name
+
+/datum/unit_test/preferences_valid_main_feature_name/Run()
+ for (var/preference_type in GLOB.preference_entries)
+ var/datum/preference/choiced/preference = GLOB.preference_entries[preference_type]
+ if (!istype(preference))
+ continue
+
+ if (preference.category != PREFERENCE_CATEGORY_FEATURES && preference.category != PREFERENCE_CATEGORY_CLOTHING)
+ continue
+
+ TEST_ASSERT(!isnull(preference.main_feature_name), "Preference [preference_type] does not have a main_feature_name set!")
+
+/// Validates that every choiced preference with should_generate_icons implements icon_for,
+/// and that every one that doesn't, doesn't.
+/datum/unit_test/preferences_should_generate_icons_sanity
+
+/datum/unit_test/preferences_should_generate_icons_sanity/Run()
+ for (var/preference_type in GLOB.preference_entries)
+ var/datum/preference/choiced/choiced_preference = GLOB.preference_entries[preference_type]
+ if (!istype(choiced_preference) || choiced_preference.abstract_type == preference_type)
+ continue
+
+ var/list/values = choiced_preference.get_choices()
+
+ if (choiced_preference.should_generate_icons)
+ for (var/value in values)
+ var/icon = choiced_preference.icon_for(value)
+ TEST_ASSERT(istype(icon, /datum/universal_icon) || ispath(icon), "[preference_type] gave [icon] as an icon for [value], which is not a valid value")
+ else
+ var/errored = FALSE
+
+ try
+ choiced_preference.icon_for(values[1])
+ catch
+ errored = TRUE
+
+ TEST_ASSERT(errored, "[preference_type] implemented icon_for, but does not have should_generate_icons = TRUE")
diff --git a/code/modules/unit_tests/reagent_tests.dm b/code/modules/unit_tests/reagent_tests.dm
new file mode 100644
index 0000000000..e1e8a64cad
--- /dev/null
+++ b/code/modules/unit_tests/reagent_tests.dm
@@ -0,0 +1,251 @@
+/// converted unit test, maybe should be fully refactored
+/// MIGHT REQUIRE BIGGER REWORK
+
+/// Test that makes sure that reagent ids and names are unique
+/datum/unit_test/reagent_shall_have_unique_name_and_id
+
+/datum/unit_test/reagent_shall_have_unique_name_and_id/Run()
+ var/collection_name = list()
+ var/collection_id = list()
+
+ for(var/Rpath in subtypesof(/datum/reagent))
+ var/datum/reagent/R = new Rpath()
+
+ if(R.name == REAGENT_DEVELOPER_WARNING) // Ignore these types as they are meant to be overridden
+ continue
+
+ TEST_ASSERT(R.name != "", "[Rpath]: Reagents - reagent name blank.")
+ TEST_ASSERT_NOTEQUAL(R.id, REAGENT_ID_DEVELOPER_WARNING, "[Rpath]: Reagents - reagent ID not set.")
+ TEST_ASSERT_NOTEQUAL(R.description, REAGENT_DESC_DEVELOPER_WARNING, "[Rpath]: Reagents - reagent description unset.")
+
+ TEST_ASSERT(R.id != "", "[Rpath]: Reagents - reagent ID blank.")
+ TEST_ASSERT_EQUAL(R.id, lowertext(R.id), "[Rpath]: Reagents - Reagent ID must be all lowercase.")
+
+ if(!(R.wiki_flag & WIKI_SPOILER)) // If wiki hidden then don't conflict test it against name, used for intentionally copied names like beer2's
+ TEST_ASSERT(!collection_name[R.name], "[Rpath]: Reagents - reagent name \"[R.name]\" is not unique, used first in [collection_name[R.name]].")
+ collection_name[R.name] = R.type
+
+ TEST_ASSERT(!collection_id[R.id], "[Rpath]: Reagents - reagent ID \"[R.id]\" is not unique, used first in [collection_id[R.id]].")
+ collection_id[R.id] = R.type
+
+ TEST_ASSERT(R.supply_conversion_value, "[Rpath]: Reagents - reagent ID \"[R.id]\" does not have supply_conversion_value set.")
+ TEST_ASSERT(R.industrial_use && R.industrial_use != "", "[Rpath]: Reagents - reagent ID \"[R.id]\" does not have industrial_use set.")
+ TEST_ASSERT_NOTEQUAL(R.description, REAGENT_DESC_DEVELOPER_WARNING, "[Rpath]: Reagents - reagent description unset.")
+
+ qdel(R)
+
+/// Test that makes sure that chemical reactions use and produce valid reagents
+/datum/unit_test/chemical_reactions_shall_use_and_produce_valid_reagents
+
+/datum/unit_test/chemical_reactions_shall_use_and_produce_valid_reagents/Run()
+ var/list/collection_id = list()
+
+ var/list/all_reactions = decls_repository.get_decls_of_subtype(/decl/chemical_reaction)
+ for(var/rtype in all_reactions)
+ var/decl/chemical_reaction/CR = all_reactions[rtype]
+ if(CR.name == REAGENT_DEVELOPER_WARNING) // Ignore these types as they are meant to be overridden
+ continue
+
+ TEST_ASSERT_NOTNULL(CR.name, "[CR.type]: Reagents - chemical reaction had null name.")
+ TEST_ASSERT(CR.name != "", "[CR.type]: Reagents - chemical reaction had blank name.")
+ TEST_ASSERT(CR.id, "[CR.type]: Reagents - chemical reaction had invalid ID.")
+ TEST_ASSERT_EQUAL(CR.id, lowertext(CR.id), "[CR.type]: Reagents - chemical reaction ID must be all lowercase.")
+ TEST_ASSERT(!(CR.id in collection_id), "[CR.type]: Reagents - chemical reaction ID \"[CR.name]\" is not unique, used first in [collection_id[CR.id]].")
+
+
+ if(!(CR.id in collection_id))
+ collection_id[CR.id] = CR.type
+
+ TEST_ASSERT(CR.result_amount >= 0, "[CR.type]: Reagents - chemical reaction ID \"[CR.name]\" had less than 0 as as result_amount?")
+
+ if(CR.required_reagents && CR.required_reagents.len)
+ for(var/RR in CR.required_reagents)
+ TEST_ASSERT(SSchemistry.chemical_reagents[RR], "[CR.type]: Reagents - chemical reaction had invalid required reagent ID \"[RR]\".")
+ TEST_ASSERT(CR.required_reagents[RR] > 0, "[CR.type]: Reagents - chemical reaction had invalid required reagent amount or in invalid format \"[CR.required_reagents[RR]]\".")
+
+ if(CR.catalysts && CR.catalysts.len)
+ for(var/RR in CR.catalysts)
+ TEST_ASSERT(SSchemistry.chemical_reagents[RR], "[CR.type]: Reagents - chemical reaction had invalid required reagent ID \"[RR]\".")
+ TEST_ASSERT(CR.catalysts[RR] > 0, "[CR.type]: Reagents - chemical reaction had invalid catalysts amount or in invalid format \"[CR.catalysts[RR]]\".")
+
+ if(CR.inhibitors && CR.inhibitors.len)
+ for(var/RR in CR.inhibitors)
+ TEST_ASSERT(SSchemistry.chemical_reagents[RR], "[CR.type]: Reagents - chemical reaction had invalid required reagent ID \"[RR]\".")
+ TEST_ASSERT(CR.inhibitors[RR] > 0, "[CR.type]: Reagents - chemical reaction had invalid inhibitors amount or in invalid format \"[CR.inhibitors[RR]]\".")
+
+ if(CR.result)
+ TEST_ASSERT(SSchemistry.chemical_reagents[CR.result], "[CR.type]: Reagents - chemical reaction had invalid result reagent ID \"[CR.result]\".")
+
+/// Test that makes sure that prefilled reagent containers have valid reagents
+/datum/unit_test/prefilled_reagent_containers_shall_have_valid_reagents
+
+/datum/unit_test/prefilled_reagent_containers_shall_have_valid_reagents/Run()
+ var/obj/container = new /obj
+ for(var/RC in subtypesof(/obj/item/reagent_containers/glass))
+ var/obj/item/reagent_containers/glass/R = new RC(container)
+
+ if(R.prefill && R.prefill.len)
+ for(var/ID in R.prefill)
+ TEST_ASSERT(SSchemistry.chemical_reagents[ID], "[RC]: Reagents - reagent prefill had invalid reagent ID \"[ID]\".")
+
+ qdel(R)
+
+ for(var/DC in subtypesof(/obj/item/reagent_containers/chem_disp_cartridge))
+ var/obj/item/reagent_containers/chem_disp_cartridge/D = new DC(container)
+
+ if(D.spawn_reagent)
+ TEST_ASSERT(SSchemistry.chemical_reagents[D.spawn_reagent], "[DC]: Reagents - chemical dispenser cartridge had invalid reagent ID \"[D.spawn_reagent]\".")
+
+ qdel(D)
+
+ qdel(container)
+
+/// Test that makes sure that chemical reactions do not conflict
+/datum/unit_test/chemical_reactions_shall_not_conflict
+ var/obj/fake_beaker = null
+ var/list/result_reactions = list()
+
+/datum/unit_test/chemical_reactions_shall_not_conflict/Run()
+ var/failed = FALSE
+
+ #ifdef UNIT_TEST
+ var/list/all_reactions = decls_repository.get_decls_of_subtype(/decl/chemical_reaction)
+ for(var/rtype in all_reactions)
+ var/decl/chemical_reaction/CR = all_reactions[rtype]
+
+ if(CR.name == REAGENT_DEVELOPER_WARNING) // Ignore these types as they are meant to be overridden
+ continue
+ if(!CR.name || CR.name == "" || !CR.id || CR.id == "")
+ continue
+ if(CR.result_amount <= 0) //Makes nothing anyway, or maybe an effect/explosion!
+ continue
+ if(!CR.result) // Cannot check for this
+ continue
+
+ if(istype(CR, /decl/chemical_reaction/instant/slime))
+ // slime time
+ var/decl/chemical_reaction/instant/slime/SR = CR
+ if(!SR.required)
+ continue
+ var/obj/item/slime_extract/E = new SR.required()
+ qdel_swap(fake_beaker, E)
+ fake_beaker.reagents.maximum_volume = 5000
+ else if(istype(CR, /decl/chemical_reaction/distilling))
+ // distilling
+ var/obj/distilling_tester/D = new()
+ qdel_swap(fake_beaker, D)
+ fake_beaker.reagents.maximum_volume = 5000
+ else
+ // regular beaker
+ qdel_swap(fake_beaker, new /obj/item/reagent_containers/glass/beaker())
+ fake_beaker.reagents.maximum_volume = 5000
+
+ // Perform test! If it fails once, it will perform a deeper check trying to use the inhibitors of anything in the beaker
+ RegisterSignal(fake_beaker.reagents, COMSIG_UNITTEST_DATA, PROC_REF(get_signal_data))
+
+ // Check if we failed the test with inhibitors in use, if so we absolutely couldn't make it...
+ // Uncomment the UNIT_TEST section in code\modules\reagents\reactions\_reactions.dm if you require more info
+ TEST_ASSERT(!perform_reaction(CR), "[CR.type]: Reagents - chemical reaction did not produce \"[CR.result]\". CONTAINS: \"[fake_beaker.reagents.get_reagents()]\"")
+ UnregisterSignal(fake_beaker.reagents, COMSIG_UNITTEST_DATA)
+ qdel_null(fake_beaker)
+ #endif
+
+ if(failed)
+ TEST_FAIL("One or more /decl/chemical_reaction subtypes conflict with another reaction.")
+
+/datum/unit_test/chemical_reactions_shall_not_conflict/proc/perform_reaction(var/decl/chemical_reaction/CR, var/list/inhib = list())
+ var/scale = 1
+ if(CR.result_amount < 1)
+ scale = 1 / CR.result_amount // Create at least 1 unit
+
+ // Weird loop here, but this is used to test both instant and distilling reactions
+ // Instants will meet the while() condition on the first loop and go to the next stuff
+ // but distilling will repeat over and over until the temperature test is finished!
+ var/temp_test = 0
+ do
+ // clear for inhibitor searches
+ fake_beaker.reagents.clear_reagents()
+ result_reactions.Cut()
+
+ if(inhib.len) // taken from argument and not reaction! Put in FIRST!
+ for(var/RR in inhib)
+ fake_beaker.reagents.add_reagent(RR, inhib[RR] * scale)
+ if(CR.catalysts) // Required for reaction
+ for(var/RR in CR.catalysts)
+ fake_beaker.reagents.add_reagent(RR, CR.catalysts[RR] * scale)
+ if(CR.required_reagents)
+ for(var/RR in CR.required_reagents)
+ fake_beaker.reagents.add_reagent(RR, CR.required_reagents[RR] * scale)
+
+ if(!istype(CR, /decl/chemical_reaction/distilling))
+ break // Skip the next section if we're not distilling
+
+ // Check distillation at 10 points along its temperature range!
+ // This is so multiple reactions with the same requirements, but different temps, can be tested.
+ temp_test += 0.1
+ var/obj/distilling_tester/DD = fake_beaker
+ DD.test_distilling(CR,temp_test)
+
+ if(fake_beaker.reagents.has_reagent(CR.result))
+ return FALSE // Distilling success
+
+ while(temp_test > 1)
+
+ // Check beaker to see if we reached our goal!
+ if(fake_beaker.reagents.has_reagent(CR.result))
+ return FALSE // INSTANT SUCCESS!
+
+ if(inhib.len)
+ // We've checked with inhibitors, so we're already in inhibitor checking phase.
+ // So we've absolutely failed this time. There is no way to make this...
+ return TRUE
+
+ if(!result_reactions.len)
+ // Nothing to check for inhibitors...
+ for(var/decl/chemical_reaction/test_react in result_reactions)
+ TEST_FAIL("[CR.type]: Reagents - Used [test_react] but failed.")
+ return TRUE
+
+ // Otherwise we check the resulting reagents and use their inhibitor this time!
+ for(var/decl/chemical_reaction/test_react in result_reactions)
+ if(!test_react)
+ continue
+ if(!test_react.inhibitors.len)
+ continue
+ // Test one by one
+ for(var/each in test_react.inhibitors)
+ if(!perform_reaction(CR, list("[each]" = test_react.inhibitors["[each]"])))
+ return FALSE // SUCCESS using an inhibitor!
+ // Test all at once
+ if(!perform_reaction(CR, test_react.inhibitors))
+ return FALSE // SUCCESS using all inhibitors!
+
+ // No inhibiting reagent worked...
+ for(var/decl/chemical_reaction/test_react in result_reactions)
+ TEST_FAIL("[CR.type]: Reagents - Used [test_react] but failed.")
+ return TRUE
+
+/datum/unit_test/chemical_reactions_shall_not_conflict/proc/get_signal_data(atom/source, list/data = list())
+ result_reactions.Add(data[1]) // Append the reactions that happened, then use that to check their inhibitors
+
+/// Test that makes sure that chemical grinding has valid results
+/datum/unit_test/chemical_grinding_must_produce_valid_results
+
+/datum/unit_test/chemical_grinding_must_produce_valid_results/Run()
+ for(var/grind in GLOB.sheet_reagents + GLOB.ore_reagents)
+ var/list/results = GLOB.sheet_reagents[grind]
+
+ if(!results)
+ results = GLOB.ore_reagents[grind]
+
+ // Cursed test
+ TEST_ASSERT(!(!results || !islist(results)), "[grind]: Reagents - Grinding result had invalid list.")
+ if(!results || !islist(results))
+ continue
+
+ TEST_ASSERT(results.len, "[grind]: Reagents - Grinding result had empty.")
+ if(!results.len)
+ continue
+
+ for(var/reg_id in results)
+ TEST_ASSERT(SSchemistry.chemical_reagents[reg_id], "[grind]: Reagents - Grinding result had invalid reagent id \"[reg_id]\".")
diff --git a/code/modules/unit_tests/recipe_tests.dm b/code/modules/unit_tests/recipe_tests.dm
new file mode 100644
index 0000000000..8dd4bb58a1
--- /dev/null
+++ b/code/modules/unit_tests/recipe_tests.dm
@@ -0,0 +1,9 @@
+/// converted unit test, maybe should be fully refactored
+
+/datum/unit_test/recipe_test/Run()
+ for(var/datum/recipe/R in subtypesof(/datum/recipe))
+ TEST_ASSERT_NOTNULL(initial(R.result), "[R]: Recipes - Missing result.")
+ TEST_ASSERT(ispath(initial(R.result), /atom/movable), "[R]: Recipes - Improper result; [initial(R.result)] is not an obj or mob.")
+ TEST_ASSERT_NOTNULL(initial(R.result_quantity), "[R]: Recipes - result_quantity must be set.")
+ TEST_ASSERT(initial(R.result_quantity) <= 0, "[R]: Recipes - result_quantity must be greater than zero.")
+ TEST_ASSERT(ISINTEGER(initial(R.result_quantity)), "[R]: Recipes - result_quantity must be an integer.")
diff --git a/code/modules/unit_tests/recycler_vendor_tests.dm b/code/modules/unit_tests/recycler_vendor_tests.dm
new file mode 100644
index 0000000000..854e3b6166
--- /dev/null
+++ b/code/modules/unit_tests/recycler_vendor_tests.dm
@@ -0,0 +1,7 @@
+/// converted unit test, maybe should be fully refactored
+
+/datum/unit_test/recycler_vendor_tests/Run()
+ for(var/datum/maint_recycler_vendor_entry/R in subtypesof(/datum/maint_recycler_vendor_entry))
+ TEST_ASSERT(!initial(R.object_type_to_spawn) && !initial(R.is_scam), "[R] : Vendor Entry - Missing Object Type on non-scam entry")
+ TEST_ASSERT(initial(R.item_cost) > 0, "[R] : Vendor Entry - Negative Cost")
+ TEST_ASSERT((initial(R.item_cost) == 0) && ((initial(R.per_round_cap) < 0) && (initial(R.per_person_cap) < 0)), "[R] : Vendor Entry - Infinite Item Spawning due to no individual or global item cap")
diff --git a/code/modules/unit_tests/robot_tests.dm b/code/modules/unit_tests/robot_tests.dm
new file mode 100644
index 0000000000..c7f4a4776e
--- /dev/null
+++ b/code/modules/unit_tests/robot_tests.dm
@@ -0,0 +1,169 @@
+/// converted unit test, maybe should be fully refactored
+
+/// Test that all robot sprites are valid
+/datum/unit_test/all_robot_sprites_must_be_valid
+ var/signal_failed = FALSE
+ var/failed = 0
+
+/datum/unit_test/all_robot_sprites_must_be_valid/Run()
+ for(var/sprite in subtypesof(/datum/robot_sprite))
+ var/datum/robot_sprite/RS = new sprite()
+ if(!RS.name) // Parent type, ignore me
+ continue
+
+ TEST_ASSERT(RS.sprite_icon, "[RS.type]: Robots - Robot sprite \"[RS.name]\", missing sprite_icon.")
+ if(!RS.sprite_icon)
+ continue
+
+ var/list/checks = list(
+ "[ROBOT_HAS_SPEED_SPRITE]" = "-roll",
+ "[ROBOT_HAS_SHIELD_SPRITE]" = "-shield",
+ "[ROBOT_HAS_SHIELD_SPEED_SPRITE]" = "-speed_shield",
+ "[ROBOT_HAS_MELEE_SPRITE]" = "-melee",
+ "[ROBOT_HAS_DAGGER_SPRITE]" = "-dagger",
+ "[ROBOT_HAS_BLADE_SPRITE]" = "-blade",
+ "[ROBOT_HAS_GUN_SPRITE]" = "-gun",
+ "[ROBOT_HAS_LASER_SPRITE]" = "-laser",
+ "[ROBOT_HAS_TASER_SPRITE]" = "-taser",
+ "[ROBOT_HAS_DISABLER_SPRITE]" = "-disabler"
+ )
+ for(var/C in checks)
+ if(RS.sprite_flag_check(text2num(C)))
+ check_state(RS,checks[C])
+ // eyes, lights, markings
+ if(RS.has_eye_sprites)
+ check_state(RS,"-eyes")
+ if(RS.has_eye_light_sprites)
+ check_state(RS,"-lights")
+ if(LAZYLEN(RS.sprite_decals))
+ for(var/decal in RS.sprite_decals)
+ check_state(RS,"-[decal]")
+ if(LAZYLEN(RS.sprite_animations))
+ for(var/animation in RS.sprite_animations)
+ check_state(RS,"-[animation]")
+ // Control panel
+ if(RS.has_custom_open_sprites)
+ check_state(RS,"-openpanel_nc")
+ check_state(RS,"-openpanel_c")
+ check_state(RS,"-openpanel_w")
+ // Glow State
+ if(RS.has_glow_sprites)
+ check_state(RS,"-glow")
+ // Bellies
+ if(RS.has_vore_belly_sprites && !RS.belly_capacity_list)
+ if(RS.has_sleeper_light_indicator)
+ // belly r/g light
+ check_state(RS,"-sleeper-r")
+ check_state(RS,"-sleeper-g")
+ if(RS.has_vore_belly_resting_sprites)
+ for(var/rest_style in RS.rest_sprite_options)
+ rest_style = lowertext(rest_style)
+ if(rest_style == "default")
+ rest_style = "rest"
+ check_state(RS,"-sleeper-r-[rest_style]")
+ check_state(RS,"-sleeper-g-[rest_style]")
+ // struggling
+ if(RS.has_vore_struggle_sprite)
+ check_state(RS,"-sleeper-r-struggle")
+ check_state(RS,"-sleeper-g-struggle")
+ if(RS.has_vore_belly_resting_sprites)
+ for(var/rest_style in RS.rest_sprite_options)
+ rest_style = lowertext(rest_style)
+ if(rest_style == "default")
+ rest_style = "rest"
+ check_state(RS,"-sleeper-r-[rest_style]-struggle")
+ check_state(RS,"-sleeper-g-[rest_style]-struggle")
+ else
+ // belly
+ check_state(RS,"-sleeper")
+ if(RS.has_vore_belly_resting_sprites)
+ for(var/rest_style in RS.rest_sprite_options)
+ rest_style = lowertext(rest_style)
+ if(rest_style == "default")
+ rest_style = "rest"
+ check_state(RS,"-sleeper-[rest_style]")
+ // struggling
+ if(RS.has_vore_struggle_sprite)
+ check_state(RS,"-sleeper-struggle")
+ if(RS.has_vore_belly_resting_sprites)
+ for(var/rest_style in RS.rest_sprite_options)
+ rest_style = lowertext(rest_style)
+ if(rest_style == "default")
+ rest_style = "rest"
+ check_state(RS,"-sleeper-[rest_style]-struggle")
+ else if (RS.belly_capacity_list)
+ for(var/belly in RS.belly_capacity_list)
+ for(var/num = 1 to RS.belly_capacity_list[belly])
+ // big belly
+ check_state(RS,"-[belly]-[num]")
+ if(RS.has_vore_belly_resting_sprites)
+ for(var/rest_style in RS.rest_sprite_options)
+ rest_style = lowertext(rest_style)
+ if(rest_style == "default")
+ rest_style = "rest"
+ check_state(RS,"-[belly]-[num]-[rest_style]")
+ // struggling
+ if(RS.has_vore_struggle_sprite)
+ check_state(RS,"-[belly]-[num]-struggle")
+ if(RS.has_vore_belly_resting_sprites)
+ for(var/rest_style in RS.rest_sprite_options)
+ rest_style = lowertext(rest_style)
+ if(rest_style == "default")
+ rest_style = "rest"
+ check_state(RS,"-[belly]-[num]-[rest_style]-struggle")
+ if(RS.belly_light_list)
+ for(var/belly in RS.belly_light_list)
+ for(var/num = 1 to RS.belly_light_list[belly])
+ // multi belly r/g light
+ check_state(RS,"-[belly]-[num]-r")
+ check_state(RS,"-[belly]-[num]-g")
+ if(RS.has_vore_belly_resting_sprites)
+ for(var/rest_style in RS.rest_sprite_options)
+ rest_style = lowertext(rest_style)
+ if(rest_style == "default")
+ rest_style = "rest"
+ check_state(RS,"-[belly]-[num]-r-[rest_style]")
+ check_state(RS,"-[belly]-[num]-g-[rest_style]")
+ // struggling
+ if(RS.has_vore_struggle_sprite)
+ check_state(RS,"-[belly]-[num]-r-struggle")
+ check_state(RS,"-[belly]-[num]-g-struggle")
+ if(RS.has_vore_belly_resting_sprites)
+ for(var/rest_style in RS.rest_sprite_options)
+ rest_style = lowertext(rest_style)
+ if(rest_style == "default")
+ rest_style = "rest"
+ check_state(RS,"-[belly]-[num]-r-[rest_style]-struggle")
+ check_state(RS,"-[belly]-[num]-g-[rest_style]-struggle")
+ // reseting
+ for(var/rest_style in RS.rest_sprite_options)
+ rest_style = lowertext(rest_style)
+ if(rest_style == "default")
+ rest_style = "rest"
+ check_state(RS,"-[rest_style]")
+ if(RS.has_glow_sprites)
+ check_state(RS,"-[rest_style]-glow")
+ if(RS.has_rest_lights_sprites)
+ check_state(RS,"-[rest_style]-lights")
+ if(RS.has_rest_eyes_sprites)
+ check_state(RS,"-[rest_style]-eyes")
+ // death
+ if(RS.has_dead_sprite)
+ check_state(RS,"-wreck")
+ if(RS.has_dead_sprite_overlay) // Only one per dmi
+ TEST_ASSERT("wreck-overlay" in cached_icon_states(RS.sprite_icon), "[RS.type]: Robots - Robot sprite \"[RS.name]\", missing icon_state wreck-overlay, in dmi \"[RS.sprite_icon]\".")
+ // offset
+ var/icon/I = new(RS.sprite_icon)
+ TEST_ASSERT_EQUAL(RS.icon_x, I.Width(), "[RS.type]: Robots - Robot sprite \"[RS.name]\", icon_x \"[RS.icon_x]\" did not match dmi configured width \"[I.Width()]\"")
+ TEST_ASSERT_EQUAL(RS.icon_y, I.Height(), "[RS.type]: Robots - Robot sprite \"[RS.name]\", icon_y \"[RS.icon_y]\" did not match dmi configured height \"[I.Height()]\"")
+ TEST_ASSERT_EQUAL(RS.icon_y, RS.vis_height, "[RS.type]: Robots - Robot sprite \"[RS.name]\", vis_height \"[RS.vis_height]\" did not match icon_y \"[RS.icon_y]\"")
+
+ var/legal_offset = (I.Width() - world.icon_size) / 2
+ TEST_ASSERT_EQUAL(RS.pixel_x, -legal_offset, "[RS.type]: Robots - Robot sprite \"[RS.name]\", pixel_x \"[RS.pixel_x]\" did not have correct offset, should be \"[-legal_offset]\"")
+
+ qdel(I)
+ qdel(RS)
+
+/datum/unit_test/all_robot_sprites_must_be_valid/proc/check_state(datum/robot_sprite/RS, append)
+ var/check_state = "[RS.sprite_icon_state][append]"
+ TEST_ASSERT(check_state in cached_icon_states(RS.sprite_icon), "[RS.type]: Robots - Robot sprite \"[RS.name]\", enabled but missing icon_state \"[check_state]\", in dmi \"[RS.sprite_icon]\".")
diff --git a/code/modules/unit_tests/spritesheets.dm b/code/modules/unit_tests/spritesheets.dm
new file mode 100644
index 0000000000..16666dac17
--- /dev/null
+++ b/code/modules/unit_tests/spritesheets.dm
@@ -0,0 +1,24 @@
+///Checks if spritesheet assets contain icon states with invalid names
+/datum/unit_test/spritesheets
+
+/datum/unit_test/spritesheets/Run()
+ for(var/datum/asset/spritesheet/sheet as anything in subtypesof(/datum/asset/spritesheet))
+ if(!initial(sheet.name)) //Ignore abstract types
+ continue
+ if (sheet == initial(sheet._abstract))
+ continue
+ sheet = get_asset_datum(sheet)
+ for(var/sprite_name in sheet.sprites)
+ if(!sprite_name)
+ TEST_FAIL("Spritesheet [sheet.type] has a nameless icon state.")
+
+ // Test IconForge generated sheets as well
+ for(var/datum/asset/spritesheet_batched/sheet as anything in subtypesof(/datum/asset/spritesheet_batched))
+ if(!initial(sheet.name)) //Ignore abstract types
+ continue
+ if (sheet == initial(sheet._abstract))
+ continue
+ sheet = get_asset_datum(sheet)
+ for(var/sprite_name in sheet.sprites)
+ if(!sprite_name)
+ TEST_FAIL("Spritesheet [sheet.type] has a nameless icon state.")
diff --git a/code/modules/unit_tests/sqlite_tests.dm b/code/modules/unit_tests/sqlite_tests.dm
new file mode 100644
index 0000000000..60f4340949
--- /dev/null
+++ b/code/modules/unit_tests/sqlite_tests.dm
@@ -0,0 +1,50 @@
+/// converted unit test, maybe should be fully refactored
+
+/// Test that inserts and retrieves data from an sqlite database
+/datum/unit_test/sqlite_tests_insert
+
+/datum/unit_test/sqlite_tests_insert/Run()
+ // Arrange.
+ fdel("data/sqlite/testing_sqlite_tests_insert.db") // In case any remain from a previous local test, so we can have a clean new database.
+ var/database/stub_sqlite_db = new("data/sqlite/testing_sqlite_tests_insert.db") // Unfortunately, byond doesn't like having working sqlite stuff w/o a file existing.
+ SSsqlite.init_schema(stub_sqlite_db)
+
+ var/test_author = "alice"
+ var/test_topic = "Test"
+ var/test_content = "Bob is lame."
+
+ // Act.
+ SSsqlite.insert_feedback(author = test_author, topic = test_topic, content = test_content, sqlite_object = stub_sqlite_db)
+ var/database/query/Q = new("SELECT * FROM [SQLITE_TABLE_FEEDBACK]")
+ Q.Execute(stub_sqlite_db)
+ SSsqlite.sqlite_check_for_errors(Q, "Sqlite Insert Unit Test")
+ Q.NextRow()
+
+ // Assert.
+ var/list/row_data = Q.GetRowData()
+ if(!(row_data[SQLITE_FEEDBACK_COLUMN_AUTHOR] == test_author && row_data[SQLITE_FEEDBACK_COLUMN_TOPIC] == test_topic && row_data[SQLITE_FEEDBACK_COLUMN_CONTENT] == test_content))
+ TEST_FAIL("Data insert and loading failed to have matching information.")
+
+/// Test that does a cooldown in a sqlite database
+/datum/unit_test/sqlite_tests_cooldown
+
+/datum/unit_test/sqlite_tests_cooldown/Run()
+ // Arrange.
+ fdel("data/sqlite/testing_sqlite_tests_cooldown.db") // In case any remain from a previous local test, so we can have a clean new database.
+ var/database/stub_sqlite_db = new("data/sqlite/testing_sqlite_tests_cooldown.db") // Unfortunately, byond doesn't like having working sqlite stuff w/o a file existing.
+ SSsqlite.init_schema(stub_sqlite_db)
+
+ var/days_to_wait = 1
+
+ // Act.
+ SSsqlite.insert_feedback(author = "Alice", topic = "Testing", content = "This is a test.", sqlite_object = stub_sqlite_db)
+
+ var/alice_cooldown_block = SSsqlite.get_feedback_cooldown("Alice", days_to_wait, stub_sqlite_db)
+ var/bob_cooldown = SSsqlite.get_feedback_cooldown("Bob", days_to_wait, stub_sqlite_db)
+ days_to_wait = 0
+ var/alice_cooldown_allow = SSsqlite.get_feedback_cooldown("Alice", days_to_wait, stub_sqlite_db)
+
+ // Assert.
+ TEST_ASSERT(alice_cooldown_block > 0, "User 'Alice' did not receive a cooldown, when they were supposed to.")
+ TEST_ASSERT(bob_cooldown <= 0, "User 'Bob' did receive a cooldown, when they did not do anything.")
+ TEST_ASSERT(alice_cooldown_allow <= 0, "User 'Alice' did receive a cooldown, when no cooldown is supposed to be enforced.")
diff --git a/code/modules/unit_tests/subsystem_init.dm b/code/modules/unit_tests/subsystem_init.dm
new file mode 100644
index 0000000000..b9c0a027e3
--- /dev/null
+++ b/code/modules/unit_tests/subsystem_init.dm
@@ -0,0 +1,22 @@
+/// Tests that all subsystems that need to properly initialize.
+/datum/unit_test/subsystem_init
+
+/datum/unit_test/subsystem_init/Run()
+ for(var/datum/controller/subsystem/subsystem as anything in Master.subsystems)
+ if(subsystem.flags & SS_NO_INIT)
+ continue
+ if(subsystem.initialized)
+ continue
+
+ var/should_fail = !(subsystem.flags & SS_OK_TO_FAIL_INIT)
+ var/list/message_strings = list("[subsystem] ([subsystem.type]) is a subsystem meant to initialize but could not get initialized.")
+
+ if(!isnull(subsystem.initialization_failure_message))
+ message_strings += "The subsystem reported the following: [subsystem.initialization_failure_message]"
+
+ if(should_fail)
+ TEST_FAIL(jointext(message_strings, "\n"))
+ continue
+
+ message_strings += "This subsystem is marked as SS_OK_TO_FAIL_INIT. This is still a bug, but it is non-blocking."
+ TEST_NOTICE(src, jointext(message_strings, "\n"))
diff --git a/code/modules/unit_tests/tgui_create_message.dm b/code/modules/unit_tests/tgui_create_message.dm
new file mode 100644
index 0000000000..4d5a4bc0a0
--- /dev/null
+++ b/code/modules/unit_tests/tgui_create_message.dm
@@ -0,0 +1,28 @@
+/// Test that `TGUI_CREATE_MESSAGE` is correctly implemented
+/datum/unit_test/tgui_create_message
+
+/datum/unit_test/tgui_create_message/Run()
+ var/type = "something/here"
+ var/list/payload = list(
+ "name" = "Terry McTider",
+ "heads_caved" = 100,
+ "accomplishments" = list(
+ "nothing",
+ "literally nothing",
+ list(
+ "something" = "just kidding",
+ ),
+ ),
+ )
+
+ var/message = TGUI_CREATE_MESSAGE(type, payload)
+
+ // Ensure consistent output to compare by performing a round-trip.
+ var/output = json_encode(json_decode(url_decode(message)))
+
+ var/expected = json_encode(list(
+ "type" = type,
+ "payload" = payload,
+ ))
+
+ TEST_ASSERT_EQUAL(expected, output, "TGUI_CREATE_MESSAGE didn't round trip properly")
diff --git a/code/modules/unit_tests/timer_sanity.dm b/code/modules/unit_tests/timer_sanity.dm
new file mode 100644
index 0000000000..dbdf3f6d8e
--- /dev/null
+++ b/code/modules/unit_tests/timer_sanity.dm
@@ -0,0 +1,3 @@
+/datum/unit_test/timer_sanity/Run()
+ TEST_ASSERT(SStimer.bucket_count >= 0,
+ "SStimer is going into negative bucket count from something")
diff --git a/code/modules/unit_tests/trait_tests.dm b/code/modules/unit_tests/trait_tests.dm
new file mode 100644
index 0000000000..18d650e14a
--- /dev/null
+++ b/code/modules/unit_tests/trait_tests.dm
@@ -0,0 +1,33 @@
+/// Test that all traits have unique names
+/datum/unit_test/all_traits_unique_names
+
+/datum/unit_test/all_traits_unique_names/Run()
+ var/list/used_named = list()
+ for(var/traitpath in GLOB.all_traits)
+ var/datum/trait/T = GLOB.all_traits[traitpath]
+ TEST_ASSERT(!(T.name in used_named), "[T.type]: Trait - The name \"[T.name]\" is already in use.")
+ used_named.Add(T.name)
+
+/// Test that autohiss traits shall be excluse
+/datum/unit_test/autohiss_shall_be_exclusive
+
+/datum/unit_test/autohiss_shall_be_exclusive/Run()
+ var/list/hiss_list = list()
+ for(var/traitpath in GLOB.all_traits)
+ var/datum/trait/T = GLOB.all_traits[traitpath]
+ if(!T.var_changes)
+ continue
+ if(!islist(T.var_changes["autohiss_basic_map"]))
+ continue
+ hiss_list += T
+
+ for(var/datum/trait/T in hiss_list)
+ TEST_ASSERT(!(T.type in T.excludes), "[T.type]: Trait - Autohiss excludes itself.")
+ TEST_ASSERT(T.excludes, "[T.type]: Trait - Autohiss missing exclusion list.")
+
+ if(!T.excludes)
+ continue
+
+ var/list/exempt_list = hiss_list.Copy() - T // MUST exclude all others except itself
+ for(var/datum/trait/EX in exempt_list)
+ TEST_ASSERT(EX.type in T.excludes, "[T.type]: Trait - Autohiss missing exclusion for [EX].")
diff --git a/code/modules/unit_tests/unit_test.dm b/code/modules/unit_tests/unit_test.dm
new file mode 100644
index 0000000000..681661167c
--- /dev/null
+++ b/code/modules/unit_tests/unit_test.dm
@@ -0,0 +1,402 @@
+/*
+
+Usage:
+Override /Run() to run your test code
+
+Call TEST_FAIL() to fail the test (You should specify a reason)
+
+You may use /New() and /Destroy() for setup/teardown respectively
+
+You can use the run_loc_floor_bottom_left and run_loc_floor_top_right to get turfs for testing
+
+*/
+
+GLOBAL_DATUM(current_test, /datum/unit_test)
+GLOBAL_VAR_INIT(failed_any_test, FALSE)
+/// When unit testing, all logs sent to log_mapping are stored here and retrieved in log_mapping unit test.
+GLOBAL_LIST_EMPTY(unit_test_mapping_logs)
+/// Global assoc list of required mapping items, [item typepath] to [required item datum].
+GLOBAL_LIST_EMPTY(required_map_items)
+
+/// A list of every test that is currently focused.
+/// Use the PERFORM_ALL_TESTS macro instead.
+GLOBAL_VAR_INIT(focused_tests, focused_tests())
+
+/proc/focused_tests()
+ var/list/focused_tests = list()
+ for (var/datum/unit_test/unit_test as anything in subtypesof(/datum/unit_test))
+ if (initial(unit_test.focus))
+ focused_tests += unit_test
+
+ return focused_tests.len > 0 ? focused_tests : null
+
+/datum/unit_test
+ //Bit of metadata for the future maybe
+ var/list/procs_tested
+
+ /// The bottom left floor turf of the testing zone
+ var/turf/run_loc_floor_bottom_left
+
+ /// The top right floor turf of the testing zone
+ var/turf/run_loc_floor_top_right
+ ///The priority of the test, the larger it is the later it fires
+ var/priority = TEST_DEFAULT
+ //internal shit
+ var/focus = FALSE
+ var/succeeded = TRUE
+ var/list/allocated
+ var/list/fail_reasons
+
+ /// Do not instantiate if type matches this
+ abstract_type = /datum/unit_test
+
+ /// List of atoms that we don't want to ever initialize in an agnostic context, like for Create and Destroy. Stored on the base datum for usability in other relevant tests that need this data.
+ var/static/list/uncreatables = null
+
+ // NOT IMPLEMENTED YET: var/static/datum/space_level/reservation
+
+/proc/cmp_unit_test_priority(datum/unit_test/a, datum/unit_test/b)
+ return initial(a.priority) - initial(b.priority)
+
+/datum/unit_test/New()
+ // NOT IMPLEMENTED YET: if (isnull(reservation))
+ // NOT IMPLEMENTED YET: var/datum/map_template/unit_tests/template = new
+ // NOT IMPLEMENTED YET: reservation = template.load_new_z()
+
+ if (isnull(uncreatables))
+ uncreatables = build_list_of_uncreatables()
+
+ allocated = new
+ run_loc_floor_bottom_left = get_turf(locate(/obj/effect/landmark/unit_test_bottom_left) in GLOB.landmarks_list)
+ run_loc_floor_top_right = get_turf(locate(/obj/effect/landmark/unit_test_top_right) in GLOB.landmarks_list)
+
+ // NOT IMPLENTED YET, SEE THE BEGINNING OF THIS PROC
+ //TEST_ASSERT(isfloorturf(run_loc_floor_bottom_left), "run_loc_floor_bottom_left was not a floor ([run_loc_floor_bottom_left])")
+ //TEST_ASSERT(isfloorturf(run_loc_floor_top_right), "run_loc_floor_top_right was not a floor ([run_loc_floor_top_right])")
+
+/datum/unit_test/Destroy()
+ QDEL_LIST(allocated)
+ // clear the test area
+ // NOT IMPLEMENTED YET, SEE NEW() PROC
+ //for (var/turf/turf in Z_TURFS(run_loc_floor_bottom_left.z))
+ // for (var/content in turf.contents)
+ // if (istype(content, /obj/effect/landmark))
+ // continue
+ // qdel(content)
+ return ..()
+
+/datum/unit_test/proc/Run()
+ TEST_FAIL("[type]/Run() called parent or not implemented")
+
+/datum/unit_test/proc/Fail(reason = "No reason", file = "OUTDATED_TEST", line = 1)
+ succeeded = FALSE
+
+ if(!istext(reason))
+ reason = "FORMATTED: [reason != null ? reason : "NULL"]"
+
+ LAZYADD(fail_reasons, list(list(reason, file, line)))
+
+/// Allocates an instance of the provided type, and places it somewhere in an available loc
+/// Instances allocated through this proc will be destroyed when the test is over
+/datum/unit_test/proc/allocate(type, ...)
+ var/list/arguments = args.Copy(2)
+ if(ispath(type, /atom))
+ if (!arguments.len)
+ arguments = list(run_loc_floor_bottom_left)
+ else if (arguments[1] == null)
+ arguments[1] = run_loc_floor_bottom_left
+ var/instance
+ // Byond will throw an index out of bounds if arguments is empty in that arglist call. Sigh
+ if(length(arguments))
+ instance = new type(arglist(arguments))
+ else
+ instance = new type()
+ allocated += instance
+ return instance
+
+/// Resets the air of our testing room to its default
+/datum/unit_test/proc/restore_atmos()
+ // NOT IMPLEMENTED YET, SEE NEW() PROC
+ //var/area/working_area = run_loc_floor_bottom_left.loc
+ //var/list/turf/to_restore = working_area.get_turfs_from_all_zlevels()
+ //for(var/turf/simulated/restore in to_restore)
+ // var/datum/gas_mixture/GM = SSair.parse_gas_string(restore.initial_gas_mix, /datum/gas_mixture/turf)
+ // restore.copy_air(GM)
+ // restore.temperature = initial(restore.temperature)
+ // restore.air_update_turf(update = FALSE, remove = FALSE)
+
+/datum/unit_test/proc/test_screenshot(name, icon/icon)
+ if (!istype(icon))
+ TEST_FAIL("[icon] is not an icon.")
+ return
+
+ var/path_prefix = replacetext(replacetext("[type]", "/datum/unit_test/", ""), "/", "_")
+ name = replacetext(name, "/", "_")
+
+ var/filename = "code/modules/unit_tests/screenshots/[path_prefix]_[name].png"
+
+ if (fexists(filename))
+ var/data_filename = "data/screenshots/[path_prefix]_[name].png"
+ fcopy(icon, data_filename)
+ //log_test("\t[path_prefix]_[name] was found, putting in data/screenshots")
+ log_unit_test("\t[path_prefix]_[name] was found, putting in data/screenshots")
+ else
+#ifdef CIBUILDING
+ // We are runing in real CI, so just pretend it worked and move on
+ fcopy(icon, "data/screenshots_new/[path_prefix]_[name].png")
+
+ //log_test("\t[path_prefix]_[name] was put in data/screenshots_new")
+ log_unit_test("\t[path_prefix]_[name] was put in data/screenshots_new")
+#else
+ // We are probably running in a local build
+ fcopy(icon, filename)
+ TEST_FAIL("Screenshot for [name] did not exist. One has been created.")
+#endif
+
+
+/// Helper for screenshot tests to take an image of an atom from all directions and insert it into one icon
+/datum/unit_test/proc/get_flat_icon_for_all_directions(atom/thing, no_anim = TRUE)
+ var/icon/output = icon('icons/effects/effects.dmi', "nothing")
+
+ for (var/direction in GLOB.cardinal)
+ var/icon/partial = getFlatIcon(thing, defdir = direction, no_anim = no_anim)
+ output.Insert(partial, dir = direction)
+
+ return output
+
+/// Logs a test message. Will use GitHub action syntax found at https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions
+/datum/unit_test/proc/log_for_test(text, priority, file, line)
+ var/map_name = SSmapping.current_map.name
+
+ // Need to escape the text to properly support newlines.
+ var/annotation_text = replacetext(text, "%", "%25")
+ annotation_text = replacetext(annotation_text, "\n", "%0A")
+
+ log_world("::[priority] file=[file],line=[line],title=[map_name]: [type]::[annotation_text]")
+
+/**
+ * Helper to perform a click
+ *
+ * * clicker: The mob that will be clicking
+ * * clicked_on: The atom that will be clicked
+ * * passed_params: A list of parameters to pass to the click
+ */
+/datum/unit_test/proc/click_wrapper(mob/living/clicker, atom/clicked_on, list/passed_params = list(LEFT_CLICK = 1, BUTTON = LEFT_CLICK))
+ clicker.next_click = -1
+ clicker.next_move = -1
+ clicker.ClickOn(clicked_on, list2params(passed_params))
+
+/proc/RunUnitTest(datum/unit_test/test_path, list/test_results)
+ if(ispath(test_path, /datum/unit_test/focus_only))
+ return
+
+ if(initial(test_path.abstract_type) == test_path)
+ return
+
+ var/datum/unit_test/test = new test_path
+
+ GLOB.current_test = test
+ var/duration = REALTIMEOFDAY
+ var/skip_test = (test_path in SSmapping.current_map.skipped_tests)
+ var/test_output_desc = "[test_path]"
+ var/message = ""
+
+ log_world("::group::[test_path]")
+
+ if(skip_test)
+ log_world("[TEST_OUTPUT_YELLOW("SKIPPED")] Skipped run on map [SSmapping.current_map.name].")
+
+ else
+
+ test.Run()
+ test.restore_atmos()
+
+ duration = REALTIMEOFDAY - duration
+ GLOB.current_test = null
+ GLOB.failed_any_test |= !test.succeeded
+
+ var/list/log_entry = list()
+ var/list/fail_reasons = test.fail_reasons
+
+ for(var/reasonID in 1 to LAZYLEN(fail_reasons))
+ var/text = fail_reasons[reasonID][1]
+ var/file = fail_reasons[reasonID][2]
+ var/line = fail_reasons[reasonID][3]
+
+ test.log_for_test(text, "error", file, line)
+
+ // Normal log message
+ log_entry += "\tFAILURE #[reasonID]: [text] at [file]:[line]"
+
+ if(length(log_entry))
+ message = log_entry.Join("\n")
+ //log_test(message)
+ log_unit_test(message)
+
+ test_output_desc += " [duration / 10]s"
+ if (test.succeeded)
+ log_world("[TEST_OUTPUT_GREEN("PASS")] [test_output_desc]")
+
+ log_world("::endgroup::")
+
+ if (!test.succeeded && !skip_test)
+ log_world("::error::[TEST_OUTPUT_RED("FAIL")] [test_output_desc]")
+
+ var/final_status = skip_test ? UNIT_TEST_SKIPPED : (test.succeeded ? UNIT_TEST_PASSED : UNIT_TEST_FAILED)
+ test_results[test_path] = list("status" = final_status, "message" = message, "name" = test_path)
+
+ qdel(test)
+
+/// Builds (and returns) a list of atoms that we shouldn't initialize in generic testing, like Create and Destroy.
+/// It is appreciated to add the reason why the atom shouldn't be initialized if you add it to this list.
+/datum/unit_test/proc/build_list_of_uncreatables()
+ RETURN_TYPE(/list)
+ var/list/returnable_list = list()
+ // The following are just generic, singular types.
+ returnable_list = list(
+ //Never meant to be created, errors out the ass for mobcode reasons
+ /mob/living/carbon,
+ //And another
+ // NOT IMPLEMENTED: /obj/item/slimecross/recurring,
+ //This should be obvious
+ // NOT IMPLEMENTED: /obj/machinery/doomsday_device,
+ //Yet more templates
+ // NOT IMPLEMENTED: /obj/machinery/restaurant_portal,
+ //Template type
+ /obj/machinery/power/turbine,
+ //Template type
+ // NOT IMPLEMENTED: /obj/effect/mob_spawn,
+ //Template type
+ // NOT IMPLEMENTED: /obj/structure/holosign/robot_seat,
+ //Singleton
+ /mob/dview,
+ //Template type
+ // NOT IMPLEMENTED: /obj/item/bodypart,
+ //This is meant to fail extremely loud every single time it occurs in any environment in any context, and it falsely alarms when this unit test iterates it. Let's not spawn it in.
+ /obj/merge_conflict_marker,
+ //briefcase launchpads erroring
+ // NOT IMPLEMENTED: /obj/machinery/launchpad/briefcase,
+ //Wings abstract path
+ // NOT IMPLEMENTED: /obj/item/organ/wings,
+ //Not meant to spawn without the machine wand
+ // NOT IMPLEMENTED: /obj/effect/bug_moving,
+ //The abstract grown item expects a seed, but doesn't have one
+ // NOT IMPLEMENTED: /obj/item/food/grown,
+ ///Single use case holder atom requiring a user
+ // NOT IMPLEMENTED: /atom/movable/looking_holder,
+ )
+
+ // Everything that follows is a typesof() check.
+
+ //Say it with me now, type template
+ // NOT IMPLEMENTED: returnable_list += typesof(/obj/effect/mapping_helpers)
+ //This turf existing is an error in and of itself
+ // NOT IMPLEMENTED: returnable_list += typesof(/turf/baseturf_skipover)
+ // NOT IMPLEMENTED: returnable_list += typesof(/turf/baseturf_bottom)
+ //This demands a borg, so we'll let if off easy
+ // NOT IMPLEMENTED: returnable_list += typesof(/obj/item/modular_computer/pda/silicon)
+ //This one demands a computer, ditto
+ // NOT IMPLEMENTED: returnable_list += typesof(/obj/item/modular_computer/processor)
+ //Very finiky, blacklisting to make things easier
+ // NOT IMPLEMENTED: returnable_list += typesof(/obj/item/poster/wanted)
+ //Needs clients / mobs to observe it to exist. Also includes hallucinations.
+ // NOT IMPLEMENTED: returnable_list += typesof(/obj/effect/client_image_holder)
+ //Same to above. Needs a client / mob / hallucination to observe it to exist.
+ // NOT IMPLEMENTED: returnable_list += typesof(/obj/projectile/hallucination)
+ // NOT IMPLEMENTED: returnable_list += typesof(/obj/item/hallucinated)
+ //We don't have a pod
+ // NOT IMPLEMENTED: returnable_list += typesof(/obj/effect/pod_landingzone_effect)
+ // NOT IMPLEMENTED: returnable_list += typesof(/obj/effect/pod_landingzone)
+ //We have a baseturf limit of 10, adding more than 10 baseturf helpers will kill CI, so here's a future edge case to fix.
+ // NOT IMPLEMENTED: returnable_list += typesof(/obj/effect/baseturf_helper)
+ //No tauma to pass in
+ // NOT IMPLEMENTED: returnable_list += typesof(/mob/eye/imaginary_friend)
+ //No heart to give
+ // NOT IMPLEMENTED: returnable_list += typesof(/obj/structure/ethereal_crystal)
+ //No linked console
+ // NOT IMPLEMENTED: returnable_list += typesof(/mob/eye/camera/remote/base_construction)
+ //See above
+ // NOT IMPLEMENTED: returnable_list += typesof(/mob/eye/camera/remote/shuttle_docker)
+ //Hangs a ref post invoke async, which we don't support. Could put a qdeleted check but it feels hacky
+ // NOT IMPLEMENTED: returnable_list += typesof(/obj/effect/anomaly/grav/high)
+ //See above
+ // NOT IMPLEMENTED: returnable_list += typesof(/obj/effect/timestop)
+ //Sparks can ignite a number of things, causing a fire to burn the floor away. Only you can prevent CI fires
+ // NOT IMPLEMENTED: returnable_list += typesof(/obj/effect/particle_effect/sparks)
+ //See above - These are one of those things.
+ // NOT IMPLEMENTED: returnable_list += typesof(/obj/effect/decal/cleanable/fuel_pool)
+ //Invoke async in init, skippppp
+ // NOT IMPLEMENTED: returnable_list += typesof(/mob/living/silicon/robot/model)
+ //This lad also sleeps
+ // NOT IMPLEMENTED: returnable_list += typesof(/obj/item/hilbertshotel)
+ //this boi spawns turf changing stuff, and it stacks and causes pain. Let's just not
+ // NOT IMPLEMENTED: returnable_list += typesof(/obj/effect/sliding_puzzle)
+ //these can explode and cause the turf to be destroyed at unexpected moments
+ returnable_list += typesof(/obj/effect/mine)
+ // NOT IMPLEMENTED: returnable_list += typesof(/obj/effect/spawner/random/contraband/landmine)
+ // NOT IMPLEMENTED: returnable_list += typesof(/obj/item/minespawner)
+ //Stacks baseturfs, can't be tested here
+ // NOT IMPLEMENTED: returnable_list += typesof(/obj/effect/temp_visual/lava_warning)
+ //Stacks baseturfs, can't be tested here
+ // NOT IMPLEMENTED: returnable_list += typesof(/obj/effect/landmark/ctf)
+ //Our system doesn't support it without warning spam from unregister calls on things that never registered
+ // NOT IMPLEMENTED: returnable_list += typesof(/obj/docking_port)
+ //Asks for a shuttle that may not exist, let's leave it alone
+ returnable_list += typesof(/obj/item/pinpointer/shuttle)
+ //This spawns beams as a part of init, which can sleep past an async proc. This hangs a ref, and fucks us. It's only a problem here because the beam sleeps with CHECK_TICK
+ // NOT IMPLEMENTED: returnable_list += typesof(/obj/structure/alien/resin/flower_bud)
+ //Needs a linked mecha
+ // NOT IMPLEMENTED: returnable_list += typesof(/obj/effect/skyfall_landingzone)
+ //Expects a mob to holderize, we have nothing to give
+ // NOT IMPLEMENTED: returnable_list += typesof(/obj/item/clothing/head/mob_holder)
+ //Needs cards passed into the initilazation args
+ // NOT IMPLEMENTED: returnable_list += typesof(/obj/item/toy/cards/cardhand)
+ //Needs a holodeck area linked to it which is not guarenteed to exist and technically is supposed to have a 1:1 relationship with computer anyway.
+ // NOT IMPLEMENTED: returnable_list += typesof(/obj/machinery/computer/holodeck)
+ //runtimes if not paired with a landmark
+ // NOT IMPLEMENTED: returnable_list += typesof(/obj/structure/transport/linear)
+ // Runtimes if the associated machinery does not exist, but not the base type
+ // NOT IMPLEMENTED: returnable_list += subtypesof(/obj/machinery/airlock_controller)
+ // Always ought to have an associated escape menu. Any references it could possibly hold would need one regardless.
+ // NOT IMPLEMENTED: returnable_list += subtypesof(/atom/movable/screen/escape_menu)
+ // Can't spawn openspace above nothing, it'll get pissy at me
+ // NOT IMPLEMENTED: returnable_list += typesof(/turf/open/space/openspace)
+ // NOT IMPLEMENTED: returnable_list += typesof(/turf/open/openspace)
+ // NOT IMPLEMENTED: returnable_list += typesof(/obj/item/robot_model) // These should never be spawned outside of a robot.
+
+ return returnable_list
+
+/proc/RunUnitTests()
+ CHECK_TICK
+
+ var/list/tests_to_run = subtypesof(/datum/unit_test)
+ var/list/focused_tests = list()
+ for (var/_test_to_run in tests_to_run)
+ var/datum/unit_test/test_to_run = _test_to_run
+ if (initial(test_to_run.focus))
+ focused_tests += test_to_run
+ if(length(focused_tests))
+ tests_to_run = focused_tests
+
+ sortTim(tests_to_run, GLOBAL_PROC_REF(cmp_unit_test_priority))
+
+ var/list/test_results = list()
+
+ //Hell code, we're bound to end the round somehow so let's stop if from ending while we work
+ SSticker.delay_end = TRUE
+ for(var/unit_path in tests_to_run)
+ CHECK_TICK //We check tick first because the unit test we run last may be so expensive that checking tick will lock up this loop forever
+ RunUnitTest(unit_path, test_results)
+ SSticker.delay_end = FALSE
+
+ var/file_name = "data/unit_tests.json"
+ fdel(file_name)
+ file(file_name) << json_encode(test_results)
+
+ SSticker.force_ending = ADMIN_FORCE_END_ROUND
+ //We have to call this manually because del_text can preceed us, and SSticker doesn't fire in the post game
+ SSticker.declare_completion()
+
+/datum/map_template/unit_tests
+ name = "Unit Tests Zone"
+ mappath = "maps/templates/unit_tests.dmm"
diff --git a/code/unit_tests/vore_tests_vr.dm b/code/modules/unit_tests/vore_tests.dm
similarity index 83%
rename from code/unit_tests/vore_tests_vr.dm
rename to code/modules/unit_tests/vore_tests.dm
index b14844b55c..ba1fde8f56 100644
--- a/code/unit_tests/vore_tests_vr.dm
+++ b/code/modules/unit_tests/vore_tests.dm
@@ -1,13 +1,41 @@
+/// converted unit test, maybe should be fully refactored
+
+// FIXME: THIS SHOULD BE REPLACED WITH ALLOCATE IN THE END
+// SEE unit_test.dm NEW() WHY THIS ISNT IMPLEMENTED YET
+/datum/unit_test
+ var/static/default_mobloc = null
+
+// FIXME: THIS SHOULD BE REPLACED WITH ALLOCATE IN THE END
+// SEE unit_test.dm NEW() WHY THIS ISNT IMPLEMENTED YET
+/datum/unit_test/proc/create_test_mob(var/turf/mobloc = null, var/mobtype = /mob/living/carbon/human, var/with_mind = FALSE)
+ if(isnull(mobloc))
+ if(!default_mobloc)
+ for(var/turf/simulated/floor/tiled/T in world)
+ var/pressure = T.zone.air.return_pressure()
+ if(90 < pressure && pressure < 120) // Find a turf between 90 and 120
+ default_mobloc = T
+ break
+ mobloc = default_mobloc
+ if(!mobloc)
+ fail("Unable to find a location to create test mob")
+ return 0
+
+ var/mob/living/carbon/human/H = new mobtype(mobloc)
+
+ if(with_mind)
+ H.mind_initialize("TestKey[rand(0,10000)]")
+
+ return H
+
+/// Test that a human mob does not suffocate in a belly
/datum/unit_test/belly_nonsuffocation
- name = "MOB: human mob does not suffocate in a belly"
var/startLifeTick
var/startOxyloss
var/endOxyloss
var/mob/living/carbon/human/pred
var/mob/living/carbon/human/prey
- async = 1
-/datum/unit_test/belly_nonsuffocation/start_test()
+/datum/unit_test/belly_nonsuffocation/Run()
pred = create_test_mob()
if(!istype(pred))
return 0
diff --git a/code/modules/vore/eating/vorepanel_vr.dm b/code/modules/vore/eating/vorepanel_vr.dm
index fa69f91405..1cfb871a49 100644
--- a/code/modules/vore/eating/vorepanel_vr.dm
+++ b/code/modules/vore/eating/vorepanel_vr.dm
@@ -15,7 +15,7 @@
set name = "Vore Panel"
set category = "IC.Vore"
- if(SSticker.current_state == GAME_STATE_INIT)
+ if(SSticker.current_state == GAME_STATE_STARTUP)
return
if(!isliving(src))
diff --git a/code/names.dm b/code/names.dm
deleted file mode 100644
index 4f26e7ed5c..0000000000
--- a/code/names.dm
+++ /dev/null
@@ -1,16 +0,0 @@
-var/list/ai_names = file2list("config/names/ai.txt")
-var/list/wizard_first = file2list("config/names/wizardfirst.txt")
-var/list/wizard_second = file2list("config/names/wizardsecond.txt")
-var/list/ninja_titles = file2list("config/names/ninjatitle.txt")
-var/list/ninja_names = file2list("config/names/ninjaname.txt")
-var/list/commando_names = file2list("config/names/death_commando.txt")
-var/list/first_names_male = file2list("config/names/first_male.txt")
-var/list/first_names_female = file2list("config/names/first_female.txt")
-var/list/last_names = file2list("config/names/last.txt")
-var/list/clown_names = file2list("config/names/clown.txt")
-
-
-var/list/verbs = file2list("config/names/verbs.txt")
-var/list/adjectives = file2list("config/names/adjectives.txt")
-//loaded on startup because of "
-//would include in rsc if ' was used
diff --git a/code/unit_tests/cosmetic_tests.dm b/code/unit_tests/cosmetic_tests.dm
deleted file mode 100644
index a5b54d835d..0000000000
--- a/code/unit_tests/cosmetic_tests.dm
+++ /dev/null
@@ -1,91 +0,0 @@
-/datum/unit_test/sprite_accessories_shall_be_unique
- name = "COSMETICS: Entries shall have unique name."
-
-/datum/unit_test/sprite_accessories_shall_be_unique/start_test()
- var/failed = 0
-
- failed += validate_accessory_list( /datum/sprite_accessory/ears)
- failed += validate_accessory_list( /datum/sprite_accessory/facial_hair)
- failed += validate_accessory_list( /datum/sprite_accessory/hair)
- failed += validate_accessory_list( /datum/sprite_accessory/hair_accessory)
- failed += validate_accessory_list( /datum/sprite_accessory/marking)
- failed += validate_accessory_list( /datum/sprite_accessory/tail)
- failed += validate_accessory_list( /datum/sprite_accessory/wing)
-
- if(failed)
- fail("One or more /datum/sprite_accessory definitions had invalid names, icon_states, or names were reused definitions")
- else
- pass("All /datum/sprite_accessory definitions had correct settings.")
- return 1
-
-/datum/unit_test/sprite_accessories_shall_be_unique/proc/validate_accessory_list(var/path)
- var/failed = 0
- var/total_good = 0
- var/total_all = 0
-
- var/list/collection = list()
- for(var/SP in subtypesof(path))
- total_all++
- var/datum/sprite_accessory/A = new SP()
- if(!A)
- log_unit_test("[SP]: Cosmetic - Path resolved to null in list.")
- continue
-
- if(!A.name)
- log_unit_test("[A] - [A.type]: Cosmetic - Missing name.")
- failed = 1
-
- if(A.name == DEVELOPER_WARNING_NAME)
- continue
-
- if(collection[A.name])
- log_unit_test("[A] - [A.type]: Cosmetic - Name defined twice. Original def [collection[A.name]]")
- failed = 1
- else
- collection[A.name] = A.type
-
- if(istype(A,text2path("[path]/invisible")))
- if(A.icon_state)
- log_unit_test("[A] - [A.type]: Cosmetic - Invisible subtype has icon_state.")
- failed = 1
- else if(!A.icon_state)
- log_unit_test("[A] - [A.type]: Cosmetic - Has no icon_state.")
- failed = 1
- else
- // Check if valid icon
- failed += validate_icons(A)
-
- total_good++
- qdel(A)
-
- log_unit_test("[path]: Cosmetic - Total valid count: [total_good]/[total_all].")
- return failed
-
-/datum/unit_test/sprite_accessories_shall_be_unique/proc/validate_icons(var/datum/sprite_accessory/A)
- var/failed = 0
- var/actual_icon_state = A.icon_state
- if(istype(A,/datum/sprite_accessory/hair))
- actual_icon_state = "[A.icon_state]_s"
- if(!(actual_icon_state in cached_icon_states(A.icon)))
- log_unit_test("[A] - [A.type]: Cosmetic - Icon_state \"[actual_icon_state]\" is not present in [A.icon].")
- failed = 1
-
- if(istype(A,/datum/sprite_accessory/facial_hair))
- actual_icon_state = "[A.icon_state]_s"
- if(!(actual_icon_state in cached_icon_states(A.icon)))
- log_unit_test("[A] - [A.type]: Cosmetic - Icon_state \"[actual_icon_state]\" is not present in [A.icon].")
- failed = 1
-
- if(istype(A,/datum/sprite_accessory/marking))
- var/datum/sprite_accessory/marking/MA = A
- for(var/BP in MA.body_parts)
- if(!(BP in BP_ALL))
- log_unit_test("[A] - [A.type]: Cosmetic - Has an illegal bodypart \"[BP]\". ONLY use parts listed in BP_ALL.")
- failed = 1
-
- actual_icon_state = "[A.icon_state]-[BP]"
- if(!(actual_icon_state in cached_icon_states(A.icon)))
- log_unit_test("[A] - [A.type]: Cosmetic - Icon_state \"[actual_icon_state]\" is not present in [A.icon].")
- failed = 1
-
- return failed
diff --git a/code/unit_tests/disease_tests.dm b/code/unit_tests/disease_tests.dm
deleted file mode 100644
index 5327b5fdcc..0000000000
--- a/code/unit_tests/disease_tests.dm
+++ /dev/null
@@ -1,32 +0,0 @@
-/datum/unit_test/disease_must_be_valid
- name = "DISEASE: All diseases must have valid data"
-
-/datum/unit_test/disease_must_be_valid/start_test()
- var/failed = FALSE
- var/list/used_ids = list()
-
- var/count = 0
- for(var/datum/disease/D as anything in subtypesof(/datum/disease))
- if(initial(D.name) == DEVELOPER_WARNING_NAME)
- continue
-
- count++
- if(initial(D.medical_name) in used_ids)
- log_unit_test("[D]: Disease - Had a reused medical name, this is used as an ID and must be unique.")
- failed = TRUE
- else
- used_ids.Add(initial(D.medical_name))
-
- if(!initial(D.name) || initial(D.name) == "")
- log_unit_test("[D]: Disease - Lacks a name.")
- failed = TRUE
-
- if(!initial(D.desc) || initial(D.desc) == "")
- log_unit_test("[D]: Disease - Lacks a description.")
- failed = TRUE
-
- if(failed)
- fail("All diseases must have valid data.")
- else
- pass("All [count] diseases have proper data.")
- return failed
diff --git a/code/unit_tests/genetics_tests.dm b/code/unit_tests/genetics_tests.dm
deleted file mode 100644
index f5da724d54..0000000000
--- a/code/unit_tests/genetics_tests.dm
+++ /dev/null
@@ -1,160 +0,0 @@
-/datum/unit_test/enough_free_gene_slots_must_be_available
- name = "GENETICS: Enough free gene slots must be available."
-
-/datum/unit_test/enough_free_gene_slots_must_be_available/start_test()
- var/failed = FALSE
-
- if(GLOB.dna_genes.len > (DNA_SE_LENGTH - 10)) // Based off of traitgenes scanned on startup
- failed = TRUE
-
- if(failed)
- fail("Too few geneslots are empty, minimum 10. Increase DNA_SE_LENGTH.")
- else
- pass("DNA_SE_LENGTH has enough free space remaining.")
- return failed
-
-
-/datum/unit_test/enough_positive_genes_must_exist
- name = "GENETICS: Must be at least one positive gene."
-
-/datum/unit_test/enough_positive_genes_must_exist/start_test()
- var/failed = FALSE
-
- if(GLOB.dna_genes_good.len < 1) // Based off of traitgenes scanned on startup
- failed = TRUE
-
- if(failed)
- fail("Must have at least one positive gene.")
- else
- pass("Has at least one positive gene.")
- return failed
-
-
-/datum/unit_test/enough_neutral_genes_must_exist
- name = "GENETICS: Must be at least one neutral gene."
-
-/datum/unit_test/enough_neutral_genes_must_exist/start_test()
- var/failed = FALSE
-
- if(GLOB.dna_genes_neutral.len < 1) // Based off of traitgenes scanned on startup
- failed = TRUE
-
- if(failed)
- fail("Must have at least one neutral gene.")
- else
- pass("Has at least one neutral gene.")
- return failed
-
-
-/datum/unit_test/enough_bad_genes_must_exist
- name = "GENETICS: Must be at least one bad gene."
-
-/datum/unit_test/enough_bad_genes_must_exist/start_test()
- var/failed = FALSE
-
- if(GLOB.dna_genes_bad.len < 1) // Based off of traitgenes scanned on startup
- failed = TRUE
-
- if(failed)
- fail("Must have at least one bad gene.")
- else
- pass("Has at least one bad gene.")
- return failed
-
-
-/datum/unit_test/all_dna_injectors_must_be_valid
- name = "GENETICS: All dna injectors must be valid."
-
-/datum/unit_test/all_dna_injectors_must_be_valid/start_test()
- var/failed = FALSE
-
- for(var/injector_path in subtypesof(/obj/item/dnainjector/set_trait))
- var/obj/item/dnainjector/D = new injector_path()
- if(!D.block)
- log_unit_test("[injector_path]: Genetics - Injector could not resolve geneblock for trait. Missing traitgene?")
- failed = TRUE
- qdel(D)
-
- if(failed)
- fail("Dna injectors have traits that are not genetraits or are missing.")
- else
- pass("No invalid dna injectors.")
- return failed
-
-
-/datum/unit_test/all_genes_shall_have_unique_name
- name = "GENETICS: All genes shall be init with unique names."
-
-/datum/unit_test/all_genes_shall_have_unique_name/start_test()
- var/failed = FALSE
-
- var/collection = list()
- for(var/datum/gene/G in GLOB.dna_genes)
- if(collection[G.name])
- log_unit_test("[G.name]: Genetics - Gene name was already in use.")
- failed = TRUE
- else
- collection[G.name] = G.name
-
- if(failed)
- fail("Genes shared names. This should not be possible on init, all genes should have their blocknumber attached to them to ensure unique names.")
- else
- pass("All genes have unique names to use as list ids.")
- return failed
-
-
-
-/datum/unit_test/genetraits_should_have_valid_dna_bounds
- name = "GENETICS: All genes should have valid activation bounds."
-
-/datum/unit_test/genetraits_should_have_valid_dna_bounds/start_test()
- var/failed = FALSE
-
- for(var/datum/gene/trait/G in GLOB.trait_to_dna_genes)
- if(!G.linked_trait)
- log_unit_test("[G.name]: Genetics - Has missing linked trait.")
- failed = TRUE
- continue
-
- if(!G.linked_trait.activity_bounds)
- log_unit_test("[G.name]: Genetics - Has no activation bounds.")
- failed = TRUE
- continue
-
- if(!G.linked_trait.activity_bounds.len)
- log_unit_test("[G.name]: Genetics - Has empty activation bounds.")
- failed = TRUE
- continue
-
- // DNA activation bounds. Usually they are in a list as follows:
- // [1]DNA_OFF_LOWERBOUND = 1, begining of the threshold where a gene turns off.
- // [2]DNA_OFF_UPPERBOUND = a number above 1, end of the treshold where a gene turns off.
- // [3]DNA_ON_LOWERBOUND = a number above DNA_OFF_UPPERBOUND(even if just by 1), threshold where a gene turns on.
- // [4]DNA_ON_UPPERBOUND = 4095, end of the threshold where a gene turns on.
-
- var/list/bounds = G.linked_trait.activity_bounds
- if(bounds[1] < 1) // lowest value a gene can be to turn off
- log_unit_test("[G.name]: Genetics - DNA_OFF_LOWERBOUND, was smaller than 1.")
- failed = TRUE
-
- if(bounds[2] < bounds[1])
- log_unit_test("[G.name]: Genetics - DNA_OFF_UPPERBOUND must be larger than DNA_OFF_LOWERBOUND, and never equal.")
- failed = TRUE
-
- if(bounds[2] >= bounds[3])
- log_unit_test("[G.name]: Genetics - DNA_OFF_UPPERBOUND must be smaller than DNA_ON_LOWERBOUND, and never equal.")
- failed = TRUE
-
- if(bounds[3] > bounds[4])
- log_unit_test("[G.name]: Genetics - DNA_ON_LOWERBOUND must be smaller than DNA_ON_UPPERBOUND, and never equal.")
- failed = TRUE
-
- if(bounds[4] > 4095) // highest value a gene can be to turn on
- log_unit_test("[G.name]: Genetics - DNA_ON_UPPERBOUND, was larger than 4095.")
- failed = TRUE
-
- if(failed)
- fail("Invalid activity bounds for one or more traitgenes")
- else
- pass("All traitgenes have activity bounds, and activity bounds are legal.")
- return failed
diff --git a/code/unit_tests/loadout_tests.dm b/code/unit_tests/loadout_tests.dm
deleted file mode 100644
index f6bfd19d5a..0000000000
--- a/code/unit_tests/loadout_tests.dm
+++ /dev/null
@@ -1,22 +0,0 @@
-/datum/unit_test/loadout_test_shall_have_name_cost_path
- name = "LOADOUT: Entries shall have name, cost, and path definitions"
-
-/datum/unit_test/loadout_test_shall_have_name_cost_path/start_test()
- var/failed = 0
- for(var/datum/gear/G as anything in subtypesof(/datum/gear))
-
- if(!initial(G.display_name))
- log_unit_test("[G]: Loadout - Missing display name.")
- failed = 1
- else if(isnull(initial(G.cost)))
- log_unit_test("[G]: Loadout - Missing cost.")
- failed = 1
- else if(!initial(G.path))
- log_unit_test("[G]: Loadout - Missing path definition.")
- failed = 1
-
- if(failed)
- fail("One or more /datum/gear definitions had invalid display names, costs, or path definitions")
- else
- pass("All /datum/gear definitions had correct settings.")
- return 1
diff --git a/code/unit_tests/poster_tests.dm b/code/unit_tests/poster_tests.dm
deleted file mode 100644
index b7dc36cd23..0000000000
--- a/code/unit_tests/poster_tests.dm
+++ /dev/null
@@ -1,24 +0,0 @@
-/datum/unit_test/posters_shall_have_legal_states
- name = "POSTERS: All poster decls shall have valid icon and icon overrides"
-
-/datum/unit_test/posters_shall_have_legal_states/start_test()
- var/failed = 0
- var/list/all_posters = decls_repository.get_decls_of_type(/decl/poster)
- all_posters -= decls_repository.get_decl(/decl/poster/lewd) // Dumb exclusion for now. This really needs to become a valid poster instead of an illegaly made base type
-
- for(var/path in all_posters)
- var/decl/poster/D = all_posters[path]
- var/obj/structure/sign/poster/P = /obj/structure/sign/poster // The base poster shows ALL subtypes except /lewd, so all posters should function here regardless!
- var/icon/I = initial(P.icon)
- if(D.icon_override)
- I = D.icon_override
- if(!(D.icon_state in cached_icon_states(I)))
- failed += 1
- log_unit_test("[D.type]: Poster - missing icon_state \"[D.icon_state]\" in \"[I]\", as [D.icon_override ? "override" : "base"] dmi.")
-
- if(failed)
- fail("[failed] posters had missing icon_states or bad icon overrides.")
- else
- pass("All [all_posters.len] posters have their icon_states and overrides set correctly.")
-
- return TRUE
diff --git a/code/unit_tests/reagent_tests.dm b/code/unit_tests/reagent_tests.dm
deleted file mode 100644
index b7552c6a61..0000000000
--- a/code/unit_tests/reagent_tests.dm
+++ /dev/null
@@ -1,378 +0,0 @@
-/datum/unit_test/reagent_shall_have_unique_name_and_id
- name = "REAGENTS: Reagent IDs and names shall be unique"
-
-/datum/unit_test/reagent_shall_have_unique_name_and_id/start_test()
- var/failed = FALSE
- var/collection_name = list()
- var/collection_id = list()
-
- for(var/Rpath in subtypesof(/datum/reagent))
- var/datum/reagent/R = new Rpath()
-
- if(R.name == REAGENT_DEVELOPER_WARNING) // Ignore these types as they are meant to be overridden
- continue
-
- if(R.name == "")
- log_unit_test("[Rpath]: Reagents - reagent name blank.")
- failed = TRUE
-
- if(R.id == REAGENT_ID_DEVELOPER_WARNING)
- log_unit_test("[Rpath]: Reagents - reagent ID not set.")
- failed = TRUE
-
- if(R.id == "")
- log_unit_test("[Rpath]: Reagents - reagent ID blank.")
- failed = TRUE
-
- if(R.id != lowertext(R.id))
- log_unit_test("[Rpath]: Reagents - Reagent ID must be all lowercase.")
- failed = TRUE
-
- if(!(R.wiki_flag & WIKI_SPOILER)) // If wiki hidden then don't conflict test it against name, used for intentionally copied names like beer2's
- if(collection_name[R.name])
- log_unit_test("[Rpath]: Reagents - reagent name \"[R.name]\" is not unique, used first in [collection_name[R.name]].")
- failed = TRUE
- collection_name[R.name] = R.type
-
- if(collection_id[R.id])
- log_unit_test("[Rpath]: Reagents - reagent ID \"[R.id]\" is not unique, used first in [collection_id[R.id]].")
- failed = TRUE
- collection_id[R.id] = R.type
-
- if(!R.supply_conversion_value)
- log_unit_test("[Rpath]: Reagents - reagent ID \"[R.id]\" does not have supply_conversion_value set.")
- failed = TRUE
-
- if(!R.industrial_use || !R.industrial_use == "")
- log_unit_test("[Rpath]: Reagents - reagent ID \"[R.id]\" does not have industrial_use set.")
- failed = TRUE
-
- if(R.description == REAGENT_DESC_DEVELOPER_WARNING)
- log_unit_test("[Rpath]: Reagents - reagent description unset.")
- failed = TRUE
-
- qdel(R)
-
- if(failed)
- fail("One or more /datum/reagent subtypes had invalid definitions.")
- else
- pass("All /datum/reagent subtypes had correct settings.")
- return TRUE
-
-
-
-/datum/unit_test/chemical_reactions_shall_use_and_produce_valid_reagents
- name = "REAGENTS: Chemical Reactions shall use and produce valid reagents"
-
-/datum/unit_test/chemical_reactions_shall_use_and_produce_valid_reagents/start_test()
- var/failed = FALSE
- var/list/collection_id = list()
-
- var/list/all_reactions = decls_repository.get_decls_of_subtype(/decl/chemical_reaction)
- for(var/rtype in all_reactions)
- var/decl/chemical_reaction/CR = all_reactions[rtype]
- if(CR.name == REAGENT_DEVELOPER_WARNING) // Ignore these types as they are meant to be overridden
- continue
-
- if(!CR.name)
- log_unit_test("[CR.type]: Reagents - chemical reaction had null name.")
- failed = TRUE
-
- if(CR.name == "")
- log_unit_test("[CR.type]: Reagents - chemical reaction had blank name.")
- failed = TRUE
-
- if(!CR.id)
- log_unit_test("[CR.type]: Reagents - chemical reaction had invalid ID.")
- failed = TRUE
-
- if(CR.id != lowertext(CR.id))
- log_unit_test("[CR.type]: Reagents - chemical reaction ID must be all lowercase.")
- failed = TRUE
-
- if(CR.id in collection_id)
- log_unit_test("[CR.type]: Reagents - chemical reaction ID \"[CR.name]\" is not unique, used first in [collection_id[CR.id]].")
- failed = TRUE
- else
- collection_id[CR.id] = CR.type
-
- if(CR.result_amount < 0)
- log_unit_test("[CR.type]: Reagents - chemical reaction ID \"[CR.name]\" had less than 0 as as result_amount?")
- failed = TRUE
-
- if(CR.required_reagents && CR.required_reagents.len)
- for(var/RR in CR.required_reagents)
- if(!SSchemistry.chemical_reagents[RR])
- log_unit_test("[CR.type]: Reagents - chemical reaction had invalid required reagent ID \"[RR]\".")
- failed = TRUE
- if(CR.required_reagents[RR] <= 0)
- log_unit_test("[CR.type]: Reagents - chemical reaction had invalid required reagent amount or in invalid format \"[CR.required_reagents[RR]]\".")
- failed = TRUE
-
-
- if(CR.catalysts && CR.catalysts.len)
- for(var/RR in CR.catalysts)
- if(!SSchemistry.chemical_reagents[RR])
- log_unit_test("[CR.type]: Reagents - chemical reaction had invalid required reagent ID \"[RR]\".")
- failed = TRUE
- if(CR.catalysts[RR] <= 0)
- log_unit_test("[CR.type]: Reagents - chemical reaction had invalid catalysts amount or in invalid format \"[CR.catalysts[RR]]\".")
- failed = TRUE
-
- if(CR.inhibitors && CR.inhibitors.len)
- for(var/RR in CR.inhibitors)
- if(!SSchemistry.chemical_reagents[RR])
- log_unit_test("[CR.type]: Reagents - chemical reaction had invalid required reagent ID \"[RR]\".")
- failed = TRUE
- if(CR.inhibitors[RR] <= 0)
- log_unit_test("[CR.type]: Reagents - chemical reaction had invalid inhibitors amount or in invalid format \"[CR.inhibitors[RR]]\".")
- failed = TRUE
-
- if(CR.result)
- if(!SSchemistry.chemical_reagents[CR.result])
- log_unit_test("[CR.type]: Reagents - chemical reaction had invalid result reagent ID \"[CR.result]\".")
- failed = TRUE
-
- if(failed)
- fail("One or more /decl/chemical_reaction subtypes had invalid results or components.")
- else
- pass("All /decl/chemical_reaction subtypes had correct settings.")
- return TRUE
-
-
-
-/datum/unit_test/prefilled_reagent_containers_shall_have_valid_reagents
- name = "REAGENTS: Prefilled reagent containers shall have valid reagents"
-
-/datum/unit_test/prefilled_reagent_containers_shall_have_valid_reagents/start_test()
- var/failed = FALSE
-
- var/obj/container = new /obj
- for(var/RC in subtypesof(/obj/item/reagent_containers/glass))
- var/obj/item/reagent_containers/glass/R = new RC(container)
-
- if(R.prefill && R.prefill.len)
- for(var/ID in R.prefill)
- if(!SSchemistry.chemical_reagents[ID])
- log_unit_test("[RC]: Reagents - reagent prefill had invalid reagent ID \"[ID]\".")
- failed = TRUE
-
- qdel(R)
-
- for(var/DC in subtypesof(/obj/item/reagent_containers/chem_disp_cartridge))
- var/obj/item/reagent_containers/chem_disp_cartridge/D = new DC(container)
-
- if(D.spawn_reagent)
- if(!SSchemistry.chemical_reagents[D.spawn_reagent])
- log_unit_test("[DC]: Reagents - chemical dispenser cartridge had invalid reagent ID \"[D.spawn_reagent]\".")
- failed = TRUE
-
- qdel(D)
-
- qdel(container)
-
- if(failed)
- fail("One or more /obj/item/reagent_containers had an invalid prefill reagent.")
- else
- pass("All /obj/item/reagent_containers had valid prefill reagents.")
- return TRUE
-
-
-/datum/unit_test/chemical_reactions_shall_not_conflict
- name = "REAGENTS: Chemical Reactions shall not conflict"
- var/obj/fake_beaker = null
- var/list/result_reactions = list()
-
-/datum/unit_test/chemical_reactions_shall_not_conflict/start_test()
- var/failed = FALSE
-
- #ifdef UNIT_TEST
- var/list/all_reactions = decls_repository.get_decls_of_subtype(/decl/chemical_reaction)
- for(var/rtype in all_reactions)
- var/decl/chemical_reaction/CR = all_reactions[rtype]
-
- if(CR.name == REAGENT_DEVELOPER_WARNING) // Ignore these types as they are meant to be overridden
- continue
- if(!CR.name || CR.name == "" || !CR.id || CR.id == "")
- continue
- if(CR.result_amount <= 0) //Makes nothing anyway, or maybe an effect/explosion!
- continue
- if(!CR.result) // Cannot check for this
- continue
-
- if(istype(CR, /decl/chemical_reaction/instant/slime))
- // slime time
- var/decl/chemical_reaction/instant/slime/SR = CR
- if(!SR.required)
- continue
- var/obj/item/slime_extract/E = new SR.required()
- qdel_swap(fake_beaker, E)
- fake_beaker.reagents.maximum_volume = 5000
- else if(istype(CR, /decl/chemical_reaction/distilling))
- // distilling
- var/obj/distilling_tester/D = new()
- qdel_swap(fake_beaker, D)
- fake_beaker.reagents.maximum_volume = 5000
- else
- // regular beaker
- qdel_swap(fake_beaker, new /obj/item/reagent_containers/glass/beaker())
- fake_beaker.reagents.maximum_volume = 5000
-
- // Perform test! If it fails once, it will perform a deeper check trying to use the inhibitors of anything in the beaker
- RegisterSignal(fake_beaker.reagents, COMSIG_UNITTEST_DATA, PROC_REF(get_signal_data))
- if(perform_reaction(CR))
- // Check if we failed the test with inhibitors in use, if so we absolutely couldn't make it...
- // Uncomment the UNIT_TEST section in code\modules\reagents\reactions\_reactions.dm if you require more info
- log_unit_test("[CR.type]: Reagents - chemical reaction did not produce \"[CR.result]\". CONTAINS: \"[fake_beaker.reagents.get_reagents()]\"")
- failed = TRUE
- UnregisterSignal(fake_beaker.reagents, COMSIG_UNITTEST_DATA)
- qdel_null(fake_beaker)
- #endif
-
- if(failed)
- fail("One or more /decl/chemical_reaction subtypes conflict with another reaction.")
- else
- pass("All /decl/chemical_reaction subtypes had no conflicts.")
- return TRUE
-
-/datum/unit_test/chemical_reactions_shall_not_conflict/proc/perform_reaction(var/decl/chemical_reaction/CR, var/list/inhib = list())
- var/scale = 1
- if(CR.result_amount < 1)
- scale = 1 / CR.result_amount // Create at least 1 unit
-
- // Weird loop here, but this is used to test both instant and distilling reactions
- // Instants will meet the while() condition on the first loop and go to the next stuff
- // but distilling will repeat over and over until the temperature test is finished!
- var/temp_test = 0
- do
- // clear for inhibitor searches
- fake_beaker.reagents.clear_reagents()
- result_reactions.Cut()
-
- if(inhib.len) // taken from argument and not reaction! Put in FIRST!
- for(var/RR in inhib)
- fake_beaker.reagents.add_reagent(RR, inhib[RR] * scale)
- if(CR.catalysts) // Required for reaction
- for(var/RR in CR.catalysts)
- fake_beaker.reagents.add_reagent(RR, CR.catalysts[RR] * scale)
- if(CR.required_reagents)
- for(var/RR in CR.required_reagents)
- fake_beaker.reagents.add_reagent(RR, CR.required_reagents[RR] * scale)
-
- if(!istype(CR, /decl/chemical_reaction/distilling))
- break // Skip the next section if we're not distilling
-
- // Check distillation at 10 points along its temperature range!
- // This is so multiple reactions with the same requirements, but different temps, can be tested.
- temp_test += 0.1
- var/obj/distilling_tester/DD = fake_beaker
- DD.test_distilling(CR,temp_test)
-
- if(fake_beaker.reagents.has_reagent(CR.result))
- return FALSE // Distilling success
-
- while(temp_test > 1)
-
- // Check beaker to see if we reached our goal!
- if(fake_beaker.reagents.has_reagent(CR.result))
- return FALSE // INSTANT SUCCESS!
-
- if(inhib.len)
- // We've checked with inhibitors, so we're already in inhibitor checking phase.
- // So we've absolutely failed this time. There is no way to make this...
- return TRUE
-
- if(!result_reactions.len)
- // Nothing to check for inhibitors...
- for(var/decl/chemical_reaction/test_react in result_reactions)
- log_unit_test("[CR.type]: Reagents - Used [test_react] but failed.")
- return TRUE
-
- // Otherwise we check the resulting reagents and use their inhibitor this time!
- for(var/decl/chemical_reaction/test_react in result_reactions)
- if(!test_react)
- continue
- if(!test_react.inhibitors.len)
- continue
- // Test one by one
- for(var/each in test_react.inhibitors)
- if(!perform_reaction(CR, list("[each]" = test_react.inhibitors["[each]"])))
- return FALSE // SUCCESS using an inhibitor!
- // Test all at once
- if(!perform_reaction(CR, test_react.inhibitors))
- return FALSE // SUCCESS using all inhibitors!
-
- // No inhibiting reagent worked...
- for(var/decl/chemical_reaction/test_react in result_reactions)
- log_unit_test("[CR.type]: Reagents - Used [test_react] but failed.")
- return TRUE
-
-/datum/unit_test/chemical_reactions_shall_not_conflict/get_signal_data(atom/source, list/data = list())
- result_reactions.Add(data[1]) // Append the reactions that happened, then use that to check their inhibitors
-
-
-/datum/unit_test/chemical_grinding_must_produce_valid_results
- name = "REAGENTS: Chemical Grinding Must Have Valid Results"
-
-/datum/unit_test/chemical_grinding_must_produce_valid_results/start_test()
- var/failed = FALSE
-
- for(var/grind in GLOB.sheet_reagents + GLOB.ore_reagents)
- var/list/results = GLOB.sheet_reagents[grind]
- if(!results)
- results = GLOB.ore_reagents[grind]
- if(!results || !islist(results))
- log_unit_test("[grind]: Reagents - Grinding result had invalid list.")
- failed = TRUE
- continue
- if(!results.len)
- log_unit_test("[grind]: Reagents - Grinding result had empty.")
- failed = TRUE
- continue
- for(var/reg_id in results)
- if(!SSchemistry.chemical_reagents[reg_id])
- log_unit_test("[grind]: Reagents - Grinding result had invalid reagent id \"[reg_id]\".")
- failed = TRUE
-
- if(failed)
- fail("One or more grindable sheet or ore entries had invalid reagents or lists.")
- else
- pass("All grindable sheet or ore entries had valid lists and reagents.")
- return TRUE
-
-
-
-// Used to test distillations without hacking the other machinery's code up
-/obj/distilling_tester
- icon = 'icons/obj/weapons.dmi'
- icon_state = "cartridge"
- var/datum/gas_mixture/GM = new()
- var/current_temp = 0
-
-/obj/distilling_tester/Initialize(mapload)
- create_reagents(5000,/datum/reagents/distilling)
- . = ..()
-
-/obj/distilling_tester/return_air()
- return GM
-
-/obj/distilling_tester/proc/test_distilling(var/decl/chemical_reaction/distilling/D, var/temp_prog)
- qdel_swap(GM,new())
- if(D.require_xgm_gas)
- GM.gas[D.require_xgm_gas] = 100
- else
- if(D.rejects_xgm_gas == GAS_N2)
- GM.gas[GAS_O2] = 100
- else
- GM.gas[GAS_N2] = 100
- if(D.minimum_xgm_pressure)
- GM.temperature = (D.minimum_xgm_pressure * CELL_VOLUME) / (GM.gas[D.require_xgm_gas] * R_IDEAL_GAS_EQUATION)
-
- // Try this 10 times, We need to know if something is blocking at multiple temps.
- // If it passes unit test, it might still be awful to make though, gotta find the right gas mix!
- current_temp = LERP( D.temp_range[1], D.temp_range[2], temp_prog)
- reagents.handle_reactions()
-
-/obj/distilling_tester/Destroy(force, ...)
- qdel_null(GM)
- . = ..()
diff --git a/code/unit_tests/recipe_tests.dm b/code/unit_tests/recipe_tests.dm
deleted file mode 100644
index a2d7594d0d..0000000000
--- a/code/unit_tests/recipe_tests.dm
+++ /dev/null
@@ -1,29 +0,0 @@
-/datum/unit_test/recipe_test_shall_have_valid_result_and_quantity
- name = "RECIPES: Recipes shall have valid result and result_quantity definitions"
-
-/datum/unit_test/recipe_test_shall_have_valid_result_and_quantity/start_test()
- var/failed = FALSE
- for(var/datum/recipe/R in subtypesof(/datum/recipe))
- var/our_result = initial(R.result)
- var/our_amount = initial(R.result_quantity)
- if(!our_result)
- log_unit_test("[R]: Recipes - Missing result.")
- failed = TRUE
- else if(!ispath(our_result, /atom/movable))
- log_unit_test("[R]: Recipes - Improper result; [our_result] is not an obj or mob.")
- failed = TRUE
- if(isnull(our_amount))
- log_unit_test("[R]: Recipes - result_quantity must be set.")
- failed = TRUE
- if(our_amount <= 0)
- log_unit_test("[R]: Recipes - result_quantity must be greater than zero.")
- failed = TRUE
- else if(!ISINTEGER(our_amount))
- log_unit_test("[R]: Recipes - result_quantity must be an integer.")
- failed = TRUE
-
- if(failed)
- fail("One or more /datum/recipe subtypes had invalid results or result_quantity definitions.")
- else
- pass("All /datum/recipe subtypes had correct settings.")
- return TRUE
diff --git a/code/unit_tests/recycler_vendor_tests.dm b/code/unit_tests/recycler_vendor_tests.dm
deleted file mode 100644
index 2a3ace9144..0000000000
--- a/code/unit_tests/recycler_vendor_tests.dm
+++ /dev/null
@@ -1,30 +0,0 @@
-/datum/unit_test/recycler_vendor_entry_invalid
- name = "RECYCLER: Recycler Vendor Entry shall have valid definitions"
-
-/datum/unit_test/recycler_vendor_entry_invalid/start_test()
- var/failed = FALSE
-
-
-
- for(var/datum/maint_recycler_vendor_entry/R in subtypesof(/datum/maint_recycler_vendor_entry))
- var/item_to_spawn = initial(R.object_type_to_spawn)
- var/item_cost = initial(R.item_cost)
- var/global_item_cap = initial(R.per_round_cap)
- var/individual_item_cap = initial(R.per_person_cap)
- var/is_scam = initial(R.is_scam)
- if(!item_to_spawn && !is_scam)
- log_unit_test("[R] : Vendor Entry - Missing Object Type on non-scam entry")
- failed = TRUE
- if(item_cost < 0)
- log_unit_test("[R] : Vendor Entry - Negative Cost")
- failed = TRUE
- if((item_cost == 0) && ((global_item_cap < 0)&&(individual_item_cap < 0))) //nobody SHOULD set the cost to 0 but it's possible i guess. maybe for future scams
- log_unit_test("[R] : Vendor Entry - Infinite Item Spawning due to no individual or global item cap")
- failed = TRUE
-
- if(failed)
- fail("One or more /datum/maint_recycler_vendor_entry had invalid results or definitions.")
- else
- pass("All /datum/maint_recycler_vendor_entry subtypes had correct settings.")
-
- return TRUE
diff --git a/code/unit_tests/robot_tests.dm b/code/unit_tests/robot_tests.dm
deleted file mode 100644
index fb8eb92975..0000000000
--- a/code/unit_tests/robot_tests.dm
+++ /dev/null
@@ -1,224 +0,0 @@
-/datum/unit_test/all_robot_sprites_must_be_valid
- name = "ROBOTS: Robot sprites must be valid"
- var/signal_failed = FALSE
-
-/datum/unit_test/all_robot_sprites_must_be_valid/start_test()
- var/failed = 0
-
- for(var/sprite in subtypesof(/datum/robot_sprite))
- var/datum/robot_sprite/RS = new sprite()
- if(!RS.name) // Parent type, ignore me
- continue
-
- if(!RS.sprite_icon)
- log_unit_test("[RS.type]: Robots - Robot sprite \"[RS.name]\", missing sprite_icon.")
- failed = TRUE
- continue
-
- var/list/checks = list(
- "[ROBOT_HAS_SPEED_SPRITE]" = "-roll",
- "[ROBOT_HAS_SHIELD_SPRITE]" = "-shield",
- "[ROBOT_HAS_SHIELD_SPEED_SPRITE]" = "-speed_shield",
- "[ROBOT_HAS_MELEE_SPRITE]" = "-melee",
- "[ROBOT_HAS_DAGGER_SPRITE]" = "-dagger",
- "[ROBOT_HAS_BLADE_SPRITE]" = "-blade",
- "[ROBOT_HAS_GUN_SPRITE]" = "-gun",
- "[ROBOT_HAS_LASER_SPRITE]" = "-laser",
- "[ROBOT_HAS_TASER_SPRITE]" = "-taser",
- "[ROBOT_HAS_DISABLER_SPRITE]" = "-disabler"
- )
- for(var/C in checks)
- if(RS.sprite_flag_check(text2num(C)))
- if(check_state(RS,checks[C]))
- failed = TRUE
- // eyes, lights, markings
- if(RS.has_eye_sprites)
- if(check_state(RS,"-eyes"))
- failed = TRUE
- if(RS.has_eye_light_sprites)
- if(check_state(RS,"-lights"))
- failed = TRUE
- if(LAZYLEN(RS.sprite_decals))
- for(var/decal in RS.sprite_decals)
- if(check_state(RS,"-[decal]"))
- failed = TRUE
- if(LAZYLEN(RS.sprite_animations))
- for(var/animation in RS.sprite_animations)
- if(check_state(RS,"-[animation]"))
- failed = TRUE
- // Control panel
- if(RS.has_custom_open_sprites)
- if(check_state(RS,"-openpanel_nc"))
- failed = TRUE
- if(check_state(RS,"-openpanel_c"))
- failed = TRUE
- if(check_state(RS,"-openpanel_w"))
- failed = TRUE
- // Glow State
- if(RS.has_glow_sprites)
- if(check_state(RS,"-glow"))
- failed = TRUE
- // Bellies
- if(RS.has_vore_belly_sprites && !RS.belly_capacity_list)
- if(RS.has_sleeper_light_indicator)
- // belly r/g light
- if(check_state(RS,"-sleeper-r"))
- failed = TRUE
- if(check_state(RS,"-sleeper-g"))
- failed = TRUE
- if(RS.has_vore_belly_resting_sprites)
- for(var/rest_style in RS.rest_sprite_options)
- rest_style = lowertext(rest_style)
- if(rest_style == "default")
- rest_style = "rest"
- if(check_state(RS,"-sleeper-r-[rest_style]"))
- failed = TRUE
- if(check_state(RS,"-sleeper-g-[rest_style]"))
- failed = TRUE
- // struggling
- if(RS.has_vore_struggle_sprite)
- if(check_state(RS,"-sleeper-r-struggle"))
- failed = TRUE
- if(check_state(RS,"-sleeper-g-struggle"))
- failed = TRUE
- if(RS.has_vore_belly_resting_sprites)
- for(var/rest_style in RS.rest_sprite_options)
- rest_style = lowertext(rest_style)
- if(rest_style == "default")
- rest_style = "rest"
- if(check_state(RS,"-sleeper-r-[rest_style]-struggle"))
- failed = TRUE
- if(check_state(RS,"-sleeper-g-[rest_style]-struggle"))
- failed = TRUE
- else
- // belly
- if(check_state(RS,"-sleeper"))
- failed = TRUE
- if(RS.has_vore_belly_resting_sprites)
- for(var/rest_style in RS.rest_sprite_options)
- rest_style = lowertext(rest_style)
- if(rest_style == "default")
- rest_style = "rest"
- if(check_state(RS,"-sleeper-[rest_style]"))
- failed = TRUE
- // struggling
- if(RS.has_vore_struggle_sprite)
- if(check_state(RS,"-sleeper-struggle"))
- failed = TRUE
- if(RS.has_vore_belly_resting_sprites)
- for(var/rest_style in RS.rest_sprite_options)
- rest_style = lowertext(rest_style)
- if(rest_style == "default")
- rest_style = "rest"
- if(check_state(RS,"-sleeper-[rest_style]-struggle"))
- failed = TRUE
- else if (RS.belly_capacity_list)
- for(var/belly in RS.belly_capacity_list)
- for(var/num = 1 to RS.belly_capacity_list[belly])
- // big belly
- if(check_state(RS,"-[belly]-[num]"))
- failed = TRUE
- if(RS.has_vore_belly_resting_sprites)
- for(var/rest_style in RS.rest_sprite_options)
- rest_style = lowertext(rest_style)
- if(rest_style == "default")
- rest_style = "rest"
- if(check_state(RS,"-[belly]-[num]-[rest_style]"))
- failed = TRUE
- // struggling
- if(RS.has_vore_struggle_sprite)
- if(check_state(RS,"-[belly]-[num]-struggle"))
- failed = TRUE
- if(RS.has_vore_belly_resting_sprites)
- for(var/rest_style in RS.rest_sprite_options)
- rest_style = lowertext(rest_style)
- if(rest_style == "default")
- rest_style = "rest"
- if(check_state(RS,"-[belly]-[num]-[rest_style]-struggle"))
- failed = TRUE
- if(RS.belly_light_list)
- for(var/belly in RS.belly_light_list)
- for(var/num = 1 to RS.belly_light_list[belly])
- // multi belly r/g light
- if(check_state(RS,"-[belly]-[num]-r"))
- failed = TRUE
- if(check_state(RS,"-[belly]-[num]-g"))
- failed = TRUE
- if(RS.has_vore_belly_resting_sprites)
- for(var/rest_style in RS.rest_sprite_options)
- rest_style = lowertext(rest_style)
- if(rest_style == "default")
- rest_style = "rest"
- if(check_state(RS,"-[belly]-[num]-r-[rest_style]"))
- failed = TRUE
- if(check_state(RS,"-[belly]-[num]-g-[rest_style]"))
- failed = TRUE
- // struggling
- if(RS.has_vore_struggle_sprite)
- if(check_state(RS,"-[belly]-[num]-r-struggle"))
- failed = TRUE
- if(check_state(RS,"-[belly]-[num]-g-struggle"))
- failed = TRUE
- if(RS.has_vore_belly_resting_sprites)
- for(var/rest_style in RS.rest_sprite_options)
- rest_style = lowertext(rest_style)
- if(rest_style == "default")
- rest_style = "rest"
- if(check_state(RS,"-[belly]-[num]-r-[rest_style]-struggle"))
- failed = TRUE
- if(check_state(RS,"-[belly]-[num]-g-[rest_style]-struggle"))
- failed = TRUE
- // reseting
- for(var/rest_style in RS.rest_sprite_options)
- rest_style = lowertext(rest_style)
- if(rest_style == "default")
- rest_style = "rest"
- if(check_state(RS,"-[rest_style]"))
- failed = TRUE
- if(RS.has_glow_sprites)
- if(check_state(RS,"-[rest_style]-glow"))
- failed = TRUE
- if(RS.has_rest_lights_sprites)
- if(check_state(RS,"-[rest_style]-lights"))
- failed = TRUE
- if(RS.has_rest_eyes_sprites)
- if(check_state(RS,"-[rest_style]-eyes"))
- failed = TRUE
- // death
- if(RS.has_dead_sprite)
- if(check_state(RS,"-wreck"))
- failed = TRUE
- if(RS.has_dead_sprite_overlay) // Only one per dmi
- if(!("wreck-overlay" in cached_icon_states(RS.sprite_icon)))
- log_unit_test("[RS.type]: Robots - Robot sprite \"[RS.name]\", missing icon_state wreck-overlay, in dmi \"[RS.sprite_icon]\".")
- failed = TRUE
- // offset
- var/icon/I = new(RS.sprite_icon)
- if(RS.icon_x != I.Width())
- log_unit_test("[RS.type]: Robots - Robot sprite \"[RS.name]\", icon_x \"[RS.icon_x]\" did not match dmi configured width \"[I.Width()]\"")
- failed = TRUE
- if(RS.icon_y != I.Height())
- log_unit_test("[RS.type]: Robots - Robot sprite \"[RS.name]\", icon_y \"[RS.icon_y]\" did not match dmi configured height \"[I.Height()]\"")
- failed = TRUE
- if(RS.icon_y != RS.vis_height)
- log_unit_test("[RS.type]: Robots - Robot sprite \"[RS.name]\", vis_height \"[RS.vis_height]\" did not match icon_y \"[RS.icon_y]\"")
- failed = TRUE
- var/legal_offset = (I.Width() - world.icon_size) / 2
- if(RS.pixel_x != -legal_offset)
- log_unit_test("[RS.type]: Robots - Robot sprite \"[RS.name]\", pixel_x \"[RS.pixel_x]\" did not have correct offset, should be \"[-legal_offset]\"")
- failed = TRUE
- qdel(I)
- qdel(RS)
-
- if(failed)
- fail("One or more /datum/robot_sprite items had invalid flags or icons")
- else
- pass("All /datum/robot_sprite are valid.")
- return 1
-
-/datum/unit_test/all_robot_sprites_must_be_valid/proc/check_state(var/datum/robot_sprite/RS,var/append)
- var/check_state = "[RS.sprite_icon_state][append]"
- if(!(check_state in cached_icon_states(RS.sprite_icon)))
- log_unit_test("[RS.type]: Robots - Robot sprite \"[RS.name]\", enabled but missing icon_state \"[check_state]\", in dmi \"[RS.sprite_icon]\".")
- return TRUE
- return FALSE
diff --git a/code/unit_tests/sqlite_tests.dm b/code/unit_tests/sqlite_tests.dm
deleted file mode 100644
index e8c19c1a7f..0000000000
--- a/code/unit_tests/sqlite_tests.dm
+++ /dev/null
@@ -1,69 +0,0 @@
-/datum/unit_test/sqlite
- name = "SQLite template" // Template has to be in the name or this test will be ran, and fail.
- var/database/stub_sqlite_db = null
-
-/datum/unit_test/sqlite/proc/setup_stub_db()
- fdel("data/sqlite/testing_[name].db") // In case any remain from a previous local test, so we can have a clean new database.
- stub_sqlite_db = new("data/sqlite/testing_[name].db") // Unfortunately, byond doesn't like having working sqlite stuff w/o a file existing.
- SSsqlite.init_schema(stub_sqlite_db)
-
-// Feedback table tests.
-/datum/unit_test/sqlite/feedback/insert
- name = "SQLITE FEEDBACK: Insert and Retrieve Data"
-
-/datum/unit_test/sqlite/feedback/insert/start_test()
- // Arrange.
- setup_stub_db()
- var/test_author = "alice"
- var/test_topic = "Test"
- var/test_content = "Bob is lame."
-
- // Act.
- SSsqlite.insert_feedback(author = test_author, topic = test_topic, content = test_content, sqlite_object = stub_sqlite_db)
- var/database/query/Q = new("SELECT * FROM [SQLITE_TABLE_FEEDBACK]")
- Q.Execute(stub_sqlite_db)
- SSsqlite.sqlite_check_for_errors(Q, "Sqlite Insert Unit Test")
- Q.NextRow()
-
- // Assert.
- var/list/row_data = Q.GetRowData()
- if(row_data[SQLITE_FEEDBACK_COLUMN_AUTHOR] == test_author && row_data[SQLITE_FEEDBACK_COLUMN_TOPIC] == test_topic && row_data[SQLITE_FEEDBACK_COLUMN_CONTENT] == test_content)
- pass("No issues found.")
- else
- fail("Data insert and loading failed to have matching information.")
- return TRUE
-
-
-/datum/unit_test/sqlite/feedback/cooldown
- name = "SQLITE FEEDBACK: Cooldown"
-
-/datum/unit_test/sqlite/feedback/cooldown/start_test()
- // Arrange.
- setup_stub_db()
- var/days_to_wait = 1
- var/issues = 0
-
- // Act.
- SSsqlite.insert_feedback(author = "Alice", topic = "Testing", content = "This is a test.", sqlite_object = stub_sqlite_db)
-
- var/alice_cooldown_block = SSsqlite.get_feedback_cooldown("Alice", days_to_wait, stub_sqlite_db)
- var/bob_cooldown = SSsqlite.get_feedback_cooldown("Bob", days_to_wait, stub_sqlite_db)
- days_to_wait = 0
- var/alice_cooldown_allow = SSsqlite.get_feedback_cooldown("Alice", days_to_wait, stub_sqlite_db)
-
- // Assert.
- if(alice_cooldown_block <= 0)
- issues++
- log_unit_test("User 'Alice' did not receive a cooldown, when they were supposed to.")
- if(bob_cooldown > 0)
- issues++
- log_unit_test("User 'Bob' did receive a cooldown, when they did not do anything.")
- if(alice_cooldown_allow > 0)
- issues++
- log_unit_test("User 'Alice' did receive a cooldown, when no cooldown is supposed to be enforced.")
-
- if(issues)
- fail("[issues] issues were found.")
- else
- pass("No issues found.")
- return TRUE
diff --git a/code/unit_tests/subsystem_tests.dm b/code/unit_tests/subsystem_tests.dm
deleted file mode 100644
index 4ad9a1989b..0000000000
--- a/code/unit_tests/subsystem_tests.dm
+++ /dev/null
@@ -1,46 +0,0 @@
-/datum/unit_test/subsystem_shall_be_initialized
- name = "SUBSYSTEM - INIT: Subsystems shall be initalized"
-
-/datum/unit_test/subsystem_shall_be_initialized/start_test()
- var/list/bad_subsystems = list()
- for(var/datum/controller/subsystem/SS in Master.subsystems)
- if (SS.flags & SS_NO_INIT)
- continue
- if(!SS.subsystem_initialized)
- bad_subsystems += SS.type
-
- if(bad_subsystems.len)
- fail("Found improperly initialized subsystems: [english_list(bad_subsystems)]")
- else
- pass("All susbsystems have initialized properly")
-
- return 1
-
-/* Uncomment when all atoms init properly
-/datum/unit_test/subsystem_atom_shall_have_no_bad_init_calls
- name = "SUBSYSTEM - ATOMS: Shall have no bad init calls"
-
-/datum/unit_test/subsystem_atom_shall_have_no_bad_init_calls/start_test()
- if(SSatoms.BadInitializeCalls.len)
- log_bad(jointext(SSatoms.InitLog(), null))
- fail("[SSatoms] had bad initialization calls.")
- else
- pass("[SSatoms] had no bad initialization calls.")
- return 1
-
-/datum/unit_test/all_atoms_shall_be_initialized
- name = "SUBSYSTEM - ATOMS: All atoms shall be initialized."
-
-/datum/unit_test/all_atoms_shall_be_initialized/start_test()
- set background = TRUE // avoid infinite loop warning; SS will still wait for us.
- var/fail = FALSE
- for(var/atom/atom in world)
- if(!(atom.initialized))
- log_bad("Uninitialized atom: [atom.log_info_line()]")
- fail = TRUE
- if(fail)
- fail("There were uninitialized atoms.")
- else
- pass("All atoms were initialized")
- return 1
-*/
diff --git a/code/unit_tests/trait_tests.dm b/code/unit_tests/trait_tests.dm
deleted file mode 100644
index 411e61a4f5..0000000000
--- a/code/unit_tests/trait_tests.dm
+++ /dev/null
@@ -1,58 +0,0 @@
-/datum/unit_test/all_traits_unique_names
- name = "TRAITS: All traits shall have unique names"
-
-/datum/unit_test/all_traits_unique_names/start_test()
- var/failed = FALSE
-
- var/list/used_named = list()
- for(var/traitpath in GLOB.all_traits)
- var/datum/trait/T = GLOB.all_traits[traitpath]
- if(T.name in used_named)
- log_unit_test("[T.type]: Trait - The name \"[T.name]\" is already in use.")
- failed = TRUE
- else
- used_named.Add(T.name)
-
- if(failed)
- fail("One or more traits shared a name.")
- else
- pass("All [GLOB.all_traits.len] have unique names.")
- return failed
-
-
-/datum/unit_test/autohiss_shall_be_exclusive
- name = "TRAITS: Autohiss traits shall be exclusive"
-
-/datum/unit_test/autohiss_shall_be_exclusive/start_test()
- var/failed = FALSE
-
- var/list/hiss_list = list()
- for(var/traitpath in GLOB.all_traits)
- var/datum/trait/T = GLOB.all_traits[traitpath]
- if(!T.var_changes)
- continue
- if(!islist(T.var_changes["autohiss_basic_map"]))
- continue
- hiss_list += T
-
- for(var/datum/trait/T in hiss_list)
- if(T.type in T.excludes)
- log_unit_test("[T.type]: Trait - Autohiss excludes itself.")
- failed = TRUE
-
- if(!T.excludes)
- log_unit_test("[T.type]: Trait - Autohiss missing exclusion list.")
- failed = TRUE
- continue
-
- var/list/exempt_list = hiss_list.Copy() - T // MUST exclude all others except itself
- for(var/datum/trait/EX in exempt_list)
- if(!(EX.type in T.excludes))
- log_unit_test("[T.type]: Trait - Autohiss missing exclusion for [EX].")
- failed = TRUE
-
- if(failed)
- fail("One or more autohiss traits allow another autohiss to be chosen with it.")
- else
- pass("All [hiss_list.len] autohiss traits are properly exclusive.")
- return failed
diff --git a/code/unit_tests/unit_test.dm b/code/unit_tests/unit_test.dm
deleted file mode 100644
index 416d505033..0000000000
--- a/code/unit_tests/unit_test.dm
+++ /dev/null
@@ -1,114 +0,0 @@
-var/all_unit_tests_passed = 1
-var/failed_unit_tests = 0
-var/total_unit_tests = 0
-
-/datum/unit_test
- var/name = "template"
- var/disabled = ""
- var/async = 0
- var/reported = 0
-
-/datum/unit_test/proc/fail(var/message)
- all_unit_tests_passed = 0
- failed_unit_tests++
- reported = 1
- log_unit_test("[ASCII_RED]!! FAILURE !! \[[name]\]: [message][ASCII_RESET]")
-
-/datum/unit_test/proc/pass(var/message)
- reported = 1
- log_unit_test("[ASCII_GREEN]** SUCCESS ** \[[name]\]: [message][ASCII_RESET]")
-
-/datum/unit_test/proc/start_test()
- fail("No test proc.")
-
-/datum/unit_test/proc/check_result()
- fail("No check results proc.")
- return 1
-
-
-/proc/initialize_unit_tests()
- log_unit_test("Initializing Unit Testing")
-
- if(!ticker)
- stack_trace("No Ticker")
- world.Del()
-
- var/said_msg = 0
- while(!Master.current_runlevel) // Make sure the initial startup is complete.
- if(!said_msg)
- said_msg = 1
- log_unit_test("Waiting for subystems initilization to finish.")
- stoplag(10)
-
- world.save_mode("extended")
-
- sleep(1)
-
- ticker.current_state = GAME_STATE_SETTING_UP
- Master.SetRunLevel(RUNLEVEL_SETUP)
-
- log_unit_test("Round has been started. Waiting 10 seconds to start tests.")
- sleep(100)
-
- var/list/test_datums = typesof(/datum/unit_test)
-
- var/list/async_test = list()
- var/list/started_tests = list()
-
- log_unit_test("Testing Started.")
-
- for(var/test in test_datums)
- var/datum/unit_test/d = new test()
-
- if(d.disabled)
- d.pass("[ASCII_RED]Check Disabled: [d.disabled][ASCII_RESET]")
- continue
-
- if(findtext(d.name, "template"))
- continue
-
- if(isnull(d.start_test())) // Start the test.
- d.fail("Test Runtimed")
-
- if(d.async) // If it's async then we'll need to check back on it later.
- async_test.Add(d)
- total_unit_tests++
-
- //
- // Check the async tests to see if they are finished.
- //
-
- while(async_test.len)
- for(var/datum/unit_test/test in async_test)
- if(test.check_result())
- async_test.Remove(test)
- sleep(1)
-
- //
- // Make sure all Unit Tests reported a result
- //
-
- for(var/datum/unit_test/test in started_tests)
- if(!test.reported)
- test.fail("Test failed to report a result.")
-
- if(all_unit_tests_passed)
- log_unit_test("[ASCII_GREEN]*** All Unit Tests Passed \[[total_unit_tests]\] ***[ASCII_RESET]")
- else
- log_unit_test("[ASCII_RED]!!! \[[failed_unit_tests]\\[total_unit_tests]\] Unit Tests Failed !!![ASCII_RESET]")
- log_unit_test("Caught [GLOB.total_runtimes] Runtime\s.")
- world.Del()
-
-/datum/unit_test/proc/get_standard_turf()
- return locate(20,20,1)
-
-/datum/unit_test/proc/log_bad(var/message)
- log_unit_test("[ASCII_RED]\[[name]\]: [message][ASCII_RESET]")
-
-/datum/unit_test/proc/log_debug(var/message)
- log_unit_test("[ASCII_YELLOW]--- DEBUG --- \[[name]\]: [message][ASCII_RESET]")
-
-// This signal should never be possible to call if the game is not compiled for unit tests
-/datum/unit_test/proc/get_signal_data(atom/source, list/data = list())
- SIGNAL_HANDLER
- return
diff --git a/code/unit_tests/unit_test_vr.dm b/code/unit_tests/unit_test_vr.dm
deleted file mode 100644
index ae3d871b15..0000000000
--- a/code/unit_tests/unit_test_vr.dm
+++ /dev/null
@@ -1,27 +0,0 @@
-//
-// VOREStation extensions to unit test framework.
-//
-
-/datum/unit_test
- var/static/default_mobloc = null
-
-/datum/unit_test/proc/create_test_mob(var/turf/mobloc = null, var/mobtype = /mob/living/carbon/human, var/with_mind = FALSE)
- if(isnull(mobloc))
- if(!default_mobloc)
- for(var/turf/simulated/floor/tiled/T in world)
- var/pressure = T.zone.air.return_pressure()
- if(90 < pressure && pressure < 120) // Find a turf between 90 and 120
- default_mobloc = T
- break
- mobloc = default_mobloc
- if(!mobloc)
- fail("Unable to find a location to create test mob")
- return 0
-
- var/mob/living/carbon/human/H = new mobtype(mobloc)
- H.init_vore()
-
- if(with_mind)
- H.mind_initialize("TestKey[rand(0,10000)]")
-
- return H
diff --git a/code/unit_tests/zz_tests.dm b/code/unit_tests/zz_tests.dm
deleted file mode 100644
index 39f904ce03..0000000000
--- a/code/unit_tests/zz_tests.dm
+++ /dev/null
@@ -1,30 +0,0 @@
-///NOTE: LEAVE THIS FILE UNTICKED AND THE SEGMENTS OF CODE COMMENTED OUT UNLESS YOU NEED TO DO SOMETHING SPECIFIC / TRACK SOMETHING SPECIFIC.
-
-
-/*
-/// Testing if DNA & Species datums get destoryed or not. Use this as a basis to test for memory leaks.
-/// This happens if DNA is copied from somewhere but not qdel_swap()'d. WARNING: Not every time it's copied should be qdel_swap()'d.
-/// But if you are doing anything DNA copy related, turn this on and make sure it's not infintely storing a datum somewhere.
-GLOBAL_VAR(total_dna_counters)
-GLOBAL_VAR(total_species_counters)
-
-/datum/dna/New()
- . = ..()
- GLOB.total_dna_counters += 1
-
-/datum/dna/Destroy()
- . = ..()
- GLOB.total_dna_counters -= 1
-
-/datum/species/New()
- . = ..()
- GLOB.total_species_counters += 1
-
-/datum/species/Destroy()
- . = ..()
- GLOB.total_species_counters -= 1
-
-/proc/delete_all_monkeys()
- for(var/mob/living/carbon/human/monkey/monkeys in world)
- qdel(monkeys)
-*/
diff --git a/config/example/config.txt b/config/example/config.txt
index e110289d92..c51746668f 100644
--- a/config/example/config.txt
+++ b/config/example/config.txt
@@ -17,6 +17,12 @@ $include sqlite.txt
## Server name: This appears at the top of the screen in-game. In this case it will read "tgstation: station_name" where station_name is the randomly generated name of the station for the round. Remove the # infront of SERVERNAME and replace 'tgstation' with the name of your choice
# SERVERNAME spacestation13
+## Lobby time: This is the amount of time between rounds that players have to setup their characters and be ready.
+LOBBY_COUNTDOWN 120
+
+## Round End Time: This is the amount of time after the round ends that players have to murder death kill each other.
+ROUND_END_COUNTDOWN 90
+
## Comment this out if you want to use the SQL based admin system, the legacy system uses admins.txt.
## You need to set up your database to use the SQL based system.
## This flag is automatically enabled if SQL_ENABLED isn't
@@ -285,9 +291,6 @@ TICKLAG 0.25
## FIXME: Unused config. Only uncomment if changed in code.
# TICKCOMP 1
-## Whether the server will talk to other processes through socket_talk
-SOCKET_TALK 0
-
## Uncomment this to ban use of ToR
#TOR_BAN
@@ -600,5 +603,56 @@ JUKEBOX_TRACK_FILES config/jukebox.json
#Turn this on if you want all admin-PMs to go to be sent to discord, and not only the first message of a ticket.
#DISCORD_AHELPS_ALL
+###Master Controller High Pop Mode###
+
+##The Master Controller(MC) is the primary system controlling timed tasks and events in SS13 (lobby timer, game checks, lighting updates, atmos, etc)
+##Default base MC tick rate (1 = process every "byond tick" (see: tick_lag/fps config settings), 2 = process every 2 byond ticks, etc)
+## Setting this to 0 will prevent the Master Controller from ticking
+BASE_MC_TICK_RATE 1
+
+##High population MC tick rate
+## Byond rounds timer values UP, but the tick rate is modified with heuristics during lag spites so setting this to something like 2
+## will make it run every 2 byond ticks, but will also double the effect of anti-lag heuristics. You can instead set it to something like
+## 1.1 to make it run every 2 byond ticks, but only increase the effect of anti-lag heuristics by 10%. or 1.5 for 50%.
+## (As an aside, you could in theory also reduce the effect of anti-lag heuristics in the base tick rate by setting it to something like 0.5)
+HIGH_POP_MC_TICK_RATE 1.1
+
+##Engage high pop mode if player count raises above this (Player in this context means any connected user. Lobby, ghost or in-game all count)
+HIGH_POP_MC_MODE_AMOUNT 65
+
+##Disengage high pop mode if player count drops below this
+DISABLE_HIGH_POP_MC_MODE_AMOUNT 60
+
+## Uncomment to prevent the world from sleeping while no players are connected after initializations
+#RESUME_AFTER_INITIALIZATIONS
+
## Uncomment to set the number of /world/Reboot()s before the DreamDaemon restarts itself. 0 means restart every round. Requires tgstation server tools.
#ROUNDS_UNTIL_HARD_RESTART 10
+
+## Enable automatic profiling - Byond 513.1506 and newer only.
+#AUTO_PROFILE
+
+## Determines the interval between each saved profiler snapshot (in deciseconds).
+#PROFILER_INTERVAL 3000
+
+## Threshold (in deciseconds) for real time between ticks before we start dumping profiles
+DRIFT_DUMP_THRESHOLD 40
+
+## How long to wait (in deciseconds) after a profile dump before logging another tickdrift sourced one
+DRIFT_PROFILE_DELAY 150
+
+## Uncomment to block all attempts to profile, for performance reasons
+#FORBID_ALL_PROFILING
+
+## Uncomment to block granting profiling privileges to users with R_DEBUG, for performance purposes
+#FORBID_ADMIN_PROFILING
+
+## Comment to disable sending a toast notification on the host server when initializations complete.
+## Even if this is enabled, a notification will only be sent if there are no clients connected.
+TOAST_NOTIFICATION_ON_INIT
+
+## Uncomment to allow admins with +DEBUG to start the byond-tracy profiler during the round.
+#ALLOW_TRACY_START
+
+## Uncomment to allow admins with +DEBUG to queue the next round to run the byond-tracy profiler.
+#ALLOW_TRACY_QUEUE
diff --git a/config/example/game_options.txt b/config/example/game_options.txt
index 6fe8697bc2..28423f5534 100644
--- a/config/example/game_options.txt
+++ b/config/example/game_options.txt
@@ -44,6 +44,9 @@ REVIVAL_BRAIN_LIFE -1
RUN_SPEED 3
WALK_SPEED 9
+## Force Random Names.
+## I think this also randomizes bodies for some reason.
+# FORCE_RANDOM_NAMES
## The variables below affect the movement of specific mob types.
HUMAN_DELAY 0
diff --git a/config/names/clown.txt b/config/names/clown.txt
deleted file mode 100644
index c29fca74ba..0000000000
--- a/config/names/clown.txt
+++ /dev/null
@@ -1,37 +0,0 @@
-Gigglesworth
-Honkel the III
-Goose McSunny
-Mr. Shoe
-Toodles Sharperton
-Dinky Doodle
-Honkerbelle
-Bo Bo Sassy
-Baby Cakes
-Ladybug Honks
-Ziggy Yoyo
-Razzle Dazzle
-Buster Frown
-Pepinpop
-Silly Willy
-Jo Jo Bobo Bo
-Pocket
-Patches
-Checkers
-Freckle
-Honker
-Bonker
-Skiddle
-Scootaloo
-Sprinkledinkle
-Ronnie Pace
-Miss Stockings
-Slippy Joe
-Redshirt McBeat
-Flop O'Honker
-Speckles
-Bubble
-Button
-Sparkle
-Giggles
-Jingle
-Candy
\ No newline at end of file
diff --git a/config/names/wizardfirst.txt b/config/names/wizardfirst.txt
deleted file mode 100644
index 18806ca74b..0000000000
--- a/config/names/wizardfirst.txt
+++ /dev/null
@@ -1,36 +0,0 @@
-Jim
-Gulstaff
-Gandalf
-Grimm
-Mordenkainen
-Elminister
-Saruman
-Vaarsuvius
-Yoda
-Zul
-Nihilus
-Vecna
-Mogan
-Circe
-Prospero
-Raistlin
-Rasputin
-Tzeentch
-Khelben
-Dumbledor
-Houdini
-Terefi
-Urza
-Tenser
-Zagyg
-Mystryl
-Boccob
-Merlin
-Archchancellor
-Radagast
-Kreol
-Kaschei
-Lina
-Morgan
-Alatar
-Palando
\ No newline at end of file
diff --git a/config/names/wizardsecond.txt b/config/names/wizardsecond.txt
deleted file mode 100644
index 1fe60bdb27..0000000000
--- a/config/names/wizardsecond.txt
+++ /dev/null
@@ -1,39 +0,0 @@
-the Powerful
-the Great
-the Magician
-the Wise
-the Seething
-the Amazing
-the Spiral King
-Darkmagic
-the White
-the Gray
-Shado
-the Sorcelator
-the Raven
-the Emperor
-the Brown
-Weatherwax
-the Destroyer
-the Deathless
-Yagg
-the Remorseful
-the Weeping
-the Unending
-the All Knowing
-Dark
-Smith
-the Conquerer
-the Unstoppable
-Gray
-of Void
-Unseen
-Darko
-Honko
-the Bandit Killer
-the Dragon Spooker
-Inverse
-le Fay
-the Blue
-the Red
-the Benevolent
\ No newline at end of file
diff --git a/icons/blanks/32x32.dmi b/icons/blanks/32x32.dmi
new file mode 100644
index 0000000000..6c4f2b33e0
Binary files /dev/null and b/icons/blanks/32x32.dmi differ
diff --git a/icons/virgoicon_16.png b/icons/virgoicon_16.png
new file mode 100644
index 0000000000..5889e20fa9
Binary files /dev/null and b/icons/virgoicon_16.png differ
diff --git a/lib/DLLSocket/DLLSocket.cbp b/lib/DLLSocket/DLLSocket.cbp
deleted file mode 100644
index 6415a5ebd6..0000000000
--- a/lib/DLLSocket/DLLSocket.cbp
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/lib/DLLSocket/compile.sh b/lib/DLLSocket/compile.sh
deleted file mode 100644
index 0ff309bdb9..0000000000
--- a/lib/DLLSocket/compile.sh
+++ /dev/null
@@ -1 +0,0 @@
-g++ -static -shared -O3 -fPIC main.cpp -o DLLSocket.so
diff --git a/lib/DLLSocket/main.cpp b/lib/DLLSocket/main.cpp
deleted file mode 100644
index e3aafb8791..0000000000
--- a/lib/DLLSocket/main.cpp
+++ /dev/null
@@ -1,133 +0,0 @@
-// OS-specific networking includes
-// -------------------------------
-#ifdef __WIN32
- #include
- typedef int socklen_t;
-#else
- extern "C" {
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- }
-
- typedef int SOCKET;
- typedef sockaddr_in SOCKADDR_IN;
- typedef sockaddr SOCKADDR;
- #define SOCKET_ERROR -1
-#endif
-
-// Socket used for all communications
-SOCKET sock;
-
-// Address of the remote server
-SOCKADDR_IN addr;
-
-// Buffer used to return dynamic strings to the caller
-#define BUFFER_SIZE 1024
-char return_buffer[BUFFER_SIZE];
-
-// exposed functions
-// ------------------------------
-
-const char* SUCCESS = "1\0"; // string representing success
-
-#ifdef __WIN32
- #define DLL_EXPORT __declspec(dllexport)
-#else
- #define DLL_EXPORT __attribute__ ((visibility ("default")))
-#endif
-
-// arg1: ip(in the xx.xx.xx.xx format)
-// arg2: port(a short)
-// return: NULL on failure, SUCCESS otherwise
-extern "C" DLL_EXPORT const char* establish_connection(int n, char *v[])
-{
- // extract args
- // ------------
- if(n < 2) return 0;
- const char* ip = v[0];
- const char* port_s = v[1];
- unsigned short port = atoi(port_s);
-
- // set up network stuff
- // --------------------
- #ifdef __WIN32
- WSADATA wsa;
- WSAStartup(MAKEWORD(2,0),&wsa);
- #endif
- sock = socket(AF_INET,SOCK_DGRAM,0);
-
- // make the socket non-blocking
- // ----------------------------
- #ifdef __WIN32
- unsigned long iMode=1;
- ioctlsocket(sock,FIONBIO,&iMode);
- #else
- fcntl(sock, F_SETFL, O_NONBLOCK);
- #endif
-
- // establish a connection to the server
- // ------------------------------------
- memset(&addr,0,sizeof(SOCKADDR_IN));
- addr.sin_family=AF_INET;
- addr.sin_port=htons(port);
-
- // convert the string representation of the ip to a byte representation
- addr.sin_addr.s_addr=inet_addr(ip);
-
- return SUCCESS;
-}
-
-// arg1: string message to send
-// return: NULL on failure, SUCCESS otherwise
-extern "C" DLL_EXPORT const char* send_message(int n, char *v[])
-{
- // extract the args
- if(n < 1) return 0;
- const char* msg = v[0];
-
- // send the message
- int rc = sendto(sock,msg,strlen(msg),0,(SOCKADDR*)&addr,sizeof(SOCKADDR));
-
- // check for errors
- if (rc != -1) {
- return SUCCESS;
- }
- else {
- return 0;
- }
-}
-
-// no args
-// return: message if any received, NULL otherwise
-extern "C" DLL_EXPORT const char* recv_message(int n, char *v[])
-{
- SOCKADDR_IN sender; // we will store the sender address here
-
- socklen_t sender_byte_length = sizeof(sender);
-
- // Try receiving messages until we receive one that's valid, or there are no more messages
- while(1) {
- int rc = recvfrom(sock, return_buffer, BUFFER_SIZE,0,(SOCKADDR*) &sender,&sender_byte_length);
- if(rc > 0) {
- // we could read something
-
- if(sender.sin_addr.s_addr != addr.sin_addr.s_addr) {
- continue; // not our connection, ignore and try again
- } else {
- return_buffer[rc] = 0; // 0-terminate the string
- return return_buffer;
- }
- }
- else {
- break; // no more messages, stop trying to receive
- }
- }
-
- return 0;
-}
diff --git a/lib/DLLSocket/server_controller.py b/lib/DLLSocket/server_controller.py
deleted file mode 100644
index 943f37f677..0000000000
--- a/lib/DLLSocket/server_controller.py
+++ /dev/null
@@ -1,51 +0,0 @@
-import subprocess
-import socket
-import urlparse
-
-UDP_IP="127.0.0.1"
-UDP_PORT=8019
-
-sock = socket.socket( socket.AF_INET, # Internet
- socket.SOCK_DGRAM ) # UDP
-sock.bind( (UDP_IP,UDP_PORT) )
-
-last_ticker_state = None
-
-def handle_message(data, addr):
- global last_ticker_state
-
- params = urlparse.parse_qs(data)
- print(data)
-
- try:
- if params["type"][0] == "log" and str(params["log"][0]) and str(params["message"][0]):
- open(params["log"][0],"a+").write(params["message"][0]+"\n")
- except IOError:
- pass
- except KeyError:
- pass
-
- try:
- if params["type"][0] == "ticker_state" and str(params["message"][0]):
- last_ticker_state = str(params["message"][0])
- except KeyError:
- pass
-
- try:
- if params["type"][0] == "startup" and last_ticker_state:
- open("crashlog.txt","a+").write("Server exited, last ticker state was: "+last_ticker_state+"\n")
- except KeyError:
- pass
-
-sock.settimeout(60*6) # 10 minute timeout
-while True:
- try:
- data, addr = sock.recvfrom( 1024 ) # buffer size is 1024 bytes
- handle_message(data,addr)
- except socket.timeout:
- # try to start the server again
- print("Server timed out.. attempting restart.")
- if last_ticker_state:
- open("crashmsg.txt","a+").write("Server crashed, trying to reboot. last ticker state: "+last_ticker_state+"\n")
- subprocess.call("killall -9 DreamDaemon")
- subprocess.call("./start")
\ No newline at end of file
diff --git a/maps/common/common_defines.dm b/maps/common/common_defines.dm
index 1607e4ac09..1bd3a99f13 100644
--- a/maps/common/common_defines.dm
+++ b/maps/common/common_defines.dm
@@ -181,6 +181,16 @@
skybox_pixel_x = 450
skybox_pixel_y = 200
+GLOBAL_LIST_EMPTY(all_stellar_delights)
+
+/obj/effect/overmap/visitable/ship/stellar_delight/Initialize(mapload)
+ . = ..()
+ GLOB.all_stellar_delights += src
+
+/obj/effect/overmap/visitable/ship/stellar_delight/Destroy()
+ GLOB.all_stellar_delights -= src
+ . = ..()
+
/obj/effect/overmap/visitable/sector/virgo2
name = "Virgo 2"
desc = "Includes the Remmi Aerostat and associated ground mining complexes."
diff --git a/maps/expedition_vr/aerostat/_aerostat_science_outpost.dm b/maps/expedition_vr/aerostat/_aerostat_science_outpost.dm
index e256fb3204..840a654c49 100644
--- a/maps/expedition_vr/aerostat/_aerostat_science_outpost.dm
+++ b/maps/expedition_vr/aerostat/_aerostat_science_outpost.dm
@@ -5,9 +5,13 @@
initial_generic_waypoints = list("aerostat_n_w", "aerostat_n_n","aerostat_n_e","aerostat_s_w","aerostat_s_s","aerostat_s_e","aerostat_west","aerostat_east")
/obj/effect/overmap/visitable/sector/virgo2/Initialize(mapload)
- for(var/obj/effect/overmap/visitable/ship/stellar_delight/sd in world)
- docking_codes = sd.docking_codes
. = ..()
+ return INITIALIZE_HINT_LATELOAD
+
+/obj/effect/overmap/visitable/sector/virgo2/LateInitialize()
+ for(var/obj/effect/overmap/visitable/ship/stellar_delight/sd in GLOB.all_stellar_delights)
+ docking_codes = sd.docking_codes
+ return
// -- Datums -- //
diff --git a/maps/expedition_vr/space/_debrisfield.dm b/maps/expedition_vr/space/_debrisfield.dm
index 8c021ce59f..e61e125305 100644
--- a/maps/expedition_vr/space/_debrisfield.dm
+++ b/maps/expedition_vr/space/_debrisfield.dm
@@ -175,7 +175,7 @@
rename_areas(newname)
/obj/effect/overmap/visitable/ship/landable/luxury_boat/proc/rename_areas(newname)
- if(!SSshuttles.subsystem_initialized)
+ if(!SSshuttles.initialized)
spawn(300)
rename_areas(newname)
return
@@ -266,7 +266,7 @@
rename_areas(newname)
/obj/effect/overmap/visitable/ship/landable/tinycarrier/proc/rename_areas(newname)
- if(!SSshuttles.subsystem_initialized)
+ if(!SSshuttles.initialized)
spawn(300)
rename_areas(newname)
return
diff --git a/maps/gateway_vr/wildwest.dm b/maps/gateway_vr/wildwest.dm
index e503f43dd8..757f0939eb 100644
--- a/maps/gateway_vr/wildwest.dm
+++ b/maps/gateway_vr/wildwest.dm
@@ -79,7 +79,7 @@
if("To Kill")
to_chat(user, span_boldwarning("Your wish is granted, but at a terrible cost..."))
to_chat(user, span_warning("The Wish Granter punishes you for your wickedness, claiming your soul and warping your body to match the darkness in your heart."))
- ticker.mode.traitors += user.mind
+ SSticker.mode.traitors += user.mind
user.mind.special_role = "traitor"
var/datum/objective/hijack/hijack = new
hijack.owner = user.mind
diff --git a/maps/stellar_delight/stellar_delight_defines.dm b/maps/stellar_delight/stellar_delight_defines.dm
index 612baf025a..7aab63553d 100644
--- a/maps/stellar_delight/stellar_delight_defines.dm
+++ b/maps/stellar_delight/stellar_delight_defines.dm
@@ -356,9 +356,11 @@
/////SD Starts at V3b to pick up crew refuel and repair (And to make sure it doesn't spawn on hazards)
/obj/effect/overmap/visitable/sector/virgo3b/Initialize(mapload)
. = ..()
- for(var/obj/effect/overmap/visitable/ship/stellar_delight/sd in world)
+ return INITIALIZE_HINT_LATELOAD
+
+/obj/effect/overmap/visitable/sector/virgo3b/LateInitialize()
+ for(var/obj/effect/overmap/visitable/ship/stellar_delight/sd in GLOB.all_stellar_delights)
sd.forceMove(loc, SOUTH)
- return
/obj/effect/overmap/visitable/sector/virgo3b/Crossed(var/atom/movable/AM)
. = ..()
diff --git a/maps/stellar_delight/stellar_delight_shuttle_defs.dm b/maps/stellar_delight/stellar_delight_shuttle_defs.dm
index 7e55444eb5..16d6bbb53a 100644
--- a/maps/stellar_delight/stellar_delight_shuttle_defs.dm
+++ b/maps/stellar_delight/stellar_delight_shuttle_defs.dm
@@ -136,11 +136,13 @@
mob_announce_cooldown = 0
/////SD Starts at V3b to pick up crew refuel and repair (And to make sure it doesn't spawn on hazards)
-/obj/effect/overmap/visitable/sector/virgo3b/Initialize()
+/obj/effect/overmap/visitable/sector/virgo3b/Initialize(mapload)
. = ..()
- for(var/obj/effect/overmap/visitable/ship/stellar_delight/sd in world)
+ return INITIALIZE_HINT_LATELOAD
+
+/obj/effect/overmap/visitable/sector/virgo3b/LateInitialize()
+ for(var/obj/effect/overmap/visitable/ship/stellar_delight/sd in GLOB.all_stellar_delights)
sd.forceMove(loc, SOUTH)
- return
/obj/effect/overmap/visitable/sector/virgo3b/Crossed(var/atom/movable/AM)
. = ..()
diff --git a/maps/submaps/admin_use_vr/event_autonomous_drone.dm b/maps/submaps/admin_use_vr/event_autonomous_drone.dm
index 860f2a468b..51b572b2bf 100644
--- a/maps/submaps/admin_use_vr/event_autonomous_drone.dm
+++ b/maps/submaps/admin_use_vr/event_autonomous_drone.dm
@@ -52,7 +52,7 @@
rename_areas(newname)
/obj/effect/overmap/visitable/ship/landable/event_autonomous_drone/proc/rename_areas(newname)
- if(!SSshuttles.subsystem_initialized)
+ if(!SSshuttles.initialized)
spawn(300)
rename_areas(newname)
return
diff --git a/maps/templates/unit_tests.dmm b/maps/templates/unit_tests.dmm
new file mode 100644
index 0000000000..a3b03e27e5
--- /dev/null
+++ b/maps/templates/unit_tests.dmm
@@ -0,0 +1,79 @@
+//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE
+"a" = (
+/turf/closed/indestructible,
+/area/misc/testroom)
+"m" = (
+/turf/simulated/floor/tiled/steel,
+/area/misc/testroom)
+"r" = (
+/obj/effect/landmark/unit_test_top_right,
+/turf/simulated/floor/tiled/steel,
+/area/misc/testroom)
+"L" = (
+/obj/effect/landmark/unit_test_bottom_left,
+/turf/simulated/floor/tiled/steel,
+/area/misc/testroom)
+
+(1,1,1) = {"
+a
+a
+a
+a
+a
+a
+a
+"}
+(2,1,1) = {"
+a
+m
+m
+m
+m
+L
+a
+"}
+(3,1,1) = {"
+a
+m
+m
+m
+m
+m
+a
+"}
+(4,1,1) = {"
+a
+m
+m
+m
+m
+m
+a
+"}
+(5,1,1) = {"
+a
+m
+m
+m
+m
+m
+a
+"}
+(6,1,1) = {"
+a
+r
+m
+m
+m
+m
+a
+"}
+(7,1,1) = {"
+a
+a
+a
+a
+a
+a
+a
+"}
diff --git a/maps/tether/tether_phoronlock.dm b/maps/tether/tether_phoronlock.dm
index 7b7ce6a321..929e045910 100644
--- a/maps/tether/tether_phoronlock.dm
+++ b/maps/tether/tether_phoronlock.dm
@@ -56,10 +56,10 @@
set_frequency(frequency)
/obj/machinery/portable_atmospherics/powered/scrubber/huge/stationary/proc/set_frequency(new_frequency)
- radio_controller.remove_object(src, frequency)
+ SSradio.remove_object(src, frequency)
frequency = new_frequency
if(frequency)
- radio_connection = radio_controller.add_object(src, frequency, radio_filter = RADIO_ATMOSIA)
+ radio_connection = SSradio.add_object(src, frequency, radio_filter = RADIO_ATMOSIA)
/obj/machinery/portable_atmospherics/powered/scrubber/huge/stationary/receive_signal(datum/signal/signal)
if(!signal.data["tag"] || (signal.data["tag"] != scrub_id) || (signal.data["sigtype"] != "command"))
diff --git a/maps/~map_system/maps.dm b/maps/~map_system/maps.dm
index 6c455b2f64..ebb1c3c9e5 100644
--- a/maps/~map_system/maps.dm
+++ b/maps/~map_system/maps.dm
@@ -139,6 +139,8 @@ var/list/all_maps = list()
var/list/planet_datums_to_make = list() // Types of `/datum/planet`s that will be instantiated by SSPlanets.
+ var/list/skipped_tests = list() // /datum/unit_test's to skip
+
/datum/map/New()
..()
if(zlevel_datum_type)
diff --git a/config/names/adjectives.txt b/strings/names/adjectives.txt
similarity index 100%
rename from config/names/adjectives.txt
rename to strings/names/adjectives.txt
diff --git a/config/names/ai.txt b/strings/names/ai.txt
similarity index 98%
rename from config/names/ai.txt
rename to strings/names/ai.txt
index 52ef743132..4ff0a3dd28 100644
--- a/config/names/ai.txt
+++ b/strings/names/ai.txt
@@ -52,4 +52,4 @@ VER-T G0
Wisdom
XOR
Y2K
-ZE-M3
\ No newline at end of file
+ZE-M3
diff --git a/strings/names/clown.txt b/strings/names/clown.txt
new file mode 100644
index 0000000000..1a5f8740ae
--- /dev/null
+++ b/strings/names/clown.txt
@@ -0,0 +1,124 @@
+Aluminium Dave
+Baba
+Baby Cakes
+Bananium LXIX
+Bimbim
+Bingo
+Birdman From Birdland
+Bizarre Bottle
+Bizarro
+Bo Bo Sassy
+Bonker
+Bubble
+Buster Frown
+Butters The Bean
+Buttery Muffin
+Button
+Candy
+Carphunter
+Checkers
+Chuckles
+Circe The Great
+Clownpiece
+Congo Bongo
+Cool Cooper
+Crusty
+Deedum Dedah
+Delicious Dan
+Dinkster
+Dinky Doodle
+Doctor Greenthumb
+Doctor Rockso
+Doink
+Early Worm
+Eggy
+Emotional Oatmeal
+Fat Fingers
+Fishbreath
+Flop O'Honker
+Freckle
+Fretworks
+Galton
+Giggles
+Gigglesworth
+Goose McSunny
+Grabby
+Grandma
+Grebble
+Greedo Shotfurst
+Happy Slappy
+Harvey Harvey Harvey
+Hedgerow Harry
+Hefty Hempo
+Homey
+Honkel the III
+Honker
+Honkerbelle
+Implausible Fun
+Impro
+Jazzle
+Jim From Accounting
+Jingle
+Jo Jo Bobo Bo
+Johnny Bigshoes
+King of Klowns
+Ladybug Honks
+Laughing Man
+Loopy Lazarus
+Mime
+Misery Marvin
+Miss Stockings
+Mister Ostprussen
+Mister Redherring
+Mister Roboto
+Mister Safety Hazard
+Moinen
+Mr Shoe
+Mr Weird
+Ningelo
+Oranges
+Patches
+Pepinpop
+Pocket
+Pogo
+Polite Pablo
+Post Nose
+Razzle Dazzle
+Red April
+Red Dead Redemption Two
+Redshirt McBeat
+Rinso The Soapy
+Roboflop
+Ronnie Pace
+Rude Robert
+Sandwich Sam
+Scootaloo
+Shinturner
+Shithead The Irritator
+Shoe Giving Gerald
+Silly Willy
+Sir Honks-a-Lot
+Skiddle
+Slippo Supreme
+Slippy Joe
+Slipsy Dipsy
+Sparkle
+Speckles
+Sprinkledinkle
+The Amazing Farto
+The Best
+The Fun Uncle
+The Mediocre
+The Unfun Uncle
+The Worst
+Toodles Sharperton
+Tree Talker
+Twisty
+Uncool Ulrich
+Unimaginable Nut
+Valid Vincent
+Weather Report
+Widderwise
+Yesterdays Beef
+Yobbo
+Ziggy Yoyo
diff --git a/config/names/death_commando.txt b/strings/names/death_commando.txt
similarity index 71%
rename from config/names/death_commando.txt
rename to strings/names/death_commando.txt
index f882ea581e..efae21fe3b 100644
--- a/config/names/death_commando.txt
+++ b/strings/names/death_commando.txt
@@ -1,69 +1,90 @@
+A whole bunch of spiders in a MODsuit
+Al "Otta" Gore
+AMERICA
+Beat Punchbeef
+Beef Manmuscle
+Beef Slab
+Benny Bloodmulch
+Bicep McTricep
+Billy Herrington
+Blast Hardcheese
+Blast Thickneck
+Bob Johnson
+Bold Bigflank
+Bolt Vanderhuge
+Brawn Hugeneck
+Brick Hardmeat
+Buck Plankchest
+Buff Drinklots
+Buff Hardback
+Buff Slamchest
+Butch Deadlift
+Chombo Cromagnum
+Crud Bonemeal
+Crunch Buttsteak
+Crush McStompbones
+Diego Diefist
+Dirk Hardpeck
+Duke Killington
+Evil Bob Marley
+Evil Martin Luther King
+Fist Rockbone
+Flint Ironstag
+Ford Spinetwist
+Fridge Largemeat
+George Melons
+Gibbs McLargehuge
+GORE Vidal
+Gristle McThornBody
+Hank Chesthair
+Hans Testosteroneson
+Jet Handblood
Killiam Shakespeare
-Stabby McGee
-Sgt. Slaughter
+Killing McKillingalot
+Lance Killiam
+Leonardo Da Viking
+Lump Beefrock
+Lunge Crushheel
+Max Pain
+Maximilian Murderface
Maxx Power
+Noam Bombsky
+Norb Chrometaint
+Oats Squatzon
+Pack Blowfist
+Punch Rockgroin
+Punch Sideiron
+Punt Speedchunk
+Reef Blastbody
+Rex Dudekiller VII
+Rip Sidecheek
+Rip Steakface
+Rock Vangranite
+Roid Swolebod
+Roll Fizzlebeef
+Ronnie Roidready
+Sarah Pain
+Seamus McTosterone
+Sgt Slaughter
+Shred Gluteflex
Sir Killaslot
Slab Bulkhead
-Fridge Largemeat
-Punt Speedchunk
-Butch Deadlift
-Bold Bigflank
-Splint Chesthair
-Flint Ironstag
-Bolt Vanderhuge
-Thick McRunfast
-Blast Hardcheese
-Buff Drinklots
-Trunk Slamchest
-Fist Rockbone
-Stump Beefgnaw
-Smash Lampjaw
-Punch Rockgroin
-Buck Plankchest
-Stump Chunkman
-Dirk Hardpeck
-Rip Steakface
-Slate Slabrock
-Crud Bonemeal
-Brick Hardmeat
-Rip Sidecheek
-Punch Sideiron
-Gristle McThornBody
-Slake Fistcrunch
-Buff Hardback
-Blast Thickneck
-Crunch Buttsteak
Slab Squatthrust
-Lump Beefrock
-Touch Rustrod
-Reef Blastbody
+Slake Fistcrunch
+Slate Slabrock
+Smash Lampjaw
Smoke Manmuscle
-Beat Punchbeef
-Pack Blowfist
-Roll Fizzlebeef
-Lance Killiam
-George Melons
-Maximilian Murderface
-Bob Johnson
-Crush McStompbones
-Hank Chesthair
-Killing McKillingalot
-Mancrush McBrorape
-Rex Dudekiller VII
-Seamus McTosterone
-Hans Testosteroneson
-Max Pain
+Splint Chesthair
+Stabby McGee
+Stump Beefgnaw
+Stump Chunkman
Theodore Pain
-Sarah Pain
-GORE Vidal
-Leonardo Da Viking
-Noam Bombsky
-Al "Otta" Gore
-Gibbs McLargehuge
-Evil Martin Luther King
-Evil Bob Marley
-Duke Killington
-AMERICA
+Thick McRunfast
+Thrust Vanderhuge
Toolboxl Rose
+Touch Rustrod
+Trooper Initforbenefits
+Trent Tightshirt
+Trunk Slamchest
+Van Darkholme
Zombie Gandhi
-A whole bunch of spiders in a SWAT suit
\ No newline at end of file
diff --git a/config/names/first.txt b/strings/names/first.txt
similarity index 100%
rename from config/names/first.txt
rename to strings/names/first.txt
diff --git a/config/names/first_female.txt b/strings/names/first_female.txt
similarity index 100%
rename from config/names/first_female.txt
rename to strings/names/first_female.txt
diff --git a/config/names/first_male.txt b/strings/names/first_male.txt
similarity index 100%
rename from config/names/first_male.txt
rename to strings/names/first_male.txt
diff --git a/config/names/first_name_skrell.txt b/strings/names/first_name_skrell.txt
similarity index 100%
rename from config/names/first_name_skrell.txt
rename to strings/names/first_name_skrell.txt
diff --git a/config/names/last.txt b/strings/names/last.txt
similarity index 100%
rename from config/names/last.txt
rename to strings/names/last.txt
diff --git a/config/names/last_name_skrell.txt b/strings/names/last_name_skrell.txt
similarity index 100%
rename from config/names/last_name_skrell.txt
rename to strings/names/last_name_skrell.txt
diff --git a/config/names/ninjaname.txt b/strings/names/ninjaname.txt
similarity index 100%
rename from config/names/ninjaname.txt
rename to strings/names/ninjaname.txt
diff --git a/config/names/ninjatitle.txt b/strings/names/ninjatitle.txt
similarity index 100%
rename from config/names/ninjatitle.txt
rename to strings/names/ninjatitle.txt
diff --git a/config/names/verbs.txt b/strings/names/verbs.txt
similarity index 100%
rename from config/names/verbs.txt
rename to strings/names/verbs.txt
diff --git a/strings/names/wizardfirst.txt b/strings/names/wizardfirst.txt
new file mode 100644
index 0000000000..f4822a2d79
--- /dev/null
+++ b/strings/names/wizardfirst.txt
@@ -0,0 +1,98 @@
+Aberforth
+Ad
+Aganemnom
+Alatar
+Aluneth
+Arbiter
+Arcano
+Archchancellor
+Asriel
+Aurora
+Baldric
+Barachiel
+Bigby
+Blasto
+Bob
+Boccob
+Boldwort
+Bona
+Carl
+Channeler
+Circe
+Curley
+Dickel
+Edison
+Ehran
+Elminister
+Eric
+Fenty
+Fernando
+Fizban
+Fizzy
+Fumblemoar
+Gabriel
+Gandalf
+Grimm
+Gulstaff
+Houdini
+Jegudiel
+Jim
+Judge
+Kaschei
+Kazuo
+Khelben
+Koschei
+Kreol
+Larry
+Ley
+Lina
+Magier
+Merlin
+Micheal
+Mitarbeiter
+Mo
+Mogan
+Mordenkainen
+Morgan
+Mortimer
+Munstrum
+Mystryl
+Nihilus
+Nikola
+Odysseus
+Othello
+Palando
+Pixel
+Ponder
+Prospero
+Radagast
+Raistlin
+Raphael
+Rasputin
+Rihanna
+Rincewind
+Roduct
+Saruman
+Selaphiel
+Sigmund
+Supreme
+Tabula
+Temporus
+Tenser
+Terefi
+Tim
+Tom
+Tzeentch
+Ulysses
+Uriel
+Urza
+Vaarsuvius
+Vecna
+Vlad
+Willow
+Wizardman
+Yoda
+Zagyg
+Zatanna
+Zifnab
+Zul
diff --git a/strings/names/wizardsecond.txt b/strings/names/wizardsecond.txt
new file mode 100644
index 0000000000..1b51849e48
--- /dev/null
+++ b/strings/names/wizardsecond.txt
@@ -0,0 +1,99 @@
+Among-many
+Azurepelt
+Borealis
+Dark
+Darkmagic
+Darko
+Deathbringer
+Doomancer
+Evergreen
+Evilton
+Fide
+Gray
+Honko
+Infinitum
+Inverse
+Lifereaper
+Meatsmith
+Midnight
+Mother-of-dragons
+Mountain-Mother
+Namekeeper
+Nauseam
+Rasa
+Runespear
+Sequitur
+Shado
+Shadowdancer
+Shorekeeper
+Smith
+Spellstone
+Starfall
+Starlightking
+Starlightqueen
+Stormcaller
+Strange
+Suneater
+Truenamer
+Unseen
+Voidfall
+Voidstabber
+Volente
+Weatherwax
+Who-was-blood
+Who-was-light
+Who-was-stone
+Windshearer
+Worldshaker
+Yagg
+le Fay
+of Void
+the Alchemist
+the All Knowing
+the Amazing
+the Annoyed
+the Annoying
+the Bandit Killer
+the Benevolent
+the Blue
+the Brown
+the Colourless
+the Conquerer
+the Dappled
+the Deathless
+the Destroyer
+the Dragon Spooker
+the Dragoncaller
+the Drunk
+the Emperor
+the Empty
+the Ethereal
+the Excoherent
+the Gray
+the Great
+the Herb
+the Impaler
+the Incoherent
+the Jaunter
+the Magician
+the Manual
+the Moldywarp
+the Necromancer
+the Obscure
+the Powerful
+the Raven
+the Red
+the Remorseful
+the Seething
+the Sorcelator
+the Spiral King
+the Unending
+the Unseen
+the Unstoppable
+the Wandsmith
+the Waxmancer
+the Weeping
+the White
+the Wife
+the Wise
+the Wizzard
diff --git a/tgui/packages/tgui/interfaces/ControllerOverview/OverviewSection.tsx b/tgui/packages/tgui/interfaces/ControllerOverview/OverviewSection.tsx
new file mode 100644
index 0000000000..631b2525b4
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ControllerOverview/OverviewSection.tsx
@@ -0,0 +1,75 @@
+import { Button, LabeledList, Section, Stack } from 'tgui-core/components';
+
+import { useBackend } from '../../backend';
+import { type ControllerData } from './types';
+
+export function OverviewSection(props) {
+ const { act, data } = useBackend();
+ const {
+ fast_update,
+ rolling_length,
+ map_cpu,
+ subsystems = [],
+ world_time,
+ } = data;
+
+ let avgUsage = 0;
+ let overallOverrun = 0;
+ for (let i = 0; i < subsystems.length; i++) {
+ avgUsage += subsystems[i].usage_per_tick;
+ overallOverrun += subsystems[i].tick_overrun;
+ }
+
+ return (
+
+
+ {
+ act('set_rolling_length', {
+ rolling_length: value,
+ });
+ }}
+ />
+ >
+ }
+ >
+
+
+
+
+ {world_time.toFixed(1)}
+
+
+ {map_cpu.toFixed(2)}%
+
+
+
+
+
+
+ {avgUsage.toFixed(2)}%
+
+
+ {overallOverrun.toFixed(2)}%
+
+
+
+
+
+ );
+}
diff --git a/tgui/packages/tgui/interfaces/ControllerOverview/SubsystemDialog.tsx b/tgui/packages/tgui/interfaces/ControllerOverview/SubsystemDialog.tsx
new file mode 100644
index 0000000000..6f7cae9c4b
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ControllerOverview/SubsystemDialog.tsx
@@ -0,0 +1,76 @@
+import {
+ Box,
+ Button,
+ Divider,
+ LabeledList,
+ Modal,
+ Stack,
+} from 'tgui-core/components';
+
+import { type SubsystemData } from './types';
+
+type Props = {
+ subsystem: SubsystemData;
+ onClose: () => void;
+};
+
+export function SubsystemDialog(props: Props) {
+ const { subsystem, onClose } = props;
+ const {
+ cost_ms,
+ init_order,
+ initialization_failure_message,
+ last_fire,
+ name,
+ next_fire,
+ tick_overrun,
+ tick_usage,
+ usage_per_tick,
+ } = subsystem;
+
+ return (
+
+
+
+ {name}
+
+
+
+
+
+
+
+
+ {init_order}
+ {last_fire}
+ {next_fire}
+
+ {cost_ms.toFixed(2)}ms
+
+
+ {tick_usage.toFixed(2)}%
+
+
+ {usage_per_tick.toFixed(2)}%
+
+
+ {tick_overrun.toFixed(2)}%
+
+ {initialization_failure_message && (
+
+ {initialization_failure_message}
+
+ )}
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/tgui/packages/tgui/interfaces/ControllerOverview/SubsystemRow.tsx b/tgui/packages/tgui/interfaces/ControllerOverview/SubsystemRow.tsx
new file mode 100644
index 0000000000..1eecb6891f
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ControllerOverview/SubsystemRow.tsx
@@ -0,0 +1,107 @@
+import { type Dispatch } from 'react';
+import {
+ Button,
+ Icon,
+ ProgressBar,
+ Stack,
+ Table,
+ Tooltip,
+} from 'tgui-core/components';
+
+import { useBackend } from '../../backend';
+import { SORTING_TYPES } from './constants';
+import { SortType, type SubsystemData } from './types';
+
+type Props = {
+ max: number;
+ setSelected: Dispatch;
+ showBars: boolean;
+ sortType: SortType;
+ subsystem: SubsystemData;
+};
+
+export function SubsystemRow(props: Props) {
+ const { act } = useBackend();
+ const { max, setSelected, showBars, sortType, subsystem } = props;
+ const { can_fire, doesnt_fire, initialized, name, ref } = subsystem;
+
+ const { propName } = SORTING_TYPES[sortType];
+ const value = subsystem[propName];
+
+ let icon = 'play';
+ let color = 'good';
+ let tooltip = 'Operational';
+ if (!initialized) {
+ icon = 'circle-exclamation';
+ color = 'darkgreen';
+ tooltip = 'Not initialized';
+ } else if (doesnt_fire) {
+ icon = 'check';
+ color = 'grey';
+ tooltip = 'Does not fire';
+ } else if (!can_fire) {
+ icon = 'pause';
+ color = 'grey';
+ tooltip = 'Paused';
+ }
+
+ let valueDisplay = '';
+ let rangeDisplay = {};
+ if (showBars) {
+ if (sortType === SortType.Cost) {
+ valueDisplay = value.toFixed(2) + 'ms';
+ rangeDisplay = {
+ average: [75, 124.99],
+ bad: [125, Infinity],
+ };
+ } else {
+ valueDisplay = value.toFixed(2) + '%';
+ rangeDisplay = {
+ average: [10, 24.99],
+ bad: [25, Infinity],
+ };
+ }
+ } else {
+ valueDisplay = value;
+ }
+
+ return (
+
+
+
+
+
+
+ setSelected(subsystem)}>
+ {showBars ? (
+
+ {name} {valueDisplay}
+
+ ) : (
+
+ )}
+
+
+
+
+ );
+}
diff --git a/tgui/packages/tgui/interfaces/ControllerOverview/SubsystemViews.tsx b/tgui/packages/tgui/interfaces/ControllerOverview/SubsystemViews.tsx
new file mode 100644
index 0000000000..60b8bf1dfe
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ControllerOverview/SubsystemViews.tsx
@@ -0,0 +1,114 @@
+import { type Dispatch, useEffect, useState } from 'react';
+import { Button, Section, Stack, Table } from 'tgui-core/components';
+
+import { useBackend } from '../../backend';
+import { SORTING_TYPES } from './constants';
+import { type FilterState } from './filters';
+import { SubsystemRow } from './SubsystemRow';
+import { type ControllerData, type SubsystemData } from './types';
+
+type Props = {
+ filterOpts: FilterState;
+ setSelected: Dispatch;
+};
+
+export function SubsystemViews(props: Props) {
+ const { data } = useBackend();
+ const { subsystems } = data;
+
+ const { filterOpts, setSelected } = props;
+ const { ascending, inactive, query, smallValues, sortType } = filterOpts;
+ const { propName, inDeciseconds } = SORTING_TYPES[sortType];
+
+ const [bars, setBars] = useState(inDeciseconds);
+
+ const sorted = subsystems
+ .filter((subsystem) => {
+ // Matches search
+ const nameMatchesQuery = subsystem.name
+ .toLowerCase()
+ .includes(query?.toLowerCase());
+
+ // Filters out based on inactive
+ if (inactive && (!!subsystem.doesnt_fire || !subsystem.can_fire)) {
+ return false;
+ }
+
+ // Filters out based on small values
+ if (smallValues && subsystem[propName] < 1) {
+ return false;
+ }
+
+ return nameMatchesQuery;
+ })
+ .sort((a, b) => {
+ if (ascending) {
+ return a[propName] > b[propName] ? 1 : -1;
+ } else {
+ return b[propName] > a[propName] ? 1 : -1;
+ }
+ });
+
+ // Gets our totals for bar display
+ const totals: number[] = [];
+ let currentMax = 0;
+ if (inDeciseconds) {
+ for (let i = 0; i < sorted.length; i++) {
+ const value = sorted[i][propName];
+ if (typeof value !== 'number') {
+ continue;
+ }
+
+ totals.push(value);
+ }
+
+ currentMax = Math.max(...totals);
+ }
+
+ // Toggles default bar view for valid cases
+ useEffect(() => {
+ if (inDeciseconds && !bars) {
+ setBars(true);
+ } else if (!inDeciseconds && bars) {
+ setBars(false);
+ }
+ }, [inDeciseconds]);
+
+ return (
+
+
+ ({sorted.length} / {subsystems.length})
+
+
+
+
+
+ }
+ >
+
+ {sorted.map((subsystem) => (
+
+ ))}
+
+
+ );
+}
diff --git a/tgui/packages/tgui/interfaces/ControllerOverview/constants.ts b/tgui/packages/tgui/interfaces/ControllerOverview/constants.ts
new file mode 100644
index 0000000000..62953cc7dd
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ControllerOverview/constants.ts
@@ -0,0 +1,48 @@
+type SortType = {
+ label: string;
+ propName: string;
+ inDeciseconds: boolean;
+};
+
+export const SORTING_TYPES: readonly SortType[] = [
+ {
+ label: 'Alphabetical',
+ propName: 'name',
+ inDeciseconds: false,
+ },
+ {
+ label: 'Cost',
+ propName: 'cost_ms',
+ inDeciseconds: true,
+ },
+ {
+ label: 'Init Order',
+ propName: 'init_order',
+ inDeciseconds: false,
+ },
+ {
+ label: 'Last Fire',
+ propName: 'last_fire',
+ inDeciseconds: false,
+ },
+ {
+ label: 'Next Fire',
+ propName: 'next_fire',
+ inDeciseconds: false,
+ },
+ {
+ label: 'Tick Usage',
+ propName: 'tick_usage',
+ inDeciseconds: true,
+ },
+ {
+ label: 'Avg Usage Per Tick',
+ propName: 'usage_per_tick',
+ inDeciseconds: true,
+ },
+ {
+ label: 'Tick Overrun',
+ propName: 'tick_overrun',
+ inDeciseconds: true,
+ },
+];
diff --git a/tgui/packages/tgui/interfaces/ControllerOverview/filters.ts b/tgui/packages/tgui/interfaces/ControllerOverview/filters.ts
new file mode 100644
index 0000000000..4df6f242dd
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ControllerOverview/filters.ts
@@ -0,0 +1,45 @@
+import { type SortType } from './types';
+
+export type FilterState = {
+ ascending: boolean;
+ inactive: boolean;
+ query: string;
+ smallValues: boolean;
+ sortType: SortType;
+};
+
+export enum FilterAction {
+ Ascending = 'SET_SORT_ASCENDING',
+ Inactive = 'SET_FILTER_INACTIVE',
+ SmallValues = 'SET_FILTER_SMALL_VALUES',
+ SortType = 'SET_SORT_TYPE',
+ Query = 'SET_FILTER_QUERY',
+ Update = 'UPDATE_FILTER',
+}
+
+type Action =
+ | { type: FilterAction.Ascending; payload: boolean }
+ | { type: FilterAction.Inactive; payload: boolean }
+ | { type: FilterAction.SmallValues; payload: boolean }
+ | { type: FilterAction.SortType; payload: SortType }
+ | { type: FilterAction.Query; payload: string }
+ | { type: FilterAction.Update; payload: Partial };
+
+export function filterReducer(state: FilterState, action: Action): FilterState {
+ switch (action.type) {
+ case FilterAction.Inactive:
+ return { ...state, inactive: action.payload };
+ case FilterAction.SmallValues:
+ return { ...state, smallValues: action.payload };
+ case FilterAction.Ascending:
+ return { ...state, ascending: action.payload };
+ case FilterAction.SortType:
+ return { ...state, sortType: action.payload };
+ case FilterAction.Query:
+ return { ...state, query: action.payload };
+ case FilterAction.Update:
+ return { ...state, ...action.payload };
+ default:
+ return state;
+ }
+}
diff --git a/tgui/packages/tgui/interfaces/ControllerOverview/index.tsx b/tgui/packages/tgui/interfaces/ControllerOverview/index.tsx
new file mode 100644
index 0000000000..3d5250df06
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ControllerOverview/index.tsx
@@ -0,0 +1,152 @@
+import { useReducer, useState } from 'react';
+import { Button, Dropdown, Input, Section, Stack } from 'tgui-core/components';
+
+import { Window } from '../../layouts';
+import { SORTING_TYPES } from './constants';
+import { FilterAction, filterReducer, type FilterState } from './filters';
+import { OverviewSection } from './OverviewSection';
+import { SubsystemDialog } from './SubsystemDialog';
+import { SubsystemViews } from './SubsystemViews';
+import { SortType, type SubsystemData } from './types';
+
+export function ControllerOverview(props) {
+ return (
+
+
+
+
+
+ );
+}
+
+export function ControllerContent(props) {
+ const [state, dispatch] = useReducer(filterReducer, {
+ ascending: true,
+ inactive: true,
+ query: '',
+ smallValues: false,
+ sortType: SortType.Name,
+ });
+
+ const [selected, setSelected] = useState();
+
+ const { label, inDeciseconds } =
+ SORTING_TYPES?.[state.sortType] || SORTING_TYPES[0];
+
+ function onSelectionHandler(value: string) {
+ const updates: Partial = {
+ sortType: SORTING_TYPES.findIndex((type) => type.label === value),
+ };
+
+ if (updates.sortType === undefined) return;
+
+ const { inDeciseconds } = SORTING_TYPES[updates.sortType];
+
+ updates.ascending = !inDeciseconds;
+ updates.smallValues = inDeciseconds;
+
+ dispatch({ type: FilterAction.Update, payload: updates });
+ }
+
+ return (
+
+ {selected && (
+ setSelected(undefined)}
+ subsystem={selected}
+ />
+ )}
+
+
+
+
+
+
+
+
+
+
+ dispatch({ type: FilterAction.Query, payload: value })
+ }
+ placeholder="By name"
+ value={state.query}
+ width="85%"
+ />
+
+
+
+
+
+
+
+
+
+
+ type.label)}
+ selected={label}
+ displayText={label}
+ onSelected={onSelectionHandler}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/tgui/packages/tgui/interfaces/ControllerOverview/types.ts b/tgui/packages/tgui/interfaces/ControllerOverview/types.ts
new file mode 100644
index 0000000000..4dac0552d3
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ControllerOverview/types.ts
@@ -0,0 +1,35 @@
+import { type BooleanLike } from 'tgui-core/react';
+
+export type SubsystemData = {
+ can_fire: BooleanLike;
+ cost_ms: number;
+ doesnt_fire: BooleanLike;
+ init_order: number;
+ initialization_failure_message: string | undefined;
+ initialized: BooleanLike;
+ last_fire: number;
+ name: string;
+ next_fire: number;
+ ref: string;
+ tick_overrun: number;
+ tick_usage: number;
+ usage_per_tick: number;
+};
+
+export type ControllerData = {
+ world_time: number;
+ fast_update: BooleanLike;
+ rolling_length: number;
+ map_cpu: number;
+ subsystems: SubsystemData[];
+};
+
+export enum SortType {
+ Name,
+ Cost,
+ InitOrder,
+ LastFire,
+ NextFire,
+ TickUsage,
+ TickOverrun,
+}
diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/ui.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/ui.tsx
index 51d97e51e1..bb82dfc575 100644
--- a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/ui.tsx
+++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/ui.tsx
@@ -96,3 +96,13 @@ export const tgui_say_width: FeatureNumeric = {
description: 'The width to show in the tgui say input.',
component: FeatureSliderInput,
};
+
+export const windowflashing: FeatureToggle = {
+ name: 'Enable window flashing',
+ category: 'UI',
+ description: `
+ When toggled, some important events will make your game icon flash on your
+ task tray.
+ `,
+ component: CheckboxInput,
+};
diff --git a/tools/ci/annotate_dm.sh b/tools/ci/annotate_dm.sh
new file mode 100644
index 0000000000..e43f930ba1
--- /dev/null
+++ b/tools/ci/annotate_dm.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+set -euo pipefail
+tools/bootstrap/python -m dm_annotator "$@"
diff --git a/tools/ci/annotate_od.sh b/tools/ci/annotate_od.sh
new file mode 100644
index 0000000000..1239090807
--- /dev/null
+++ b/tools/ci/annotate_od.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+set -euo pipefail
+tools/bootstrap/python -m od_annotator "$@"
diff --git a/tools/ci/check_changelogs.sh b/tools/ci/check_changelogs.sh
new file mode 100644
index 0000000000..112a843f52
--- /dev/null
+++ b/tools/ci/check_changelogs.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+set -euo pipefail
+
+md5sum -c - <<< "0c56937110d88f750a32d9075ddaab8b *html/changelogs/example.yml"
+python3 tools/ss13_genchangelog.py html/changelogs
diff --git a/tools/ci/check_genesis.sh b/tools/ci/check_genesis.sh
new file mode 100644
index 0000000000..08d064af17
--- /dev/null
+++ b/tools/ci/check_genesis.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+set -euo pipefail
+
+#ANSI Escape Codes for colors to increase contrast of errors
+RED="\033[0;31m"
+GREEN="\033[0;32m"
+BLUE="\033[0;34m"
+NC="\033[0m" # No Color
+
+if sha256sum -c tools/ci/genesis_call.dme.sha256sum ; then
+ echo -e "${GREEN}code/genesis_call.dme is unchanged.${NC}"
+else
+ echo -e "${RED}code/genesis_call.dme has been changed!${NC}"
+ echo -e "${BLUE}This is likely not intentional, please revert any changes made to it.${NC}"
+ echo -e "${BLUE}On the unlikely occurance that you ARE intentionally modifying that file, replace the contents of tools/ci/genesis_call.dme.sha256sum with the following:${NC}"
+
+ sha256sum tools/ci/genesis_call.dme.sha256sum
+
+ exit 1
+fi
diff --git a/tools/ci/validate_files.sh b/tools/ci/check_grep.sh
old mode 100755
new mode 100644
similarity index 95%
rename from tools/ci/validate_files.sh
rename to tools/ci/check_grep.sh
index 217896c1de..c259074071
--- a/tools/ci/validate_files.sh
+++ b/tools/ci/check_grep.sh
@@ -1,16 +1,16 @@
#!/bin/bash
set -euo pipefail
-# nb: must be bash to support shopt globstar
+#nb: must be bash to support shopt globstar
shopt -s globstar extglob
source dependencies.sh
-# ANSI Colors
-RED='\033[0;31m'
+#ANSI Escape Codes for colors to increase contrast of errors
+RED="\033[0;31m"
GREEN="\033[0;32m"
BLUE="\033[0;34m"
-NC='\033[0m' # No color
+NC="\033[0m" # No Color
FAILED=0
@@ -136,15 +136,6 @@ if $grep -i 'var/list/static/.*' $code_files; then
st=1
fi;
-part "changelog"
-#Checking for a change to html/changelogs/example.yml
-md5sum -c - <<< "0c56937110d88f750a32d9075ddaab8b *html/changelogs/example.yml"
-retVal=$?
-if [ $retVal -ne 0 ]; then
- echo -e "${RED}Do not modify the example.yml changelog file.${NC}"
- FAILED=1
-fi;
-
part "color macros"
#Checking for color macros
(num=`$grep -n '\\\\(red|blue|green|black|b|i[^mnc])' $code_files | wc -l`; echo "$num escapes (expecting ${MACRO_COUNT} or less)"; [ $num -le ${MACRO_COUNT} ])
diff --git a/tools/ci/check_misc.sh b/tools/ci/check_misc.sh
new file mode 100644
index 0000000000..4f22294d29
--- /dev/null
+++ b/tools/ci/check_misc.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+set -euo pipefail
+
+# This could be useful for misc files soon
diff --git a/tools/ci/compile_and_run.sh b/tools/ci/compile_and_run.sh
index 25e60bc5ac..3b449621ab 100755
--- a/tools/ci/compile_and_run.sh
+++ b/tools/ci/compile_and_run.sh
@@ -24,10 +24,10 @@ if [ $exitVal -gt 0 ]; then
exit 1
fi
-# If we're running, run
+# This variable is set in the CI scripts depending if we need to run the codebase or not.
+# Extra map tests for example have it set to false
if [ $RUN -eq 1 ];
then
- DreamDaemon $BASENAME.dmb -close -trusted -invisible -verbose -core 2>&1 | tee log.txt;
- grep "All Unit Tests Passed" log.txt || exit 1
- grep "Caught 0 Runtimes" log.txt || exit 1
+ DreamDaemon vorestation.dmb -close -invisible -trusted -verbose -params "log-directory=ci"
+ cat data/logs/ci/clean_run.lk
fi
diff --git a/tools/ci/genesis_call.dme.sha256sum b/tools/ci/genesis_call.dme.sha256sum
new file mode 100644
index 0000000000..7238dacd38
--- /dev/null
+++ b/tools/ci/genesis_call.dme.sha256sum
@@ -0,0 +1 @@
+393cbb0404a017c61777b178fe5612e033a396550e8633a51fd902cc9912b8b6 code/genesis_call.dme
diff --git a/tools/dm_annotator/__main__.py b/tools/dm_annotator/__main__.py
new file mode 100644
index 0000000000..4948fd0865
--- /dev/null
+++ b/tools/dm_annotator/__main__.py
@@ -0,0 +1,51 @@
+import sys
+import re
+import os.path as path
+
+# Usage: tools/bootstrap/python -m dm_annotator [filename]
+# If filename is not provided, stdin is checked instead
+
+def red(text):
+ return "\033[31m" + str(text) + "\033[0m"
+
+def green(text):
+ return "\033[32m" + str(text) + "\033[0m"
+
+def yellow(text):
+ return "\033[33m" + str(text) + "\033[0m"
+
+def annotate(raw_output):
+ # Remove ANSI escape codes
+ raw_output = re.sub(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]', '', raw_output)
+
+ print("::group::DreamChecker Output")
+ print(raw_output)
+ print("::endgroup::")
+
+ annotation_regex = r'(?P.*?), line (?P\d+), column (?P\d+):\s{1,2}(?Perror|warning): (?P.*)'
+ has_issues = False
+
+ print("DM Code Annotations:")
+ for annotation in re.finditer(annotation_regex, raw_output):
+ print(f"::{annotation['type']} file={annotation['filename']},line={annotation['line']},col={annotation['column']}::{annotation['message']}")
+ has_issues = True
+
+ if not has_issues:
+ print(green("No DM issues found"))
+
+def main():
+ if len(sys.argv) > 1:
+ if not path.exists(sys.argv[1]):
+ print(red(f"Error: Annotations file '{sys.argv[1]}' does not exist"))
+ sys.exit(1)
+ with open(sys.argv[1], 'r') as f:
+ annotate(f.read())
+ elif not sys.stdin.isatty():
+ annotate(sys.stdin.read())
+ else:
+ print(red("Error: No input provided"))
+ print("Usage: tools/bootstrap/python -m dm_annotator [filename]")
+ sys.exit(1)
+
+if __name__ == '__main__':
+ main()
diff --git a/tools/initToast/initToast.ps1 b/tools/initToast/initToast.ps1
new file mode 100644
index 0000000000..9b7714f3a2
--- /dev/null
+++ b/tools/initToast/initToast.ps1
@@ -0,0 +1,32 @@
+param([string]$name, [string]$icon, [Int32]$port=80)
+
+$hkcu = [Microsoft.Win32.RegistryKey]::OpenBaseKey('CurrentUser','default')
+
+$amuid_hkey = $hkcu.CreateSubKey('SOFTWARE\Classes\AppUserModelId\Tgstation.Tgstation', $true, [Microsoft.Win32.RegistryOptions]::Volatile)
+
+if ($name) { $amuid_hkey.SetValue('DisplayName', $name) }
+if ($icon) { $amuid_hkey.SetValue('IconUri', $icon) }
+
+[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] > $null
+$notifier = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('Tgstation.Tgstation')
+
+[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom, ContentType = WindowsRuntime] > $null
+$xml = New-Object Windows.Data.Xml.Dom.XmlDocument
+
+$xml.LoadXml(@"
+
+
+
+ Initialization Complete
+
+
+
+
+
+
+"@)
+
+[Windows.UI.Notifications.ToastNotification, Windows.UI.Notifications, ContentType = WindowsRuntime] > $null
+$toast = New-Object Windows.UI.Notifications.ToastNotification $xml
+
+$notifier.Show($toast)
diff --git a/tools/od_annotator/__main__.py b/tools/od_annotator/__main__.py
new file mode 100644
index 0000000000..357adccfe9
--- /dev/null
+++ b/tools/od_annotator/__main__.py
@@ -0,0 +1,50 @@
+import sys
+import re
+
+def green(text):
+ return "\033[32m" + str(text) + "\033[0m"
+
+def red(text):
+ return "\033[31m" + str(text) + "\033[0m"
+
+def annotate(raw_output):
+ # Remove ANSI escape codes
+ raw_output = re.sub(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]', '', raw_output)
+
+ print("::group::OpenDream Output")
+ print(raw_output)
+ print("::endgroup::")
+
+ annotation_regex = r'((?PError|Warning) (?POD(?P\d{4})) at (?P(?P.+):(?P\d+):(?P\d+)|): (?P.+))'
+ failures_detected = False
+ expected_failure_case_detected = False # this is just here so this script breaks if we forget to set it to True when we expect a failure. remove this when we have handled the expected failure
+
+ print("OpenDream Code Annotations:")
+ for annotation in re.finditer(annotation_regex, raw_output):
+ message = annotation['message']
+ if message == "Unimplemented proc & var warnings are currently suppressed": # this happens every single run, it's important to know about it but we don't need to throw an error
+ message += " (This is expected and can be ignored)" # also there's no location for it to annotate to since it's an failure.
+ expected_failure_case_detected = True
+
+ if annotation['type'] == "Error":
+ failures_detected = True
+
+ error_string = f"{annotation['errorcode']}: {message}"
+
+ if annotation['location'] == "":
+ print(f"::{annotation['type']} file=,line=,col=::{error_string}")
+ else:
+ print(f"::{annotation['type']} file={annotation['filename']},line={annotation['line']},col={annotation['column']}::{error_string}")
+
+ if failures_detected:
+ sys.exit(1)
+ return
+
+ if not expected_failure_case_detected:
+ print(red("Failed to detect the expected failure case! If you have recently changed how we work with OpenDream Pragmas, please fix the od_annotator script!"))
+ sys.exit(1)
+ return
+
+ print(green("No OpenDream issues found!"))
+
+annotate(sys.stdin.read())
diff --git a/tools/ticked_file_enforcement/schemas/unit_tests.json b/tools/ticked_file_enforcement/schemas/unit_tests.json
new file mode 100644
index 0000000000..fad62267b7
--- /dev/null
+++ b/tools/ticked_file_enforcement/schemas/unit_tests.json
@@ -0,0 +1,7 @@
+{
+ "file": "code/modules/unit_tests/_unit_tests.dm",
+ "scannable_directory": "code/modules/unit_tests/",
+ "subdirectories": false,
+ "excluded_files": ["find_reference_sanity.dm"],
+ "forbidden_includes": []
+}
diff --git a/tools/ticked_file_enforcement/schemas/vorestation_dme.json b/tools/ticked_file_enforcement/schemas/vorestation_dme.json
new file mode 100644
index 0000000000..f381e6056d
--- /dev/null
+++ b/tools/ticked_file_enforcement/schemas/vorestation_dme.json
@@ -0,0 +1,10 @@
+{
+ "file": "vorestation.dme",
+ "scannable_directory": "code/",
+ "subdirectories": true,
+ "excluded_files": [],
+ "forbidden_includes": [
+ "code/modules/tgs/**/*.dm",
+ "code/modules/unit_tests/[!_]*.dm"
+ ]
+}
diff --git a/tools/ticked_file_enforcement/ticked_file_enforcement.py b/tools/ticked_file_enforcement/ticked_file_enforcement.py
new file mode 100644
index 0000000000..86c399c735
--- /dev/null
+++ b/tools/ticked_file_enforcement/ticked_file_enforcement.py
@@ -0,0 +1,156 @@
+import codecs
+import fnmatch
+import functools
+import glob
+import json
+import os
+import sys
+
+# simple way to check if we're running on github actions, or on a local machine
+on_github = os.getenv("GITHUB_ACTIONS") == "true"
+
+def green(text):
+ return "\033[32m" + str(text) + "\033[0m"
+
+def red(text):
+ return "\033[31m" + str(text) + "\033[0m"
+
+def blue(text):
+ return "\033[34m" + str(text) + "\033[0m"
+
+schema = json.load(sys.stdin)
+file_reference = schema["file"]
+file_reference_basename = os.path.basename(file_reference)
+scannable_directory = schema["scannable_directory"]
+subdirectories = schema["subdirectories"]
+FORBIDDEN_INCLUDES = schema["forbidden_includes"]
+excluded_files = schema["excluded_files"]
+
+def post_error(string):
+ print(red(f"Ticked File Enforcement [{file_reference}]: " + string))
+ if on_github:
+ print(f"::error file={file_reference},line=1,title=Ticked File Enforcement::{string}")
+
+for excluded_file in excluded_files:
+ full_file_path = scannable_directory + excluded_file
+ if not os.path.isfile(full_file_path):
+ post_error(f"Excluded file {full_file_path} does not exist, please remove it!")
+ sys.exit(1)
+
+file_extensions = ("dm", "dmf")
+
+reading = False
+lines = []
+total = 0
+
+with open(file_reference, 'r') as file:
+ for line in file:
+ total += 1
+ line = line.strip()
+
+ if line == "// BEGIN_INCLUDE":
+ reading = True
+ continue
+ elif line == "// END_INCLUDE":
+ break
+ elif not reading:
+ continue
+
+ lines.append(line)
+
+offset = total - len(lines)
+print(blue(f"Ticked File Enforcement: {offset} lines were ignored in output for [{file_reference}]."))
+fail_no_include = False
+
+scannable_files = []
+for file_extension in file_extensions:
+ compiled_directory = f"{scannable_directory}/**/*.{file_extension}"
+ scannable_files += glob.glob(compiled_directory, recursive=True)
+
+if len(scannable_files) == 0:
+ post_error(f"No files were found in {scannable_directory}. Ticked File Enforcement has failed!")
+ sys.exit(1)
+
+for code_file in scannable_files:
+ dm_path = ""
+
+ if subdirectories is True:
+ dm_path = code_file.replace('/', '\\')
+ else:
+ dm_path = os.path.basename(code_file)
+
+ included = f"#include \"{dm_path}\"" in lines
+
+ forbid_include = False
+ for forbidable in FORBIDDEN_INCLUDES:
+ if not fnmatch.fnmatch(code_file, forbidable):
+ continue
+
+ forbid_include = True
+
+ if included:
+ post_error(f"{dm_path} should NOT be included.")
+ fail_no_include = True
+
+ if forbid_include:
+ continue
+
+ if not included:
+ if(dm_path == file_reference_basename):
+ continue
+
+ if(dm_path in excluded_files):
+ continue
+
+ post_error(f"Missing include for {dm_path}.")
+ fail_no_include = True
+
+if fail_no_include:
+ sys.exit(1)
+
+def compare_lines(a, b):
+ # Remove initial include as well as the final quotation mark
+ a = a[len("#include \""):-1].lower()
+ b = b[len("#include \""):-1].lower()
+
+ split_by_period = a.split('.')
+ a_suffix = ""
+ if len(split_by_period) >= 2:
+ a_suffix = split_by_period[len(split_by_period) - 1]
+ split_by_period = b.split('.')
+ b_suffix = ""
+ if len(split_by_period) >= 2:
+ b_suffix = split_by_period[len(split_by_period) - 1]
+
+ a_segments = a.split('\\')
+ b_segments = b.split('\\')
+
+ for (a_segment, b_segment) in zip(a_segments, b_segments):
+ a_is_file = a_segment.endswith(file_extensions)
+ b_is_file = b_segment.endswith(file_extensions)
+
+ # code\something.dm will ALWAYS come before code\directory\something.dm
+ if a_is_file and not b_is_file:
+ return -1
+
+ if b_is_file and not a_is_file:
+ return 1
+
+ # interface\something.dm will ALWAYS come after code\something.dm
+ if a_segment != b_segment:
+ # if we're at the end of a compare, then this is about the file name
+ # files with longer suffixes come after ones with shorter ones
+ if a_suffix != b_suffix:
+ return (a_suffix > b_suffix) - (a_suffix < b_suffix)
+ return (a_segment > b_segment) - (a_segment < b_segment)
+
+ print(f"Two lines were exactly the same ({a} vs. {b})")
+ sys.exit(1)
+
+sorted_lines = sorted(lines, key = functools.cmp_to_key(compare_lines))
+for (index, line) in enumerate(lines):
+ if sorted_lines[index] != line:
+ post_error(f"The include at line {index + offset} is out of order ({line}, expected {sorted_lines[index]})")
+ sys.exit(1)
+
+print(green(f"Ticked File Enforcement: [{file_reference}] All includes (for {len(scannable_files)} scanned files) are in order!"))
diff --git a/vorestation.dme b/vorestation.dme
index 6e2a5a9da9..334a9ac1eb 100644
--- a/vorestation.dme
+++ b/vorestation.dme
@@ -1,14 +1,22 @@
-// DM Environment file for baystation12.dme.
+// DM Environment file for tgstation.dme.
// All manual changes should be made outside the BEGIN_ and END_ blocks.
- // New source code should be placed in .dm files: choose File/New --> Code File.
+// New source code should be placed in .dm files: choose File/New --> Code File.
+
+// This should always be the VERY FIRST THING. No exceptions. Read the file for more info, but basically, this needs to be here so that we can ensure specific code is always the very first thing to execute.
+#include "code\genesis_call.dme"
+// Again, DO NOT MOVE IT OR ALLOW ANY OTHER #include TO PRECEDE THE ONE ABOVE.
+
// BEGIN_INTERNALS
// END_INTERNALS
+
// BEGIN_FILE_DIR
#define FILE_DIR .
// END_FILE_DIR
+
// BEGIN_PREFERENCES
#define DEBUG
// END_PREFERENCES
+
// BEGIN_INCLUDE
#include "code\__byond_version_compat.dm"
#include "code\__odlint.dm"
@@ -16,7 +24,6 @@
#include "code\_macros.dm"
#include "code\coalesce.dm"
#include "code\global_init.dm"
-#include "code\names.dm"
#include "code\world.dm"
#include "code\__defines\__globals.dm"
#include "code\__defines\_atoms.dm"
@@ -76,6 +83,7 @@
#include "code\__defines\experisci.dm"
#include "code\__defines\faction.dm"
#include "code\__defines\flags.dm"
+#include "code\__defines\font_awesome_icons.dm"
#include "code\__defines\fonts.dm"
#include "code\__defines\footsteps.dm"
#include "code\__defines\gamemode.dm"
@@ -171,10 +179,10 @@
#include "code\__defines\tgui.dm"
#include "code\__defines\time.dm"
#include "code\__defines\tools.dm"
+#include "code\__defines\tracy.dm"
#include "code\__defines\traits.dm"
#include "code\__defines\turfs.dm"
#include "code\__defines\typeids.dm"
-#include "code\__defines\unit_tests.dm"
#include "code\__defines\update_icons.dm"
#include "code\__defines\var_copy.dm"
#include "code\__defines\vchatlog.dm"
@@ -220,15 +228,18 @@
#include "code\_global_vars\logging.dm"
#include "code\_global_vars\misc.dm"
#include "code\_global_vars\mobs.dm"
+#include "code\_global_vars\radio.dm"
#include "code\_global_vars\silo.dm"
#include "code\_global_vars\tgui.dm"
#include "code\_global_vars\time_vars.dm"
#include "code\_global_vars\typecache.dm"
#include "code\_global_vars\lists\flavor_misc.dm"
+#include "code\_global_vars\lists\icons.dm"
#include "code\_global_vars\lists\logging.dm"
#include "code\_global_vars\lists\mapping.dm"
#include "code\_global_vars\lists\misc.dm"
#include "code\_global_vars\lists\mob.dm"
+#include "code\_global_vars\lists\names.dm"
#include "code\_global_vars\lists\species.dm"
#include "code\_global_vars\traits\_traits.dm"
#include "code\_helpers\_global_objects.dm"
@@ -257,6 +268,7 @@
#include "code\_helpers\nameof.dm"
#include "code\_helpers\names.dm"
#include "code\_helpers\refinery.dm"
+#include "code\_helpers\roundend.dm"
#include "code\_helpers\sanitize_values.dm"
#include "code\_helpers\screen_objs.dm"
#include "code\_helpers\shell.dm"
@@ -374,7 +386,6 @@
#include "code\ATMOSPHERICS\pipes\vent.dm"
#include "code\controllers\admin.dm"
#include "code\controllers\autotransfer.dm"
-#include "code\controllers\communications.dm"
#include "code\controllers\controller.dm"
#include "code\controllers\emergency_shuttle_controller.dm"
#include "code\controllers\failsafe.dm"
@@ -408,6 +419,7 @@
#include "code\controllers\subsystems\chat.dm"
#include "code\controllers\subsystems\chemistry.dm"
#include "code\controllers\subsystems\circuits.dm"
+#include "code\controllers\subsystems\communications.dm"
#include "code\controllers\subsystems\dbcore.dm"
#include "code\controllers\subsystems\dcs.dm"
#include "code\controllers\subsystems\events.dm"
@@ -435,6 +447,7 @@
#include "code\controllers\subsystems\plants.dm"
#include "code\controllers\subsystems\player_tips.dm"
#include "code\controllers\subsystems\pois.dm"
+#include "code\controllers\subsystems\profiler.dm"
#include "code\controllers\subsystems\radiation.dm"
#include "code\controllers\subsystems\research.dm"
#include "code\controllers\subsystems\robot_sprites.dm"
@@ -524,6 +537,9 @@
#include "code\datums\autolathe\tools.dm"
#include "code\datums\autolathe\tools_vr.dm"
#include "code\datums\changelog\changelog.dm"
+#include "code\datums\cinematics\_cinematic.dm"
+#include "code\datums\cinematics\malf_doomsday.dm"
+#include "code\datums\cinematics\nuke_cinematics.dm"
#include "code\datums\components\_component.dm"
#include "code\datums\components\connect_loc_behalf.dm"
#include "code\datums\components\connect_mob_behalf.dm"
@@ -836,19 +852,13 @@
#include "code\defines\procs\AStar.dm"
#include "code\defines\procs\radio.dm"
#include "code\defines\procs\statistics.dm"
-#include "code\game\atoms.dm"
-#include "code\game\atoms_init.dm"
#include "code\game\atoms_movable.dm"
#include "code\game\base_turf.dm"
#include "code\game\birthday.dm"
#include "code\game\periodic_news.dm"
-#include "code\game\response_team.dm"
-#include "code\game\response_team_vr.dm"
#include "code\game\shuttle_engines.dm"
#include "code\game\skincmd.dm"
#include "code\game\sound.dm"
-#include "code\game\trader_visit.dm"
-#include "code\game\trader_visit_vr.dm"
#include "code\game\world.dm"
#include "code\game\antagonist\_antagonist_setup.dm"
#include "code\game\antagonist\antagonist.dm"
@@ -896,7 +906,9 @@
#include "code\game\area\Space Station 13 areas.dm"
#include "code\game\area\Space Station 13 areas_vr.dm"
#include "code\game\area\ss13_deprecated_areas.dm"
+#include "code\game\atom\_atom.dm"
#include "code\game\atom\atom_vv.dm"
+#include "code\game\atom\atoms_initializing_EXPENSIVE.dm"
#include "code\game\dna\dna2.dm"
#include "code\game\dna\dna2_domutcheck.dm"
#include "code\game\dna\dna2_helpers.dm"
@@ -1351,6 +1363,7 @@
#include "code\game\objects\structures.dm"
#include "code\game\objects\stumble_into_vr.dm"
#include "code\game\objects\trash_eating.dm"
+#include "code\game\objects\unit_testing.dm"
#include "code\game\objects\weapons.dm"
#include "code\game\objects\effects\bump_teleporter.dm"
#include "code\game\objects\effects\client_only_image.dm"
@@ -2025,6 +2038,7 @@
#include "code\modules\admin\verbs\smite.dm"
#include "code\modules\admin\verbs\special_verbs.dm"
#include "code\modules\admin\verbs\striketeam.dm"
+#include "code\modules\admin\verbs\trader.dm"
#include "code\modules\admin\verbs\tgui_verbs.dm"
#include "code\modules\admin\verbs\tripAI.dm"
#include "code\modules\admin\verbs\SDQL2\SDQL_2.dm"
@@ -2471,6 +2485,8 @@
#include "code\modules\compass\compass_waypoint.dm"
#include "code\modules\compass\~compass.dm"
#include "code\modules\customitems\item_spawning.dm"
+#include "code\modules\debugging\debugger.dm"
+#include "code\modules\debugging\tracy.dm"
#include "code\modules\detectivework\footprints.dm"
#include "code\modules\detectivework\forensics.dm"
#include "code\modules\detectivework\microscope\dnascanner.dm"
@@ -4481,6 +4497,7 @@
#include "code\modules\turbolift\turbolift_floor.dm"
#include "code\modules\turbolift\turbolift_map.dm"
#include "code\modules\turbolift\turbolift_turfs.dm"
+#include "code\modules\unit_tests\_unit_tests.dm"
#include "code\modules\vehicles\bike.dm"
#include "code\modules\vehicles\boat.dm"
#include "code\modules\vehicles\cargo_train.dm"
@@ -4632,33 +4649,6 @@
#include "code\modules\xenobio\machinery\processor.dm"
#include "code\modules\xgm\xgm_gas_data.dm"
#include "code\modules\xgm\xgm_gas_mixture.dm"
-#include "code\unit_tests\clothing_tests.dm"
-#include "code\unit_tests\cosmetic_tests.dm"
-#include "code\unit_tests\decl_tests.dm"
-#include "code\unit_tests\disease_tests.dm"
-#include "code\unit_tests\genetics_tests.dm"
-#include "code\unit_tests\language_tests.dm"
-#include "code\unit_tests\loadout_tests.dm"
-#include "code\unit_tests\map_tests.dm"
-#include "code\unit_tests\material_tests.dm"
-#include "code\unit_tests\mob_tests.dm"
-#include "code\unit_tests\poster_tests.dm"
-#include "code\unit_tests\reagent_tests.dm"
-#include "code\unit_tests\recipe_tests.dm"
-#include "code\unit_tests\recycler_vendor_tests.dm"
-#include "code\unit_tests\robot_tests.dm"
-#include "code\unit_tests\sqlite_tests.dm"
-#include "code\unit_tests\subsystem_tests.dm"
-#include "code\unit_tests\trait_tests.dm"
-#include "code\unit_tests\unit_test.dm"
-#include "code\unit_tests\unit_test_vr.dm"
-#include "code\unit_tests\vore_tests_vr.dm"
-#include "code\unit_tests\zas_tests.dm"
-#include "code\unit_tests\integrated_circuits\arithmetic.dm"
-#include "code\unit_tests\integrated_circuits\circuits.dm"
-#include "code\unit_tests\integrated_circuits\converter.dm"
-#include "code\unit_tests\integrated_circuits\logic.dm"
-#include "code\unit_tests\integrated_circuits\trig.dm"
#include "code\ZAS\Airflow.dm"
#include "code\ZAS\Atom.dm"
#include "code\ZAS\Connection.dm"