From 110edaa153a6222de47fea37956ecee1fdb84269 Mon Sep 17 00:00:00 2001
From: Gandalf <9026500+Gandalf2k15@users.noreply.github.com>
Date: Sat, 25 Jun 2022 01:01:45 +0100
Subject: [PATCH] Security Level Datums (#67949)
---
code/__DEFINES/subsystems.dm | 1 +
code/controllers/subsystem/nightshift.dm | 2 +-
code/controllers/subsystem/security_level.dm | 104 +++++++++++++++++-
code/controllers/subsystem/shuttle.dm | 6 +-
code/datums/world_topic.dm | 2 +-
code/game/gamemodes/dynamic/dynamic.dm | 4 +-
.../game/machinery/computer/communications.dm | 8 +-
code/game/machinery/defibrillator_mount.dm | 4 +-
code/game/machinery/doors/door.dm | 4 +-
code/game/machinery/firealarm.dm | 6 +-
code/game/world.dm | 2 +-
code/modules/admin/verbs/adminevents.dm | 15 ++-
code/modules/antagonists/blob/overmind.dm | 2 +-
code/modules/antagonists/cult/cult_items.dm | 2 +-
.../equipment/nuclear_bomb/_nuclear_bomb.dm | 8 +-
.../traitor/equipment/Malf_Modules.dm | 4 +-
code/modules/power/singularity/narsie.dm | 4 +-
.../supermatter/supermatter_delamination.dm | 2 +-
.../security_levels/keycard_authentication.dm | 4 +-
.../security_levels/security_level_datums.dm | 82 ++++++++++++++
.../security_levels/security_levels.dm | 82 --------------
code/modules/shuttle/emergency.dm | 6 +-
code/modules/unit_tests/_unit_tests.dm | 1 +
code/modules/unit_tests/security_levels.dm | 16 +++
tgstation.dme | 2 +-
25 files changed, 243 insertions(+), 130 deletions(-)
create mode 100644 code/modules/security_levels/security_level_datums.dm
delete mode 100644 code/modules/security_levels/security_levels.dm
create mode 100644 code/modules/unit_tests/security_levels.dm
diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm
index df95c8d4eaa..453b0dc2047 100644
--- a/code/__DEFINES/subsystems.dm
+++ b/code/__DEFINES/subsystems.dm
@@ -119,6 +119,7 @@
#define INIT_ORDER_INSTRUMENTS 82
#define INIT_ORDER_GREYSCALE 81
#define INIT_ORDER_VIS 80
+#define INIT_ORDER_SECURITY_LEVEL 79 // We need to load before events so that it has a security level to choose from.
#define INIT_ORDER_DISCORD 78
#define INIT_ORDER_ACHIEVEMENTS 77
#define INIT_ORDER_STATION 74 //This is high priority because it manipulates a lot of the subsystems that will initialize after it.
diff --git a/code/controllers/subsystem/nightshift.dm b/code/controllers/subsystem/nightshift.dm
index 1e8daad6e61..f420185ec5e 100644
--- a/code/controllers/subsystem/nightshift.dm
+++ b/code/controllers/subsystem/nightshift.dm
@@ -27,7 +27,7 @@ SUBSYSTEM_DEF(nightshift)
priority_announce(message, sound='sound/misc/notice2.ogg', sender_override="Automated Lighting System Announcement")
/datum/controller/subsystem/nightshift/proc/check_nightshift()
- var/emergency = SSsecurity_level.current_level >= SEC_LEVEL_RED
+ var/emergency = SSsecurity_level.get_current_level_as_number() >= SEC_LEVEL_RED
var/announcing = TRUE
var/time = station_time()
var/night_time = (time < nightshift_end_time) || (time > nightshift_start_time)
diff --git a/code/controllers/subsystem/security_level.dm b/code/controllers/subsystem/security_level.dm
index e3168dee0c3..94dc351bdec 100644
--- a/code/controllers/subsystem/security_level.dm
+++ b/code/controllers/subsystem/security_level.dm
@@ -1,17 +1,109 @@
SUBSYSTEM_DEF(security_level)
name = "Security Level"
- flags = SS_NO_FIRE
+ can_fire = FALSE // We will control when we fire in this subsystem
+ init_order = INIT_ORDER_SECURITY_LEVEL
/// Currently set security level
- var/current_level = SEC_LEVEL_GREEN
+ var/datum/security_level/current_security_level
+ /// A list of initialised security level datums.
+ var/list/available_levels = list()
+
+/datum/controller/subsystem/security_level/Initialize(start_timeofday)
+ . = ..()
+ for(var/iterating_security_level_type in subtypesof(/datum/security_level))
+ var/datum/security_level/new_security_level = new iterating_security_level_type
+ available_levels[new_security_level.name] = new_security_level
+ current_security_level = available_levels[number_level_to_text(SEC_LEVEL_GREEN)]
+
+/datum/controller/subsystem/security_level/fire(resumed)
+ if(!current_security_level.looping_sound) // No sound? No play.
+ can_fire = FALSE
+ return
+ sound_to_playing_players(current_security_level.looping_sound)
+
/**
* Sets a new security level as our current level
*
+ * This is how everything should change the security level.
+ *
* Arguments:
- * * new_level The new security level that will become our current level
+ * * new_level - The new security level that will become our current level
*/
/datum/controller/subsystem/security_level/proc/set_level(new_level)
- SSsecurity_level.current_level = new_level
- SEND_SIGNAL(src, COMSIG_SECURITY_LEVEL_CHANGED, new_level)
+ new_level = istext(new_level) ? new_level : number_level_to_text(new_level)
+ if(new_level == current_security_level.name) // If we are already at the desired level, do nothing
+ return
+
+ var/datum/security_level/selected_level = available_levels[new_level]
+
+ if(!selected_level)
+ CRASH("set_level was called with an invalid security level([new_level])")
+
+ announce_security_level(selected_level) // We want to announce BEFORE updating to the new level
+
+ var/old_shuttle_call_time_mod = current_security_level.shuttle_call_time_mod // Need this before we set the new one
+
+ SSsecurity_level.current_security_level = selected_level
+
+ if(selected_level.looping_sound)
+ wait = selected_level.looping_sound_interval
+ can_fire = TRUE
+ else
+ can_fire = FALSE
+
+ if(SSshuttle.emergency.mode == SHUTTLE_CALL || SSshuttle.emergency.mode == SHUTTLE_RECALL) // By god this is absolutely shit
+ old_shuttle_call_time_mod = 1 / old_shuttle_call_time_mod
+ SSshuttle.emergency.modTimer(old_shuttle_call_time_mod)
+ SSshuttle.emergency.modTimer(selected_level.shuttle_call_time_mod)
+
+ SEND_SIGNAL(src, COMSIG_SECURITY_LEVEL_CHANGED, selected_level.number_level)
SSnightshift.check_nightshift()
- SSblackbox.record_feedback("tally", "security_level_changes", 1, get_security_level())
+ SSblackbox.record_feedback("tally", "security_level_changes", 1, selected_level.name)
+
+/**
+ * Handles announcements of the newly set security level
+ *
+ * Arguments:
+ * * selected_level - The new security level that has been set
+ */
+/datum/controller/subsystem/security_level/proc/announce_security_level(datum/security_level/selected_level)
+ if(selected_level.number_level > current_security_level.number_level) // We are elevating to this level.
+ minor_announce(selected_level.elevating_to_announcemnt, "Attention! Security level elevated to [selected_level.name]:")
+ else // Going down
+ minor_announce(selected_level.lowering_to_announcement, "Attention! Security level lowered to [selected_level.name]:")
+ if(selected_level.sound)
+ sound_to_playing_players(selected_level.sound)
+
+/**
+ * Returns the current security level as a number
+ */
+/datum/controller/subsystem/security_level/proc/get_current_level_as_number()
+ return current_security_level.number_level
+
+/**
+ * Returns the current security level as text
+ */
+/datum/controller/subsystem/security_level/proc/get_current_level_as_text()
+ return current_security_level.name
+
+/**
+ * Converts a text security level to a number
+ *
+ * Arguments:
+ * * level - The text security level to convert
+ */
+/datum/controller/subsystem/security_level/proc/text_level_to_number(text_level)
+ var/datum/security_level/selected_level = available_levels[text_level]
+ return selected_level.number_level
+
+/**
+ * Converts a number security level to a text
+ *
+ * Arguments:
+ * * level - The number security level to convert
+ */
+/datum/controller/subsystem/security_level/proc/number_level_to_text(number_level)
+ for(var/iterating_level_text in available_levels)
+ var/datum/security_level/iterating_security_level = available_levels[iterating_level_text]
+ if(iterating_security_level.number_level == number_level)
+ return iterating_security_level.name
diff --git a/code/controllers/subsystem/shuttle.dm b/code/controllers/subsystem/shuttle.dm
index 084b0aa60ea..390de70f33c 100644
--- a/code/controllers/subsystem/shuttle.dm
+++ b/code/controllers/subsystem/shuttle.dm
@@ -307,13 +307,13 @@ SUBSYSTEM_DEF(shuttle)
call_reason = trim(html_encode(call_reason))
- if(length(call_reason) < CALL_SHUTTLE_REASON_LENGTH && seclevel2num(get_security_level()) > SEC_LEVEL_GREEN)
+ if(length(call_reason) < CALL_SHUTTLE_REASON_LENGTH && SSsecurity_level.get_current_level_as_number() > SEC_LEVEL_GREEN)
to_chat(user, span_alert("You must provide a reason."))
return
var/area/signal_origin = get_area(user)
var/emergency_reason = "\nNature of emergency:\n\n[call_reason]"
- var/security_num = seclevel2num(get_security_level())
+ var/security_num = SSsecurity_level.get_current_level_as_number()
switch(security_num)
if(SEC_LEVEL_RED,SEC_LEVEL_DELTA)
emergency.request(null, signal_origin, html_decode(emergency_reason), 1) //There is a serious threat we gotta move no time to give them five minutes.
@@ -376,7 +376,7 @@ SUBSYSTEM_DEF(shuttle)
/datum/controller/subsystem/shuttle/proc/canRecall()
if(!emergency || emergency.mode != SHUTTLE_CALL || admin_emergency_no_recall || emergency_no_recall)
return
- var/security_num = seclevel2num(get_security_level())
+ var/security_num = SSsecurity_level.get_current_level_as_number()
switch(security_num)
if(SEC_LEVEL_GREEN)
if(emergency.timeLeft(1) < emergency_call_time)
diff --git a/code/datums/world_topic.dm b/code/datums/world_topic.dm
index b6e96dd4b16..bb22827a8e5 100644
--- a/code/datums/world_topic.dm
+++ b/code/datums/world_topic.dm
@@ -213,7 +213,7 @@
if(key_valid)
.["active_players"] = get_active_player_count()
- .["security_level"] = get_security_level()
+ .["security_level"] = SSsecurity_level.get_current_level_as_text()
.["round_duration"] = SSticker ? round((world.time-SSticker.round_start_time)/10) : 0
// Amount of world's ticks in seconds, useful for calculating round duration
diff --git a/code/game/gamemodes/dynamic/dynamic.dm b/code/game/gamemodes/dynamic/dynamic.dm
index f3f74152bd4..4e7b22e7ef5 100644
--- a/code/game/gamemodes/dynamic/dynamic.dm
+++ b/code/game/gamemodes/dynamic/dynamic.dm
@@ -340,8 +340,8 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
priority_announce("Thanks to the tireless efforts of our security and intelligence divisions, there are currently no credible threats to [station_name()]. All station construction projects have been authorized. Have a secure shift!", "Security Report", SSstation.announcer.get_rand_report_sound())
else
priority_announce("A summary has been copied and printed to all communications consoles.", "Security level elevated.", ANNOUNCER_INTERCEPT)
- if(SSsecurity_level.current_level < SEC_LEVEL_BLUE)
- set_security_level(SEC_LEVEL_BLUE)
+ if(SSsecurity_level.get_current_level_as_number() < SEC_LEVEL_BLUE)
+ SSsecurity_level.set_level(SEC_LEVEL_BLUE)
/datum/game_mode/dynamic/proc/show_threatlog(mob/admin)
if(!SSticker.HasRoundStarted())
diff --git a/code/game/machinery/computer/communications.dm b/code/game/machinery/computer/communications.dm
index 9e7b15eff08..9e474cfef41 100755
--- a/code/game/machinery/computer/communications.dm
+++ b/code/game/machinery/computer/communications.dm
@@ -197,13 +197,13 @@
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE)
return
- var/new_sec_level = seclevel2num(params["newSecurityLevel"])
+ var/new_sec_level = SSsecurity_level.text_level_to_number(params["newSecurityLevel"])
if (new_sec_level != SEC_LEVEL_GREEN && new_sec_level != SEC_LEVEL_BLUE)
return
- if (SSsecurity_level.current_level == new_sec_level)
+ if (SSsecurity_level.get_current_level_as_number() == new_sec_level)
return
- set_security_level(new_sec_level)
+ SSsecurity_level.set_level(new_sec_level)
to_chat(usr, span_notice("Authorization confirmed. Modifying security level."))
playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE)
@@ -517,7 +517,7 @@
data["shuttleCalled"] = FALSE
data["shuttleLastCalled"] = FALSE
data["aprilFools"] = SSevents.holidays && SSevents.holidays[APRIL_FOOLS]
- data["alertLevel"] = get_security_level()
+ data["alertLevel"] = SSsecurity_level.get_current_level_as_text()
data["authorizeName"] = authorize_name
data["canLogOut"] = !issilicon(user)
data["shuttleCanEvacOrFailReason"] = SSshuttle.canEvac(user)
diff --git a/code/game/machinery/defibrillator_mount.dm b/code/game/machinery/defibrillator_mount.dm
index 2b9851033e0..648e5ae15d8 100644
--- a/code/game/machinery/defibrillator_mount.dm
+++ b/code/game/machinery/defibrillator_mount.dm
@@ -41,7 +41,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/defibrillator_mount, 28)
. = ..()
if(defib)
. += span_notice("There is a defib unit hooked up. Alt-click to remove it.")
- if(SSsecurity_level.current_level >= SEC_LEVEL_RED)
+ if(SSsecurity_level.get_current_level_as_number() >= SEC_LEVEL_RED)
. += span_notice("Due to a security situation, its locking clamps can be toggled by swiping any ID.")
else
. += span_notice("Its locking clamps can be [clamps_locked ? "dis" : ""]engaged by swiping an ID with access.")
@@ -107,7 +107,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/defibrillator_mount, 28)
return
var/obj/item/card/id = I.GetID()
if(id)
- if(check_access(id) || SSsecurity_level.current_level >= SEC_LEVEL_RED) //anyone can toggle the clamps in red alert!
+ if(check_access(id) || SSsecurity_level.get_current_level_as_number() >= SEC_LEVEL_RED) //anyone can toggle the clamps in red alert!
if(!defib)
to_chat(user, span_warning("You can't engage the clamps on a defibrillator that isn't there."))
return
diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm
index 26015873b55..d06d431e268 100644
--- a/code/game/machinery/doors/door.dm
+++ b/code/game/machinery/doors/door.dm
@@ -68,7 +68,7 @@
/obj/machinery/door/examine(mob/user)
. = ..()
if(red_alert_access)
- if(SSsecurity_level.current_level >= SEC_LEVEL_RED)
+ if(SSsecurity_level.get_current_level_as_number() >= SEC_LEVEL_RED)
. += span_notice("Due to a security threat, its access requirements have been lifted!")
else
. += span_notice("In the event of a red alert, its access requirements will automatically lift.")
@@ -88,7 +88,7 @@
return CONTEXTUAL_SCREENTIP_SET
/obj/machinery/door/check_access_list(list/access_list)
- if(red_alert_access && SSsecurity_level.current_level >= SEC_LEVEL_RED)
+ if(red_alert_access && SSsecurity_level.get_current_level_as_number() >= SEC_LEVEL_RED)
return TRUE
return ..()
diff --git a/code/game/machinery/firealarm.dm b/code/game/machinery/firealarm.dm
index 7a5fad78b1b..31be707ea87 100644
--- a/code/game/machinery/firealarm.dm
+++ b/code/game/machinery/firealarm.dm
@@ -128,9 +128,9 @@
. += "fire_overlay"
if(is_station_level(z))
- . += "fire_[SSsecurity_level.current_level]"
- . += mutable_appearance(icon, "fire_[SSsecurity_level.current_level]")
- . += emissive_appearance(icon, "fire_[SSsecurity_level.current_level]", alpha = src.alpha)
+ . += "fire_[SSsecurity_level.get_current_level_as_number()]"
+ . += mutable_appearance(icon, "fire_[SSsecurity_level.get_current_level_as_number()]")
+ . += emissive_appearance(icon, "fire_[SSsecurity_level.get_current_level_as_number()]", alpha = src.alpha)
else
. += "fire_[SEC_LEVEL_GREEN]"
. += mutable_appearance(icon, "fire_[SEC_LEVEL_GREEN]")
diff --git a/code/game/world.dm b/code/game/world.dm
index 6c1bc850d3b..8097ca7c2a4 100644
--- a/code/game/world.dm
+++ b/code/game/world.dm
@@ -342,7 +342,7 @@ GLOBAL_VAR(restart_counter)
s += ": [jointext(features, ", ")]"
s += "
Round time: [gameTimestamp("hh:mm")]"
- s += "
Alert level: [capitalize(get_security_level())]"
+ s += "
Alert level: [capitalize(SSsecurity_level.get_current_level_as_text())]"
status = s
diff --git a/code/modules/admin/verbs/adminevents.dm b/code/modules/admin/verbs/adminevents.dm
index af32ef6a1d7..ba07eb7858b 100644
--- a/code/modules/admin/verbs/adminevents.dm
+++ b/code/modules/admin/verbs/adminevents.dm
@@ -268,13 +268,16 @@
if(!check_rights(R_ADMIN))
return
- var/level = input("Select security level to change to","Set Security Level") as null|anything in list("green","blue","red","delta")
- if(level)
- set_security_level(level)
+ var/level = tgui_input_list(usr, "Select Security Level:", "Set Security Level", SSsecurity_level.available_levels)
- log_admin("[key_name(usr)] changed the security level to [level]")
- message_admins("[key_name_admin(usr)] changed the security level to [level]")
- SSblackbox.record_feedback("tally", "admin_verb", 1, "Set Security Level [capitalize(level)]") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+ if(!level)
+ return
+
+ SSsecurity_level.set_level(level)
+
+ log_admin("[key_name(usr)] changed the security level to [level]")
+ message_admins("[key_name_admin(usr)] changed the security level to [level]")
+ SSblackbox.record_feedback("tally", "admin_verb", 1, "Set Security Level [capitalize(level)]") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/run_weather()
set category = "Admin.Events"
diff --git a/code/modules/antagonists/blob/overmind.dm b/code/modules/antagonists/blob/overmind.dm
index f2d28f9c6f1..4bb83647d9d 100644
--- a/code/modules/antagonists/blob/overmind.dm
+++ b/code/modules/antagonists/blob/overmind.dm
@@ -139,7 +139,7 @@ GLOBAL_LIST_EMPTY(blob_nodes)
else if(!victory_in_progress && (blobs_legit.len >= blobwincount))
victory_in_progress = TRUE
priority_announce("Biohazard has reached critical mass. Station loss is imminent.", "Biohazard Alert")
- set_security_level("delta")
+ SSsecurity_level.set_level(SEC_LEVEL_DELTA)
max_blob_points = INFINITY
blob_points = INFINITY
addtimer(CALLBACK(src, .proc/victory), 450)
diff --git a/code/modules/antagonists/cult/cult_items.dm b/code/modules/antagonists/cult/cult_items.dm
index cfa9bfd9cae..e36961a93f6 100644
--- a/code/modules/antagonists/cult/cult_items.dm
+++ b/code/modules/antagonists/cult/cult_items.dm
@@ -549,7 +549,7 @@ Striking a noncultist, however, will tear their flesh."}
if(SSshuttle.emergency.mode == SHUTTLE_CALL)
var/cursetime = 3 MINUTES
var/timer = SSshuttle.emergency.timeLeft(1) + cursetime
- var/security_num = seclevel2num(get_security_level())
+ var/security_num = SSsecurity_level.get_current_level_as_number()
var/set_coefficient = 1
if(totalcurses == 0)
diff --git a/code/modules/antagonists/nukeop/equipment/nuclear_bomb/_nuclear_bomb.dm b/code/modules/antagonists/nukeop/equipment/nuclear_bomb/_nuclear_bomb.dm
index e9e3a9fba2b..b90b56ec743 100644
--- a/code/modules/antagonists/nukeop/equipment/nuclear_bomb/_nuclear_bomb.dm
+++ b/code/modules/antagonists/nukeop/equipment/nuclear_bomb/_nuclear_bomb.dm
@@ -62,7 +62,7 @@ GLOBAL_VAR(station_nuke_source)
STOP_PROCESSING(SSobj, core)
update_appearance()
SSpoints_of_interest.make_point_of_interest(src)
- previous_level = get_security_level()
+ previous_level = SSsecurity_level.get_current_level_as_text()
/obj/machinery/nuclearbomb/Destroy()
safety = FALSE
@@ -451,7 +451,7 @@ GLOBAL_VAR(station_nuke_source)
message_admins("\The [src] was armed at [ADMIN_VERBOSEJMP(our_turf)] by [armer ? ADMIN_LOOKUPFLW(armer) : "an unknown user"].")
log_game("\The [src] was armed at [loc_name(our_turf)] by [armer ? key_name(armer) : "an unknown user"].")
- previous_level = get_security_level()
+ previous_level = SSsecurity_level.get_current_level_as_number()
detonation_timer = world.time + (timer_set * 10)
for(var/obj/item/pinpointer/nuke/syndicate/nuke_pointer in GLOB.pinpointer_list)
nuke_pointer.switch_mode_to(TRACK_INFILTRATOR)
@@ -459,7 +459,7 @@ GLOBAL_VAR(station_nuke_source)
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_NUKE_DEVICE_ARMED, src)
countdown.start()
- set_security_level("delta")
+ SSsecurity_level.set_level(SEC_LEVEL_DELTA)
update_appearance()
/// Disarms the nuke, reverting all pinpointers and the security level
@@ -469,7 +469,7 @@ GLOBAL_VAR(station_nuke_source)
log_game("\The [src] at [loc_name(our_turf)] was disarmed by [disarmer ? key_name(disarmer) : "an unknown user"].")
detonation_timer = null
- set_security_level(previous_level)
+ SSsecurity_level.set_level(previous_level)
for(var/obj/item/pinpointer/nuke/syndicate/nuke_pointer in GLOB.pinpointer_list)
nuke_pointer.switch_mode_to(initial(nuke_pointer.mode))
diff --git a/code/modules/antagonists/traitor/equipment/Malf_Modules.dm b/code/modules/antagonists/traitor/equipment/Malf_Modules.dm
index efa3ecdf502..5b9c6f21b92 100644
--- a/code/modules/antagonists/traitor/equipment/Malf_Modules.dm
+++ b/code/modules/antagonists/traitor/equipment/Malf_Modules.dm
@@ -282,7 +282,7 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module))
return
if (owner_AI.stat != DEAD)
priority_announce("Hostile runtimes detected in all station systems, please deactivate your AI to prevent possible damage to its morality core.", "Anomaly Alert", ANNOUNCER_AIMALF)
- set_security_level("delta")
+ SSsecurity_level.set_level(SEC_LEVEL_DELTA)
var/obj/machinery/doomsday_device/DOOM = new(owner_AI)
owner_AI.nuking = TRUE
owner_AI.doomsday_device = DOOM
@@ -319,7 +319,7 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module))
STOP_PROCESSING(SSfastprocess, src)
SSshuttle.clearHostileEnvironment(src)
SSmapping.remove_nuke_threat(src)
- set_security_level("red")
+ SSsecurity_level.set_level(SEC_LEVEL_RED)
for(var/mob/living/silicon/robot/borg in owner?.connected_robots)
borg.lamp_doom = FALSE
borg.toggle_headlamp(FALSE, TRUE) //forces borg lamp to update
diff --git a/code/modules/power/singularity/narsie.dm b/code/modules/power/singularity/narsie.dm
index d8e9ae1b2ab..bcb2a8b51ff 100644
--- a/code/modules/power/singularity/narsie.dm
+++ b/code/modules/power/singularity/narsie.dm
@@ -235,7 +235,7 @@
///security level and shuttle lockdowns for [/proc/begin_the_end()]
/proc/narsie_start_destroy_station()
- set_security_level("delta")
+ SSsecurity_level.set_level(SEC_LEVEL_DELTA)
SSshuttle.registerHostileEnvironment(GLOB.cult_narsie)
SSshuttle.lockdown = TRUE
addtimer(CALLBACK(GLOBAL_PROC, .proc/narsie_apocalypse), 1 MINUTES)
@@ -255,7 +255,7 @@
///Called only if the crew managed to destroy narsie at the very last second for [/proc/begin_the_end()]
/proc/narsie_last_second_win()
- set_security_level("red")
+ SSsecurity_level.set_level(SEC_LEVEL_RED)
SSshuttle.lockdown = FALSE
INVOKE_ASYNC(GLOBAL_PROC, .proc/cult_ending_helper, CULT_FAILURE_NARSIE_KILLED)
diff --git a/code/modules/power/supermatter/supermatter_delamination.dm b/code/modules/power/supermatter/supermatter_delamination.dm
index 961d9b7defc..1fb0335e947 100644
--- a/code/modules/power/supermatter/supermatter_delamination.dm
+++ b/code/modules/power/supermatter/supermatter_delamination.dm
@@ -223,7 +223,7 @@
* Adds a bit of spiciness to the cascade by breaking lights and turning emergency maint access on
*/
/datum/supermatter_delamination/proc/create_cascade_ambience()
- if(SSsecurity_level.current_level != SEC_LEVEL_DELTA)
+ if(SSsecurity_level.get_current_level_as_number() != SEC_LEVEL_DELTA)
SSsecurity_level.set_level(SEC_LEVEL_DELTA) // skip the announcement and shuttle timer adjustment in set_security_level()
make_maint_all_access()
break_lights_on_station()
diff --git a/code/modules/security_levels/keycard_authentication.dm b/code/modules/security_levels/keycard_authentication.dm
index 26f43924410..738b65ec868 100644
--- a/code/modules/security_levels/keycard_authentication.dm
+++ b/code/modules/security_levels/keycard_authentication.dm
@@ -43,7 +43,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/keycard_auth, 26)
var/list/data = list()
data["waiting"] = waiting
data["auth_required"] = event_source ? event_source.event : 0
- data["red_alert"] = (seclevel2num(get_security_level()) >= SEC_LEVEL_RED) ? 1 : 0
+ data["red_alert"] = (SSsecurity_level.get_current_level_as_number() >= SEC_LEVEL_RED) ? 1 : 0
data["emergency_maint"] = GLOB.emergency_access
data["bsa_unlock"] = GLOB.bsa_unlock
return data
@@ -131,7 +131,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/keycard_auth, 26)
deadchat_broadcast(" confirmed [event] at [span_name("[A2.name]")].", span_name("[confirmer]"), confirmer, message_type=DEADCHAT_ANNOUNCEMENT)
switch(event)
if(KEYCARD_RED_ALERT)
- set_security_level(SEC_LEVEL_RED)
+ SSsecurity_level.set_level(SEC_LEVEL_RED)
if(KEYCARD_EMERGENCY_MAINTENANCE_ACCESS)
make_maint_all_access()
if(KEYCARD_BSA_UNLOCK)
diff --git a/code/modules/security_levels/security_level_datums.dm b/code/modules/security_levels/security_level_datums.dm
new file mode 100644
index 00000000000..c6addd4d4eb
--- /dev/null
+++ b/code/modules/security_levels/security_level_datums.dm
@@ -0,0 +1,82 @@
+/**
+ * Security levels
+ *
+ * These are used by the security level subsystem. Each one of these represents a security level that a player can set.
+ *
+ * Base type is abstract
+ */
+
+/datum/security_level
+ /// The name of this security level.
+ var/name = "not set"
+ /// The numerical level of this security level, see defines for more information.
+ var/number_level = -1
+ /// The sound that we will play when this security level is set
+ var/sound
+ /// The looping sound that will be played while the security level is set
+ var/looping_sound
+ /// The looping sound interval
+ var/looping_sound_interval
+ /// The shuttle call time modification of this security level
+ var/shuttle_call_time_mod = 0
+ /// Our announcement when lowering to this level
+ var/lowering_to_announcement
+ /// Our announcement when elevating to this level
+ var/elevating_to_announcemnt
+ /// Our configuration key for lowering to text, if set, will override the default lowering to announcement.
+ var/lowering_to_configuration_key
+ /// Our configuration key for elevating to text, if set, will override the default elevating to announcement.
+ var/elevating_to_configuration_key
+
+/datum/security_level/New()
+ . = ..()
+ if(lowering_to_configuration_key) // I'm not sure about you, but isn't there an easier way to do this?
+ lowering_to_announcement = global.config.Get(lowering_to_configuration_key)
+ if(elevating_to_configuration_key)
+ elevating_to_announcemnt = global.config.Get(elevating_to_configuration_key)
+
+/**
+ * GREEN
+ *
+ * No threats
+ */
+/datum/security_level/green
+ name = "green"
+ number_level = SEC_LEVEL_GREEN
+ lowering_to_configuration_key = /datum/config_entry/string/alert_green
+ shuttle_call_time_mod = 2
+
+/**
+ * BLUE
+ *
+ * Caution advised
+ */
+/datum/security_level/blue
+ name = "blue"
+ number_level = SEC_LEVEL_BLUE
+ lowering_to_configuration_key = /datum/config_entry/string/alert_blue_downto
+ elevating_to_configuration_key = /datum/config_entry/string/alert_blue_upto
+ shuttle_call_time_mod = 1
+
+/**
+ * RED
+ *
+ * Hostile threats
+ */
+/datum/security_level/red
+ name = "red"
+ number_level = SEC_LEVEL_RED
+ lowering_to_configuration_key = /datum/config_entry/string/alert_red_downto
+ elevating_to_configuration_key = /datum/config_entry/string/alert_red_upto
+ shuttle_call_time_mod = 0.5
+
+/**
+ * DELTA
+ *
+ * Station destruction is imminent
+ */
+/datum/security_level/delta
+ name = "delta"
+ number_level = SEC_LEVEL_DELTA
+ elevating_to_configuration_key = /datum/config_entry/string/alert_delta
+ shuttle_call_time_mod = 0.25
diff --git a/code/modules/security_levels/security_levels.dm b/code/modules/security_levels/security_levels.dm
deleted file mode 100644
index c289f90b607..00000000000
--- a/code/modules/security_levels/security_levels.dm
+++ /dev/null
@@ -1,82 +0,0 @@
-/proc/set_security_level(level)
- switch(level)
- if("green")
- level = SEC_LEVEL_GREEN
- if("blue")
- level = SEC_LEVEL_BLUE
- if("red")
- level = SEC_LEVEL_RED
- if("delta")
- level = SEC_LEVEL_DELTA
-
- //Will not be announced if you try to set to the same level as it already is
- if(level >= SEC_LEVEL_GREEN && level <= SEC_LEVEL_DELTA && level != SSsecurity_level.current_level)
- switch(level)
- if(SEC_LEVEL_GREEN)
- minor_announce(CONFIG_GET(string/alert_green), "Attention! Security level lowered to green:")
- if(SSshuttle.emergency.mode == SHUTTLE_CALL || SSshuttle.emergency.mode == SHUTTLE_RECALL)
- if(SSsecurity_level.current_level >= SEC_LEVEL_RED)
- SSshuttle.emergency.modTimer(4)
- else
- SSshuttle.emergency.modTimer(2)
- if(SEC_LEVEL_BLUE)
- if(SSsecurity_level.current_level < SEC_LEVEL_BLUE)
- minor_announce(CONFIG_GET(string/alert_blue_upto), "Attention! Security level elevated to blue:",1)
- if(SSshuttle.emergency.mode == SHUTTLE_CALL || SSshuttle.emergency.mode == SHUTTLE_RECALL)
- SSshuttle.emergency.modTimer(0.5)
- else
- minor_announce(CONFIG_GET(string/alert_blue_downto), "Attention! Security level lowered to blue:")
- if(SSshuttle.emergency.mode == SHUTTLE_CALL || SSshuttle.emergency.mode == SHUTTLE_RECALL)
- SSshuttle.emergency.modTimer(2)
- if(SEC_LEVEL_RED)
- if(SSsecurity_level.current_level < SEC_LEVEL_RED)
- minor_announce(CONFIG_GET(string/alert_red_upto), "Attention! Code red!",1)
- if(SSshuttle.emergency.mode == SHUTTLE_CALL || SSshuttle.emergency.mode == SHUTTLE_RECALL)
- if(SSsecurity_level.current_level == SEC_LEVEL_GREEN)
- SSshuttle.emergency.modTimer(0.25)
- else
- SSshuttle.emergency.modTimer(0.5)
- else
- minor_announce(CONFIG_GET(string/alert_red_downto), "Attention! Code red!")
- if(SEC_LEVEL_DELTA)
- minor_announce(CONFIG_GET(string/alert_delta), "Attention! Delta security level reached!",1)
- if(SSshuttle.emergency.mode == SHUTTLE_CALL || SSshuttle.emergency.mode == SHUTTLE_RECALL)
- if(SSsecurity_level.current_level == SEC_LEVEL_GREEN)
- SSshuttle.emergency.modTimer(0.25)
- else if(SSsecurity_level.current_level == SEC_LEVEL_BLUE)
- SSshuttle.emergency.modTimer(0.5)
-
- SSsecurity_level.set_level(level)
-
-/proc/get_security_level()
- switch(SSsecurity_level.current_level)
- if(SEC_LEVEL_GREEN)
- return "green"
- if(SEC_LEVEL_BLUE)
- return "blue"
- if(SEC_LEVEL_RED)
- return "red"
- if(SEC_LEVEL_DELTA)
- return "delta"
-
-/proc/num2seclevel(num)
- switch(num)
- if(SEC_LEVEL_GREEN)
- return "green"
- if(SEC_LEVEL_BLUE)
- return "blue"
- if(SEC_LEVEL_RED)
- return "red"
- if(SEC_LEVEL_DELTA)
- return "delta"
-
-/proc/seclevel2num(seclevel)
- switch( lowertext(seclevel) )
- if("green")
- return SEC_LEVEL_GREEN
- if("blue")
- return SEC_LEVEL_BLUE
- if("red")
- return SEC_LEVEL_RED
- if("delta")
- return SEC_LEVEL_DELTA
diff --git a/code/modules/shuttle/emergency.dm b/code/modules/shuttle/emergency.dm
index 06babb9198f..73b613d2970 100644
--- a/code/modules/shuttle/emergency.dm
+++ b/code/modules/shuttle/emergency.dm
@@ -321,7 +321,7 @@
/obj/docking_port/mobile/emergency/request(obj/docking_port/stationary/S, area/signalOrigin, reason, redAlert, set_coefficient=null)
if(!isnum(set_coefficient))
- var/security_num = seclevel2num(get_security_level())
+ var/security_num = SSsecurity_level.get_current_level_as_number()
switch(security_num)
if(SEC_LEVEL_GREEN)
set_coefficient = 2
@@ -571,7 +571,7 @@
var/obj/machinery/computer/shuttle/C = getControlConsole()
if(!istype(C, /obj/machinery/computer/shuttle/pod))
return ..()
- if(SSsecurity_level.current_level >= SEC_LEVEL_RED || (C && (C.obj_flags & EMAGGED)))
+ if(SSsecurity_level.get_current_level_as_number() >= SEC_LEVEL_RED || (C && (C.obj_flags & EMAGGED)))
if(launch_status == UNLAUNCHED)
launch_status = EARLY_LAUNCHED
return ..()
@@ -732,7 +732,7 @@
/obj/item/storage/pod/can_interact(mob/user)
if(!..())
return FALSE
- if(SSsecurity_level.current_level >= SEC_LEVEL_RED || unlocked)
+ if(SSsecurity_level.get_current_level_as_number() >= SEC_LEVEL_RED || unlocked)
return TRUE
to_chat(user, "The storage unit will only unlock during a Red or Delta security alert.")
return FALSE
diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm
index 6bbbe23804d..17027ecfc13 100644
--- a/code/modules/unit_tests/_unit_tests.dm
+++ b/code/modules/unit_tests/_unit_tests.dm
@@ -137,6 +137,7 @@
#include "screenshot_basic.dm"
#include "screenshot_humanoids.dm"
#include "security_officer_distribution.dm"
+#include "security_levels.dm"
#include "serving_tray.dm"
#include "siunit.dm"
#include "slips.dm"
diff --git a/code/modules/unit_tests/security_levels.dm b/code/modules/unit_tests/security_levels.dm
new file mode 100644
index 00000000000..eecef51ff99
--- /dev/null
+++ b/code/modules/unit_tests/security_levels.dm
@@ -0,0 +1,16 @@
+/**
+ * Security Level Unit Test
+ *
+ * This test is here to ensure there are no security levels with the same name or number level. Having the same name or number level will cause problems.
+ */
+/datum/unit_test/security_levels
+
+/datum/unit_test/security_levels/Run()
+ var/list/comparison = subtypesof(/datum/security_level)
+
+ for(var/datum/security_level/iterating_level in comparison)
+ for(var/datum/security_level/iterating_level_check in comparison)
+ if(iterating_level == iterating_level_check) // If they are the same type, don't check
+ continue
+ TEST_ASSERT_NOTEQUAL(iterating_level.name, iterating_level_check.name, "Security level [iterating_level] has the same name as [iterating_level_check]!")
+ TEST_ASSERT_NOTEQUAL(iterating_level.number_level, iterating_level_check.number_level, "Security level [iterating_level] has the same level number as [iterating_level_check]!")
diff --git a/tgstation.dme b/tgstation.dme
index 5baa62cd39c..d918df94b7c 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -4133,7 +4133,7 @@
#include "code\modules\research\xenobiology\vatgrowing\samples\cell_lines\common.dm"
#include "code\modules\research\xenobiology\vatgrowing\samples\viruses\_virus.dm"
#include "code\modules\security_levels\keycard_authentication.dm"
-#include "code\modules\security_levels\security_levels.dm"
+#include "code\modules\security_levels\security_level_datums.dm"
#include "code\modules\shuttle\arrivals.dm"
#include "code\modules\shuttle\assault_pod.dm"
#include "code\modules\shuttle\battlecruiser_starfury.dm"