From ac6ae3b7c1b41150c6ec2fe0d421211058185423 Mon Sep 17 00:00:00 2001
From: Shoddd <148718717+Shoddd@users.noreply.github.com>
Date: Sat, 21 Dec 2024 15:33:31 -0500
Subject: [PATCH 01/28] NO MORE (#4641)

Co-authored-by: shodd <linkmonissussy@gmail.com>
---
 code/modules/projectiles/guns/magic/wand.dm | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/code/modules/projectiles/guns/magic/wand.dm b/code/modules/projectiles/guns/magic/wand.dm
index 82b78a4859ed..56c3a44a5837 100644
--- a/code/modules/projectiles/guns/magic/wand.dm
+++ b/code/modules/projectiles/guns/magic/wand.dm
@@ -28,6 +28,11 @@
 	icon_state = "[base_icon_state][charges ? null : "-drained"]"
 	return ..()
 
+/obj/item/gun/magic/wand/can_trigger_gun(mob/living/user, akimbo_usage)
+	if(akimbo_usage)
+		return FALSE
+	return ..()
+
 /obj/item/gun/magic/wand/attack(atom/target, mob/living/user)
 	if(target == user)
 		return

From 92dc88bbcf71359d11d8a6c25cf13445b8be4ddc Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Sat, 21 Dec 2024 20:33:51 +0000
Subject: [PATCH 02/28] Automatic changelog for PR #4641 [ci skip]

---
 html/changelogs/AutoChangeLog-pr-4641.yml | 4 ++++
 1 file changed, 4 insertions(+)
 create mode 100644 html/changelogs/AutoChangeLog-pr-4641.yml

diff --git a/html/changelogs/AutoChangeLog-pr-4641.yml b/html/changelogs/AutoChangeLog-pr-4641.yml
new file mode 100644
index 000000000000..2e4b7182013d
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-4641.yml
@@ -0,0 +1,4 @@
+author: "Shoddd"
+delete-after: True
+changes:
+  - bugfix: "you can no longer akimbo and fire a wand forever"
\ No newline at end of file

From ece4a124db964d2b6b6a0df71842dec9a3773f27 Mon Sep 17 00:00:00 2001
From: Changelogs <action@github.com>
Date: Sun, 22 Dec 2024 01:47:17 +0000
Subject: [PATCH 03/28] Automatic changelog compile [ci skip]

---
 html/changelogs/AutoChangeLog-pr-4641.yml | 4 ----
 html/changelogs/archive/2024-12.yml       | 3 +++
 2 files changed, 3 insertions(+), 4 deletions(-)
 delete mode 100644 html/changelogs/AutoChangeLog-pr-4641.yml

diff --git a/html/changelogs/AutoChangeLog-pr-4641.yml b/html/changelogs/AutoChangeLog-pr-4641.yml
deleted file mode 100644
index 2e4b7182013d..000000000000
--- a/html/changelogs/AutoChangeLog-pr-4641.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Shoddd"
-delete-after: True
-changes:
-  - bugfix: "you can no longer akimbo and fire a wand forever"
\ No newline at end of file
diff --git a/html/changelogs/archive/2024-12.yml b/html/changelogs/archive/2024-12.yml
index b239606c6752..5be305e9ae98 100644
--- a/html/changelogs/archive/2024-12.yml
+++ b/html/changelogs/archive/2024-12.yml
@@ -362,3 +362,6 @@
       can only be used in the cargo's warehouse and storage.
   - bugfix: Heretic sacrifice targets will have their blood restored and cleared of
       chems if they are sent to the mansus.
+2024-12-22:
+  Shoddd:
+  - bugfix: you can no longer akimbo and fire a wand forever

From 5c655db2193270e0e1324316def309b25cc6ec36 Mon Sep 17 00:00:00 2001
From: Tractor Mann <69653259+Noot-Toot@users.noreply.github.com>
Date: Sun, 22 Dec 2024 17:43:08 +0000
Subject: [PATCH 04/28] switcharoo (#4632)

i was gonna fix cyborg emps as well, but i couldnt figure out how, if you, the fool reading this try to do so yourself, good luck!~
---
 monkestation/code/modules/clothing/gloves/power_gloves.dm | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/monkestation/code/modules/clothing/gloves/power_gloves.dm b/monkestation/code/modules/clothing/gloves/power_gloves.dm
index 5caefddf16e9..0528ec7e1cc1 100644
--- a/monkestation/code/modules/clothing/gloves/power_gloves.dm
+++ b/monkestation/code/modules/clothing/gloves/power_gloves.dm
@@ -47,10 +47,10 @@
 
 	if(power >= dust_power) //Dusts if there's enough in the grid
 		electrocute_victim.dust(TRUE, FALSE, TRUE)
-		log_combat(owner, target, "zapped", /obj/item/clothing/gloves/color/yellow/power_gloves, "[power] watts were used resulting in [shock_damage] damage.")
+		log_combat(owner, target, "zapped", /obj/item/clothing/gloves/color/yellow/power_gloves, "[power] watts were used resulting in the target dusting.")
 	else
 		electrocute_victim.electrocute_act(shock_damage, source, 1, SHOCK_TESLA | ((zap_flags & ZAP_MOB_STUN) ? NONE : SHOCK_NOSTUN))
-		log_combat(owner, target, "zapped", /obj/item/clothing/gloves/color/yellow/power_gloves, "[power] watts were used resulting in the target dusting.")
+		log_combat(owner, target, "zapped", /obj/item/clothing/gloves/color/yellow/power_gloves, "[power] watts were used resulting in [shock_damage] damage.")
 		return
 
 

From ef34303440ce42d0c139bb60efc610323aea1588 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Sun, 22 Dec 2024 17:43:27 +0000
Subject: [PATCH 05/28] Automatic changelog for PR #4632 [ci skip]

---
 html/changelogs/AutoChangeLog-pr-4632.yml | 4 ++++
 1 file changed, 4 insertions(+)
 create mode 100644 html/changelogs/AutoChangeLog-pr-4632.yml

diff --git a/html/changelogs/AutoChangeLog-pr-4632.yml b/html/changelogs/AutoChangeLog-pr-4632.yml
new file mode 100644
index 000000000000..f88d93e27281
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-4632.yml
@@ -0,0 +1,4 @@
+author: "Tractor Mann"
+delete-after: True
+changes:
+  - admin: "Admins will no longer be told you dusted someone with power gloves when you didnt, and that you shocked someone with power gloves when you dust them."
\ No newline at end of file

From c56095648d8b0d03539a3e4b12c19196e0fba2a1 Mon Sep 17 00:00:00 2001
From: dwasint <82520990+dwasint@users.noreply.github.com>
Date: Sun, 22 Dec 2024 11:42:33 -0800
Subject: [PATCH 06/28] Update TGS DMAPI (#4643)

Co-authored-by: tgstation-server <tgstation-server@users.noreply.github.com>
---
 code/__DEFINES/tgs.dm         | 29 ++++++++++++++++++++++++++++-
 code/modules/tgs/v5/undefs.dm |  1 +
 2 files changed, 29 insertions(+), 1 deletion(-)

diff --git a/code/__DEFINES/tgs.dm b/code/__DEFINES/tgs.dm
index 42f2d5fc31fe..7e1ba820dd8b 100644
--- a/code/__DEFINES/tgs.dm
+++ b/code/__DEFINES/tgs.dm
@@ -1,7 +1,7 @@
 // tgstation-server DMAPI
 // The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in IETF RFC 2119.
 
-#define TGS_DMAPI_VERSION "7.3.0"
+#define TGS_DMAPI_VERSION "7.3.1"
 
 // All functions and datums outside this document are subject to change with any version and should not be relied on.
 
@@ -58,6 +58,11 @@
 #define TGS_FILE2TEXT_NATIVE file2text
 #endif
 
+// SpacemanDMM compatibility
+#ifndef CAN_BE_REDEFINED
+#define CAN_BE_REDEFINED(X)
+#endif
+
 // EVENT CODES
 
 /// Before a reboot mode change, extras parameters are the current and new reboot mode enums.
@@ -160,6 +165,7 @@
  * * http_handler - Optional user defined [/datum/tgs_http_handler].
  */
 /world/proc/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SECURITY_ULTRASAFE, datum/tgs_http_handler/http_handler)
+	CAN_BE_REDEFINED(TRUE)
 	return
 
 /**
@@ -170,6 +176,7 @@
  * This function should not be called before ..() in [/world/proc/New].
  */
 /world/proc/TgsInitializationComplete()
+	CAN_BE_REDEFINED(TRUE)
 	return
 
 /// Consumers MUST run this macro at the start of [/world/proc/Topic].
@@ -177,6 +184,7 @@
 
 /// Consumers MUST call this as late as possible in [world/proc/Reboot] (BEFORE ..()).
 /world/proc/TgsReboot()
+	CAN_BE_REDEFINED(TRUE)
 	return
 
 // DATUM DEFINITIONS
@@ -214,6 +222,7 @@
  * Returns [TRUE]/[FALSE] based on if the [/datum/tgs_version] contains wildcards.
  */
 /datum/tgs_version/proc/Wildcard()
+	CAN_BE_REDEFINED(TRUE)
 	return
 
 /**
@@ -222,6 +231,7 @@
  * other_version - The [/datum/tgs_version] to compare against.
  */
 /datum/tgs_version/proc/Equals(datum/tgs_version/other_version)
+	CAN_BE_REDEFINED(TRUE)
 	return
 
 /// Represents a merge of a GitHub pull request.
@@ -459,16 +469,19 @@
 
 /// Returns the maximum supported [/datum/tgs_version] of the DMAPI.
 /world/proc/TgsMaximumApiVersion()
+	CAN_BE_REDEFINED(TRUE)
 	return
 
 /// Returns the minimum supported [/datum/tgs_version] of the DMAPI.
 /world/proc/TgsMinimumApiVersion()
+	CAN_BE_REDEFINED(TRUE)
 	return
 
 /**
  * Returns [TRUE] if DreamDaemon was launched under TGS, the API matches, and was properly initialized. [FALSE] will be returned otherwise.
  */
 /world/proc/TgsAvailable()
+	CAN_BE_REDEFINED(TRUE)
 	return
 
 // No function below this succeeds if it TgsAvailable() returns FALSE or if TgsNew() has yet to be called.
@@ -480,6 +493,7 @@
  * If TGS has not requested a [TGS_REBOOT_MODE_SHUTDOWN] DreamDaemon will be launched again.
  */
 /world/proc/TgsEndProcess()
+	CAN_BE_REDEFINED(TRUE)
 	return
 
 /**
@@ -490,6 +504,7 @@
  * admin_only: If [TRUE], message will be sent to admin connected chats. Vice-versa applies.
  */
 /world/proc/TgsTargetedChatBroadcast(datum/tgs_message_content/message, admin_only = FALSE)
+	CAN_BE_REDEFINED(TRUE)
 	return
 
 /**
@@ -500,6 +515,7 @@
  * user: The [/datum/tgs_chat_user] to PM.
  */
 /world/proc/TgsChatPrivateMessage(datum/tgs_message_content/message, datum/tgs_chat_user/user)
+	CAN_BE_REDEFINED(TRUE)
 	return
 
 /**
@@ -510,42 +526,52 @@
  * channels - Optional list of [/datum/tgs_chat_channel]s to restrict the message to.
  */
 /world/proc/TgsChatBroadcast(datum/tgs_message_content/message, list/channels = null)
+	CAN_BE_REDEFINED(TRUE)
 	return
 
 /// Returns the current [/datum/tgs_version] of TGS if it is running the server, null otherwise. This function may sleep if the call to [/world/proc/TgsNew] is sleeping!
 /world/proc/TgsVersion()
+	CAN_BE_REDEFINED(TRUE)
 	return
 
 /// Returns the running engine type
 /world/proc/TgsEngine()
+	CAN_BE_REDEFINED(TRUE)
 	return
 
 /// Returns the current [/datum/tgs_version] of the DMAPI being used if it was activated, null otherwise. This function may sleep if the call to [/world/proc/TgsNew] is sleeping!
 /world/proc/TgsApiVersion()
+	CAN_BE_REDEFINED(TRUE)
 	return
 
 /// Returns the name of the TGS instance running the game if TGS is present, null otherwise. This function may sleep if the call to [/world/proc/TgsNew] is sleeping!
 /world/proc/TgsInstanceName()
+	CAN_BE_REDEFINED(TRUE)
 	return
 
 /// Return the current [/datum/tgs_revision_information] of the running server if TGS is present, null otherwise. This function may sleep if the call to [/world/proc/TgsNew] is sleeping!
 /world/proc/TgsRevision()
+	CAN_BE_REDEFINED(TRUE)
 	return
 
 /// Returns the current BYOND security level as a TGS_SECURITY_ define if TGS is present, null otherwise. This function may sleep if the call to [/world/proc/TgsNew] is sleeping!
 /world/proc/TgsSecurityLevel()
+	CAN_BE_REDEFINED(TRUE)
 	return
 
 /// Returns the current BYOND visibility level as a TGS_VISIBILITY_ define if TGS is present, null otherwise. Requires TGS to be using interop API version 5 or higher otherwise the string "___unimplemented" wil be returned. This function may sleep if the call to [/world/proc/TgsNew] is sleeping!
 /world/proc/TgsVisibility()
+	CAN_BE_REDEFINED(TRUE)
 	return
 
 /// Returns a list of active [/datum/tgs_revision_information/test_merge]s if TGS is present, null otherwise. This function may sleep if the call to [/world/proc/TgsNew] is sleeping!
 /world/proc/TgsTestMerges()
+	CAN_BE_REDEFINED(TRUE)
 	return
 
 /// Returns a list of connected [/datum/tgs_chat_channel]s if TGS is present, null otherwise. This function may sleep if the call to [/world/proc/TgsNew] is sleeping!
 /world/proc/TgsChatChannelInfo()
+	CAN_BE_REDEFINED(TRUE)
 	return
 
 /**
@@ -556,6 +582,7 @@
  * wait_for_completion - If set, this function will not return until the event has run to completion.
  */
 /world/proc/TgsTriggerEvent(event_name, list/parameters, wait_for_completion = FALSE)
+	CAN_BE_REDEFINED(TRUE)
 	return
 
 /*
diff --git a/code/modules/tgs/v5/undefs.dm b/code/modules/tgs/v5/undefs.dm
index acd19dfa6411..ca49e46cdffa 100644
--- a/code/modules/tgs/v5/undefs.dm
+++ b/code/modules/tgs/v5/undefs.dm
@@ -18,6 +18,7 @@
 
 #undef DMAPI5_PARAMETER_ACCESS_IDENTIFIER
 #undef DMAPI5_PARAMETER_CUSTOM_COMMANDS
+#undef DMAPI5_PARAMETER_TOPIC_PORT
 
 #undef DMAPI5_CHUNK
 #undef DMAPI5_CHUNK_PAYLOAD

From 39b4f1887ec8417107332c02bc37b83218002db1 Mon Sep 17 00:00:00 2001
From: Lucy <lucy@absolucy.moe>
Date: Sun, 22 Dec 2024 14:42:51 -0500
Subject: [PATCH 07/28] Prevent setting the next map from conflicting across
 LRP/MRP/HRP (#4638)

* Prevent setting the next map from conflicting across LRP/MRP/HRP

* fix typo
---
 code/__DEFINES/maps.dm | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/code/__DEFINES/maps.dm b/code/__DEFINES/maps.dm
index d59e0477c798..1bccfebfc558 100644
--- a/code/__DEFINES/maps.dm
+++ b/code/__DEFINES/maps.dm
@@ -61,7 +61,7 @@ Always compile, always use that verb, and always make sure that it works for wha
 #define MAP_MAXZ 6
 
 /// Path for the next_map.json file, if someone, for some messed up reason, wants to change it.
-#define PATH_TO_NEXT_MAP_JSON "data/next_map.json"
+#define PATH_TO_NEXT_MAP_JSON (world.GetConfig("env", "AUXTOOLS_DEBUG_DLL") ? "data/next_map.json" : "data/next_map.[world.port].json") // monkestation edit: messed up cat here, i changed it (added world.port to it if there's no debugger attached)
 
 /// List of directories we can load map .json files from
 #define MAP_DIRECTORY_MAPS "_maps"

From 6b05c0989e98ad2ca6ec7bce6102522cc7ecdfab Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Sun, 22 Dec 2024 19:43:09 +0000
Subject: [PATCH 08/28] Automatic changelog for PR #4638 [ci skip]

---
 html/changelogs/AutoChangeLog-pr-4638.yml | 4 ++++
 1 file changed, 4 insertions(+)
 create mode 100644 html/changelogs/AutoChangeLog-pr-4638.yml

diff --git a/html/changelogs/AutoChangeLog-pr-4638.yml b/html/changelogs/AutoChangeLog-pr-4638.yml
new file mode 100644
index 000000000000..a90c6aed296c
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-4638.yml
@@ -0,0 +1,4 @@
+author: "Absolucy"
+delete-after: True
+changes:
+  - bugfix: "The map selected for the next round should actually always be the map that loads next round."
\ No newline at end of file

From 7e019ae7476201ae67e6c1f5c7184987626c9df6 Mon Sep 17 00:00:00 2001
From: Lucy <lucy@absolucy.moe>
Date: Sun, 22 Dec 2024 14:43:24 -0500
Subject: [PATCH 09/28] Properly marks type and confidentiality of a few
 admin/mentor-related notifications (#4635)

---
 code/modules/client/client_procs.dm               | 7 +++----
 monkestation/code/modules/mentor/mentor_follow.dm | 6 +++---
 2 files changed, 6 insertions(+), 7 deletions(-)

diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm
index dd5c2b429c12..be80860d23e1 100644
--- a/code/modules/client/client_procs.dm
+++ b/code/modules/client/client_procs.dm
@@ -460,7 +460,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
 		add_admin_verbs()
 		var/memo_message = get_message_output("memo")
 		if(memo_message)
-			to_chat(src, memo_message)
+			to_chat(src, memo_message, type = MESSAGE_TYPE_ADMINLOG, confidential = TRUE)
 		adminGreet()
 	if (mob && reconnecting)
 		var/stealth_admin = mob.client?.holder?.fakekey
@@ -479,7 +479,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
 		var/time_stamp = query_last_connected.item[1]
 		var/unread_notes = get_message_output("note", ckey, FALSE, time_stamp)
 		if(unread_notes)
-			to_chat(src, unread_notes)
+			to_chat(src, unread_notes, type = MESSAGE_TYPE_ADMINPM, confidential = TRUE)
 	qdel(query_last_connected)
 
 	var/cached_player_age = set_client_age_from_db(tdata) //we have to cache this because other shit may change it and we need it's current value now down below.
@@ -517,7 +517,6 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
 		message_admins("[key_name_admin(src)] (IP: [address], ID: [computer_id]) is a new BYOND account [account_age] day[(account_age == 1?"":"s")] old, created on [account_join_date].")
 		if (CONFIG_GET(flag/irc_first_connection_alert))
 			send2tgs_adminless_only("new_byond_user", "[key_name(src)] (IP: [address], ID: [computer_id]) is a new BYOND account [account_age] day[(account_age == 1?"":"s")] old, created on [account_join_date].")
-	get_message_output("watchlist entry", ckey)
 	if(check_overwatch() && CONFIG_GET(flag/vpn_kick))
 		return
 	validate_key_in_db()
@@ -546,7 +545,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
 		convert_notes_sql(ckey)
 	var/user_messages = get_message_output("message", ckey)
 	if(user_messages)
-		to_chat(src, user_messages)
+		to_chat(src, user_messages, type = MESSAGE_TYPE_ADMINPM, confidential = TRUE)
 	if(!winexists(src, "asset_cache_browser")) // The client is using a custom skin, tell them.
 		to_chat(src, span_warning("Unable to access asset cache browser, if you are using a custom skin file, please allow DS to download the updated version, if you are not, then make a bug report. This is not a critical issue but can cause issues with resource downloading, as it is impossible to know when extra resources arrived to you."))
 
diff --git a/monkestation/code/modules/mentor/mentor_follow.dm b/monkestation/code/modules/mentor/mentor_follow.dm
index 09f8e7a058de..7a8e6ed8961c 100644
--- a/monkestation/code/modules/mentor/mentor_follow.dm
+++ b/monkestation/code/modules/mentor/mentor_follow.dm
@@ -8,8 +8,8 @@
 	mentor_datum.following = followed_guy
 	usr.reset_perspective(followed_guy)
 	add_verb(src, /client/proc/mentor_unfollow)
-	to_chat(GLOB.admins, span_adminooc("<span class='prefix'>MENTOR:</span> <EM>[key_name(usr)]</EM> is now following <EM>[key_name(followed_guy)]</span>"))
-	to_chat(usr, span_info("Click the \"Stop Following\" button in the Mentor tab to stop following [key_name(followed_guy)]."))
+	to_chat(GLOB.admins, span_adminooc("<span class='prefix'>MENTOR:</span> <EM>[key_name(usr)]</EM> is now following <EM>[key_name(followed_guy)]</span>"), type = MESSAGE_TYPE_ADMINLOG, confidential = TRUE)
+	to_chat(src, span_info("Click the \"Stop Following\" button in the Mentor tab to stop following [key_name(followed_guy)]."), type = MESSAGE_TYPE_ADMINLOG, confidential = TRUE)
 	log_mentor("[key_name(usr)] began following [key_name(followed_guy)]")
 
 /client/proc/mentor_unfollow()
@@ -19,6 +19,6 @@
 
 	remove_verb(src, /client/proc/mentor_unfollow)
 	usr.reset_perspective()
-	to_chat(GLOB.admins, span_adminooc("<span class='prefix'>MENTOR:</span> <EM>[key_name(usr)]</EM> is no longer following <EM>[key_name(mentor_datum.following)]</span>"))
+	to_chat(GLOB.admins, span_adminooc("<span class='prefix'>MENTOR:</span> <EM>[key_name(usr)]</EM> is no longer following <EM>[key_name(mentor_datum.following)]</span>"), type = MESSAGE_TYPE_ADMINLOG, confidential = TRUE)
 	log_mentor("[key_name(usr)] stopped following [key_name(mentor_datum.following)]")
 	mentor_datum.following = null

From f548b9fca6657c79b318253b1f723f9166810203 Mon Sep 17 00:00:00 2001
From: Lucy <lucy@absolucy.moe>
Date: Sun, 22 Dec 2024 14:43:31 -0500
Subject: [PATCH 10/28] Adds tinyfans to arrivals on Blueshift (#4633)

---
 _maps/map_files/Blueshift/Blueshift.dmm | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/_maps/map_files/Blueshift/Blueshift.dmm b/_maps/map_files/Blueshift/Blueshift.dmm
index 250dae6db367..96658451b2c6 100644
--- a/_maps/map_files/Blueshift/Blueshift.dmm
+++ b/_maps/map_files/Blueshift/Blueshift.dmm
@@ -4701,6 +4701,10 @@
 /obj/effect/turf_decal/tile/blue/fourcorners,
 /turf/open/floor/iron/white,
 /area/station/medical/treatment_center)
+"aWw" = (
+/obj/structure/fans/tiny,
+/turf/open/floor/plating,
+/area/station/hallway/secondary/entry)
 "aWA" = (
 /obj/structure/cable,
 /obj/effect/landmark/start/hangover,
@@ -55519,6 +55523,7 @@
 /obj/effect/mapping_helpers/airlock/cyclelink_helper{
 	dir = 8
 	},
+/obj/structure/fans/tiny,
 /turf/open/floor/plating,
 /area/station/hallway/secondary/entry)
 "kKp" = (
@@ -170231,7 +170236,7 @@ ahn
 cXo
 cXo
 rMe
-ivx
+aWw
 rMe
 gLc
 gLc

From 1959d3a08fab51d3c20692dc3209db15296b38a5 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Sun, 22 Dec 2024 19:43:43 +0000
Subject: [PATCH 11/28] Automatic changelog for PR #4635 [ci skip]

---
 html/changelogs/AutoChangeLog-pr-4635.yml | 4 ++++
 1 file changed, 4 insertions(+)
 create mode 100644 html/changelogs/AutoChangeLog-pr-4635.yml

diff --git a/html/changelogs/AutoChangeLog-pr-4635.yml b/html/changelogs/AutoChangeLog-pr-4635.yml
new file mode 100644
index 000000000000..07b5b61ef5ce
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-4635.yml
@@ -0,0 +1,4 @@
+author: "Absolucy"
+delete-after: True
+changes:
+  - bugfix: "Properly marked the message type and confidentiality of a few admin/mentor-related notifications."
\ No newline at end of file

From e07d7aab76caae6d4bb5b3673b3a89b9ab90b1d2 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Sun, 22 Dec 2024 19:43:49 +0000
Subject: [PATCH 12/28] Automatic changelog for PR #4633 [ci skip]

---
 html/changelogs/AutoChangeLog-pr-4633.yml | 4 ++++
 1 file changed, 4 insertions(+)
 create mode 100644 html/changelogs/AutoChangeLog-pr-4633.yml

diff --git a/html/changelogs/AutoChangeLog-pr-4633.yml b/html/changelogs/AutoChangeLog-pr-4633.yml
new file mode 100644
index 000000000000..172b28ab6c19
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-4633.yml
@@ -0,0 +1,4 @@
+author: "Absolucy"
+delete-after: True
+changes:
+  - rscadd: "Added tinyfans to the arrivals shuttle and public mining shuttle doors on Blueshift."
\ No newline at end of file

From 24684a330cec34bcef9623ed470ebcc6eb983560 Mon Sep 17 00:00:00 2001
From: Lucy <lucy@absolucy.moe>
Date: Sun, 22 Dec 2024 14:45:41 -0500
Subject: [PATCH 13/28] [PORT] Adds unit test for effects set to "Curse of
 Mundanity" (and missing IDs) (#4580)

Ports https://github.com/tgstation/tgstation/pull/88240 and partially https://github.com/tgstation/tgstation/pull/87842
---
 code/__DEFINES/status_effects.dm              | 10 +++
 code/datums/elements/organ_set_bonus.dm       |  4 +-
 code/datums/status_effects/_status_effect.dm  |  2 +-
 .../datums/status_effects/agent_pinpointer.dm |  2 +-
 code/datums/status_effects/buffs.dm           | 15 ++---
 .../status_effects/buffs/stun_asorption.dm    |  2 +-
 .../status_effects/debuffs/blindness.dm       |  4 +-
 code/datums/status_effects/debuffs/debuffs.dm | 30 ++++-----
 .../debuffs/dna_transformation.dm             |  4 +-
 code/datums/status_effects/debuffs/drunk.dm   |  1 +
 .../status_effects/debuffs/fire_stacks.dm     |  3 +-
 .../status_effects/debuffs/genetic_damage.dm  |  2 +-
 .../status_effects/debuffs/hallucination.dm   |  2 +-
 .../status_effects/debuffs/speech_debuffs.dm  |  2 +-
 .../status_effects/debuffs/tower_of_babel.dm  |  2 +-
 code/datums/status_effects/drug_effects.dm    |  6 +-
 code/datums/status_effects/gas.dm             |  2 +-
 code/datums/status_effects/grouped_effect.dm  |  2 +
 code/datums/status_effects/limited_effect.dm  |  3 +-
 code/datums/status_effects/neutral.dm         | 25 ++++----
 code/datums/status_effects/stacking_effect.dm |  4 +-
 code/datums/status_effects/wound_effects.dm   |  5 +-
 .../heretic/magic/ash_ascension.dm            |  2 +-
 .../antagonists/heretic/magic/shadow_cloak.dm |  2 +-
 .../antagonists/heretic/magic/star_touch.dm   |  2 +-
 .../heretic/status_effects/buffs.dm           |  4 +-
 .../heretic/status_effects/debuffs.dm         |  6 +-
 .../heretic/status_effects/ghoul.dm           |  2 +-
 .../equipment/monster_organs/brimdust_sac.dm  |  2 +-
 .../lavaland/basilisk/basilisk_overheat.dm    |  1 +
 .../space_fauna/revenant/revenant_effects.dm  |  2 +
 code/modules/mob/living/status_procs.dm       |  2 +-
 code/modules/religion/burdened/psyker.dm      |  2 +-
 .../religion/hunt/hunter_pinpointer.dm        |  2 +-
 .../crossbreeding/_status_effects.dm          | 16 ++---
 code/modules/unit_tests/_unit_tests.dm        |  1 +
 .../unit_tests/status_effect_validity.dm      | 61 +++++++++++++++++++
 .../code/datums/status_effects/buffs.dm       |  2 +-
 .../code/datums/status_effects/changeling.dm  |  6 +-
 .../code/datums/status_effects/food_buffs.dm  |  2 +-
 .../code/modules/a_medical_day/lungless.dm    |  4 +-
 .../antagonists/borers/code/status_effects.dm |  2 +-
 .../antagonists/clock_cult/status_effects.dm  |  2 +-
 .../events/wonderland_apocalypse.dm           |  2 +-
 .../monster_hunters/tools/bnuuy_mask.dm       |  2 +-
 .../weapons/hunter_revolver.dm                |  2 +-
 .../artifact_effects/false_rod.dm             |  2 +-
 .../bloodsucker/bloodsucker_frenzy.dm         |  2 +-
 .../bloodsucker/bloodsucker_sol.dm            |  2 +-
 .../modules/bloodsuckers/powers/masquerade.dm |  4 +-
 .../bloodsuckers/vassals/vassal_pinpointer.dm |  2 +-
 .../modules/blueshift/species/ashwalker.dm    |  4 +-
 .../pain/status_effects/low_blood_pressure.dm |  2 +-
 .../modules/datums/status_effects/debuffs.dm  |  3 +
 .../modules/ghost_players/centcom_grace.dm    |  2 +-
 .../modules/liquids/liquid_status_effect.dm   |  5 +-
 .../ranching/chickens/tier1/chicken.dm        |  2 +
 .../crossbreeding/regenerative/cooldown.dm    |  2 +-
 58 files changed, 193 insertions(+), 102 deletions(-)
 create mode 100644 code/modules/unit_tests/status_effect_validity.dm

diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm
index c9d51b852c76..77b3092cfc68 100644
--- a/code/__DEFINES/status_effects.dm
+++ b/code/__DEFINES/status_effects.dm
@@ -7,6 +7,16 @@
 /// if it only allows one, and new instances just instead refresh the timer
 #define STATUS_EFFECT_REFRESH 3
 
+/// Use in status effect "duration" to make it last forever
+#define STATUS_EFFECT_PERMANENT -1
+/// Use in status effect "tick_interval" to prevent it from calling tick()
+#define STATUS_EFFECT_NO_TICK -1
+
+/// Indicates this status effect is an abstract type, ie not instantiated
+/// Doesn't actually do anything in practice, primarily just a marker / used in unit tests,
+/// so don't worry if your abstract status effect doesn't actually set this
+#define STATUS_EFFECT_ID_ABSTRACT "abstract"
+
 ///Processing flags - used to define the speed at which the status will work
 ///This is fast - 0.2s between ticks (I believe!)
 #define STATUS_EFFECT_FAST_PROCESS 0
diff --git a/code/datums/elements/organ_set_bonus.dm b/code/datums/elements/organ_set_bonus.dm
index aeb63356fb48..2859f7b802c5 100644
--- a/code/datums/elements/organ_set_bonus.dm
+++ b/code/datums/elements/organ_set_bonus.dm
@@ -42,8 +42,8 @@
 
 /datum/status_effect/organ_set_bonus
 	id = "organ_set_bonus"
-	duration = -1
-	tick_interval = -1
+	duration = STATUS_EFFECT_PERMANENT
+	tick_interval = STATUS_EFFECT_NO_TICK
 	alert_type = null
 	///how many organs the carbon with this has in the set
 	var/organs = 0
diff --git a/code/datums/status_effects/_status_effect.dm b/code/datums/status_effects/_status_effect.dm
index 49ba273c24db..b95504702c28 100644
--- a/code/datums/status_effects/_status_effect.dm
+++ b/code/datums/status_effects/_status_effect.dm
@@ -6,7 +6,7 @@
 	/// When set initially / in on_creation, this is how long the status effect lasts in deciseconds.
 	/// While processing, this becomes the world.time when the status effect will expire.
 	/// -1 = infinite duration.
-	var/duration = -1
+	var/duration = STATUS_EFFECT_PERMANENT
 	/// When set initially / in on_creation, this is how long between [proc/tick] calls in deciseconds.
 	/// While processing, this becomes the world.time when the next tick will occur.
 	/// -1 = will stop processing, if duration is also unlimited (-1).
diff --git a/code/datums/status_effects/agent_pinpointer.dm b/code/datums/status_effects/agent_pinpointer.dm
index 3f64ff252a02..7afb85805f05 100644
--- a/code/datums/status_effects/agent_pinpointer.dm
+++ b/code/datums/status_effects/agent_pinpointer.dm
@@ -10,7 +10,7 @@
 
 /datum/status_effect/agent_pinpointer
 	id = "agent_pinpointer"
-	duration = -1
+	duration = STATUS_EFFECT_PERMANENT
 	tick_interval = PINPOINTER_PING_TIME
 	alert_type = /atom/movable/screen/alert/status_effect/agent_pinpointer
 	///The minimum range to start pointing towards your target.
diff --git a/code/datums/status_effects/buffs.dm b/code/datums/status_effects/buffs.dm
index e4edd4997c62..a2352c34b15d 100644
--- a/code/datums/status_effects/buffs.dm
+++ b/code/datums/status_effects/buffs.dm
@@ -2,7 +2,7 @@
 
 /datum/status_effect/his_grace
 	id = "his_grace"
-	duration = -1
+	duration = STATUS_EFFECT_PERMANENT
 	tick_interval = 4
 	alert_type = /atom/movable/screen/alert/status_effect/his_grace
 	var/bloodlust = 0
@@ -86,7 +86,7 @@
 
 /datum/status_effect/cult_master
 	id = "The Cult Master"
-	duration = -1
+	duration = STATUS_EFFECT_PERMANENT
 	alert_type = null
 	on_remove_on_mob_delete = TRUE
 	var/alive = TRUE
@@ -116,7 +116,7 @@
 /datum/status_effect/blooddrunk
 	id = "blooddrunk"
 	duration = 10
-	tick_interval = -1 // monkestation edit
+	tick_interval = STATUS_EFFECT_NO_TICK // monkestation edit
 	alert_type = /atom/movable/screen/alert/status_effect/blooddrunk
 
 /atom/movable/screen/alert/status_effect/blooddrunk
@@ -211,7 +211,7 @@
 /datum/status_effect/hippocratic_oath
 	id = "Hippocratic Oath"
 	status_type = STATUS_EFFECT_UNIQUE
-	duration = -1
+	duration = STATUS_EFFECT_PERMANENT
 	tick_interval = 25
 	alert_type = null
 
@@ -423,6 +423,7 @@
 	duration = 2 SECONDS
 	status_type = STATUS_EFFECT_REPLACE
 	show_duration = TRUE
+	alert_type = null
 
 /datum/status_effect/speed_boost/on_creation(mob/living/new_owner, set_duration)
 	if(isnum(set_duration))
@@ -475,7 +476,7 @@
 
 /datum/status_effect/nest_sustenance
 	id = "nest_sustenance"
-	duration = -1
+	duration = STATUS_EFFECT_PERMANENT
 	tick_interval = 0.4 SECONDS
 	alert_type = /atom/movable/screen/alert/status_effect/nest_sustenance
 
@@ -504,8 +505,8 @@
  */
 /datum/status_effect/blessing_of_insanity
 	id = "blessing_of_insanity"
-	duration = -1
-	tick_interval = -1
+	duration = STATUS_EFFECT_PERMANENT
+	tick_interval = STATUS_EFFECT_NO_TICK
 	alert_type = /atom/movable/screen/alert/status_effect/blessing_of_insanity
 
 /atom/movable/screen/alert/status_effect/blessing_of_insanity
diff --git a/code/datums/status_effects/buffs/stun_asorption.dm b/code/datums/status_effects/buffs/stun_asorption.dm
index d68f2f7408cc..f6212bc88421 100644
--- a/code/datums/status_effects/buffs/stun_asorption.dm
+++ b/code/datums/status_effects/buffs/stun_asorption.dm
@@ -9,7 +9,7 @@
  */
 /datum/status_effect/stun_absorption
 	id = "absorb_stun"
-	tick_interval = -1
+	tick_interval = STATUS_EFFECT_NO_TICK
 	alert_type = null
 	status_type = STATUS_EFFECT_MULTIPLE
 
diff --git a/code/datums/status_effects/debuffs/blindness.dm b/code/datums/status_effects/debuffs/blindness.dm
index 0bfaaee7485b..310b995090c8 100644
--- a/code/datums/status_effects/debuffs/blindness.dm
+++ b/code/datums/status_effects/debuffs/blindness.dm
@@ -5,7 +5,7 @@
 /// Nearsighted
 /datum/status_effect/grouped/nearsighted
 	id = "nearsighted"
-	tick_interval = -1
+	tick_interval = STATUS_EFFECT_NO_TICK
 	alert_type = null
 	// This is not "remove on fullheal" as in practice,
 	// fullheal should instead remove all the sources and in turn cure this
@@ -55,7 +55,7 @@
 /// Blindness
 /datum/status_effect/grouped/blindness
 	id = "blindness"
-	tick_interval = -1
+	tick_interval = STATUS_EFFECT_NO_TICK
 	alert_type = /atom/movable/screen/alert/status_effect/blind
 	// This is not "remove on fullheal" as in practice,
 	// fullheal should instead remove all the sources and in turn cure this
diff --git a/code/datums/status_effects/debuffs/debuffs.dm b/code/datums/status_effects/debuffs/debuffs.dm
index 83572e56630c..3e6491813387 100644
--- a/code/datums/status_effects/debuffs/debuffs.dm
+++ b/code/datums/status_effects/debuffs/debuffs.dm
@@ -1,7 +1,8 @@
 //Largely negative status effects go here, even if they have small benificial effects
 //STUN EFFECTS
 /datum/status_effect/incapacitating
-	tick_interval = -1 // monkestation edit
+	id = STATUS_EFFECT_ID_ABSTRACT
+	tick_interval = STATUS_EFFECT_NO_TICK // monkestation edit
 	status_type = STATUS_EFFECT_REPLACE
 	alert_type = null
 	remove_on_fullheal = TRUE
@@ -134,7 +135,7 @@
 	if(!.)
 		return
 	if(HAS_TRAIT(owner, TRAIT_SLEEPIMMUNE))
-		tick_interval = -1
+		tick_interval = STATUS_EFFECT_NO_TICK
 	else
 		ADD_TRAIT(owner, TRAIT_KNOCKEDOUT, TRAIT_STATUS_EFFECT(id))
 	RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_SLEEPIMMUNE), PROC_REF(on_owner_insomniac))
@@ -156,7 +157,7 @@
 /datum/status_effect/incapacitating/sleeping/proc/on_owner_insomniac(mob/living/source)
 	SIGNAL_HANDLER
 	REMOVE_TRAIT(owner, TRAIT_KNOCKEDOUT, TRAIT_STATUS_EFFECT(id))
-	tick_interval = -1
+	tick_interval = STATUS_EFFECT_NO_TICK
 
 ///If the mob has the TRAIT_SLEEPIMMUNE but somehow looses it we make him sleep and restart the tick()
 /datum/status_effect/incapacitating/sleeping/proc/on_owner_sleepy(mob/living/source)
@@ -237,7 +238,7 @@
 //STASIS
 /datum/status_effect/grouped/stasis
 	id = "stasis"
-	duration = -1
+	duration = STATUS_EFFECT_PERMANENT
 	alert_type = /atom/movable/screen/alert/status_effect/stasis
 	var/last_dead_time
 
@@ -289,7 +290,7 @@
 
 /datum/status_effect/his_wrath //does minor damage over time unless holding His Grace
 	id = "his_wrath"
-	duration = -1
+	duration = STATUS_EFFECT_PERMANENT
 	tick_interval = 4
 	alert_type = /atom/movable/screen/alert/status_effect/his_wrath
 
@@ -309,7 +310,7 @@
 
 /datum/status_effect/cultghost //is a cult ghost and can't use manifest runes
 	id = "cult_ghost"
-	duration = -1
+	duration = STATUS_EFFECT_PERMANENT
 	alert_type = null
 
 /datum/status_effect/cultghost/on_apply()
@@ -386,7 +387,7 @@
 	id = "neck_slice"
 	status_type = STATUS_EFFECT_UNIQUE
 	alert_type = null
-	duration = -1
+	duration = STATUS_EFFECT_PERMANENT
 
 /datum/status_effect/neck_slice/on_apply()
 	if(!ishuman(owner))
@@ -511,7 +512,7 @@
 /datum/status_effect/gonbola_pacify
 	id = "gonbolaPacify"
 	status_type = STATUS_EFFECT_MULTIPLE
-	tick_interval = -1
+	tick_interval = STATUS_EFFECT_NO_TICK
 	alert_type = null
 
 /datum/status_effect/gonbola_pacify/on_apply()
@@ -693,9 +694,9 @@
 
 /datum/status_effect/go_away
 	id = "go_away"
-	duration = 100
+	duration = 10 SECONDS
 	status_type = STATUS_EFFECT_REPLACE
-	tick_interval = 1
+	tick_interval = 0.2 SECONDS
 	alert_type = /atom/movable/screen/alert/status_effect/go_away
 	var/direction
 
@@ -716,11 +717,11 @@
 
 /datum/status_effect/fake_virus
 	id = "fake_virus"
-	duration = 1800//3 minutes
+	duration = 3 MINUTES //3 minutes
 	status_type = STATUS_EFFECT_REPLACE
-	tick_interval = 1
+	tick_interval = 0.2 SECONDS
 	alert_type = null
-	var/msg_stage = 0//so you dont get the most intense messages immediately
+	var/msg_stage = 0//so you don't get the most intense messages immediately
 
 /datum/status_effect/fake_virus/on_apply()
 	if(HAS_TRAIT(owner, TRAIT_VIRUSIMMUNE))
@@ -937,7 +938,8 @@
 	id = "teleport_madness"
 	duration = 10 SECONDS
 	status_type = STATUS_EFFECT_REPLACE
-	tick_interval = 0.1 SECONDS
+	tick_interval = 0.2 SECONDS
+	alert_type = null
 
 /datum/status_effect/teleport_madness/tick()
 	dump_in_space(owner)
diff --git a/code/datums/status_effects/debuffs/dna_transformation.dm b/code/datums/status_effects/debuffs/dna_transformation.dm
index 1ffcc2f5929d..f38c88c3f2bb 100644
--- a/code/datums/status_effects/debuffs/dna_transformation.dm
+++ b/code/datums/status_effects/debuffs/dna_transformation.dm
@@ -2,7 +2,7 @@
 /// then turns them back to how they were before transformation.
 /datum/status_effect/temporary_transformation
 	id = "temp_dna_transformation"
-	tick_interval = -1
+	tick_interval = STATUS_EFFECT_NO_TICK
 	duration = 1 MINUTES // set in on creation, this just needs to be any value to process
 	alert_type = null
 	remove_on_fullheal = TRUE
@@ -85,7 +85,7 @@
 			return // Already paused
 
 		time_before_pause = duration - world.time
-		duration = -1
+		duration = STATUS_EFFECT_PERMANENT
 
 	// Resume if we're none of the above and also were paused
 	else if(time_before_pause != -1)
diff --git a/code/datums/status_effects/debuffs/drunk.dm b/code/datums/status_effects/debuffs/drunk.dm
index 026577fdc972..4ccc273b0b2b 100644
--- a/code/datums/status_effects/debuffs/drunk.dm
+++ b/code/datums/status_effects/debuffs/drunk.dm
@@ -15,6 +15,7 @@
 	tick_interval = 2 SECONDS
 	status_type = STATUS_EFFECT_REPLACE
 	remove_on_fullheal = TRUE
+	alert_type = null
 	/// The level of drunkness we are currently at.
 	var/drunk_value = 0
 
diff --git a/code/datums/status_effects/debuffs/fire_stacks.dm b/code/datums/status_effects/debuffs/fire_stacks.dm
index d00d0c348439..81d81deee4af 100644
--- a/code/datums/status_effects/debuffs/fire_stacks.dm
+++ b/code/datums/status_effects/debuffs/fire_stacks.dm
@@ -1,5 +1,6 @@
 /datum/status_effect/fire_handler
-	duration = -1
+	duration = STATUS_EFFECT_PERMANENT
+	id = STATUS_EFFECT_ID_ABSTRACT
 	alert_type = null
 	status_type = STATUS_EFFECT_REFRESH //Custom code
 	on_remove_on_mob_delete = TRUE
diff --git a/code/datums/status_effects/debuffs/genetic_damage.dm b/code/datums/status_effects/debuffs/genetic_damage.dm
index 438bcc7c6905..12512efd06af 100644
--- a/code/datums/status_effects/debuffs/genetic_damage.dm
+++ b/code/datums/status_effects/debuffs/genetic_damage.dm
@@ -5,7 +5,7 @@
 	id = "genetic_damage"
 	alert_type = null
 	status_type = STATUS_EFFECT_REFRESH // New effects will add to total_damage
-	duration = -1
+	duration = STATUS_EFFECT_PERMANENT
 	tick_interval = 2 SECONDS
 	on_remove_on_mob_delete = TRUE // Need to unregister from owner, be_replaced() would cause runtimes
 	remove_on_fullheal = TRUE
diff --git a/code/datums/status_effects/debuffs/hallucination.dm b/code/datums/status_effects/debuffs/hallucination.dm
index 4c5e1c305e1b..942ab9deb0ed 100644
--- a/code/datums/status_effects/debuffs/hallucination.dm
+++ b/code/datums/status_effects/debuffs/hallucination.dm
@@ -82,7 +82,7 @@
 /datum/status_effect/hallucination/sanity
 	id = "low sanity"
 	status_type = STATUS_EFFECT_REFRESH
-	duration = -1 // This lasts "forever", only goes away with sanity gain
+	duration = STATUS_EFFECT_PERMANENT // This lasts "forever", only goes away with sanity gain
 
 /datum/status_effect/hallucination/sanity/on_apply()
 	if(!owner.mob_mood)
diff --git a/code/datums/status_effects/debuffs/speech_debuffs.dm b/code/datums/status_effects/debuffs/speech_debuffs.dm
index 2b095bf6047f..607f78c64af2 100644
--- a/code/datums/status_effects/debuffs/speech_debuffs.dm
+++ b/code/datums/status_effects/debuffs/speech_debuffs.dm
@@ -1,5 +1,5 @@
 /datum/status_effect/speech
-	id = null
+	id = STATUS_EFFECT_ID_ABSTRACT
 	alert_type = null
 	remove_on_fullheal = TRUE
 
diff --git a/code/datums/status_effects/debuffs/tower_of_babel.dm b/code/datums/status_effects/debuffs/tower_of_babel.dm
index 1ba46d0b87b5..923938185d77 100644
--- a/code/datums/status_effects/debuffs/tower_of_babel.dm
+++ b/code/datums/status_effects/debuffs/tower_of_babel.dm
@@ -33,7 +33,7 @@
 // Used by wizard magic and tower of babel event
 /datum/status_effect/tower_of_babel/magical
 	id = "tower_of_babel_magic" // do we need a new id?
-	duration = -1
+	duration = STATUS_EFFECT_PERMANENT
 	trait_source = TRAUMA_TRAIT
 
 /datum/status_effect/tower_of_babel/magical/on_apply()
diff --git a/code/datums/status_effects/drug_effects.dm b/code/datums/status_effects/drug_effects.dm
index d01a92743b58..949c00d2edff 100644
--- a/code/datums/status_effects/drug_effects.dm
+++ b/code/datums/status_effects/drug_effects.dm
@@ -1,6 +1,6 @@
 /datum/status_effect/woozy
 	id = "woozy"
-	tick_interval = -1
+	tick_interval = STATUS_EFFECT_NO_TICK
 	status_type = STATUS_EFFECT_UNIQUE
 	alert_type = /atom/movable/screen/alert/status_effect/woozy
 
@@ -14,7 +14,7 @@
 
 /datum/status_effect/high_blood_pressure
 	id = "high_blood_pressure"
-	tick_interval = -1
+	tick_interval = STATUS_EFFECT_NO_TICK
 	status_type = STATUS_EFFECT_UNIQUE
 	alert_type = /atom/movable/screen/alert/status_effect/high_blood_pressure
 
@@ -40,7 +40,7 @@
 
 /datum/status_effect/seizure
 	id = "seizure"
-	tick_interval = -1
+	tick_interval = STATUS_EFFECT_NO_TICK
 	status_type = STATUS_EFFECT_UNIQUE
 	alert_type = /atom/movable/screen/alert/status_effect/seizure
 
diff --git a/code/datums/status_effects/gas.dm b/code/datums/status_effects/gas.dm
index bf0822d879f8..f7b8cf2b375b 100644
--- a/code/datums/status_effects/gas.dm
+++ b/code/datums/status_effects/gas.dm
@@ -57,7 +57,7 @@
 
 /datum/status_effect/freon/lasting
 	id = "lasting_frozen"
-	duration = -1
+	duration = STATUS_EFFECT_PERMANENT
 
 /datum/status_effect/hypernob_protection
 	id = "hypernob_protection"
diff --git a/code/datums/status_effects/grouped_effect.dm b/code/datums/status_effects/grouped_effect.dm
index ade0a187e0db..403f1c740913 100644
--- a/code/datums/status_effects/grouped_effect.dm
+++ b/code/datums/status_effects/grouped_effect.dm
@@ -1,5 +1,7 @@
 /// Status effect from multiple sources, when all sources are removed, so is the effect
 /datum/status_effect/grouped
+	id = STATUS_EFFECT_ID_ABSTRACT
+	alert_type = null
 	// Grouped effects adds itself to [var/sources] and destroys itself if one exists already, there are never actually multiple
 	status_type = STATUS_EFFECT_MULTIPLE
 	/// A list of all sources applying this status effect. Sources are a list of keys
diff --git a/code/datums/status_effects/limited_effect.dm b/code/datums/status_effects/limited_effect.dm
index 0f56e72da52f..047a7a5ae07c 100644
--- a/code/datums/status_effects/limited_effect.dm
+++ b/code/datums/status_effects/limited_effect.dm
@@ -1,8 +1,9 @@
 /// These effects reapply their on_apply() effect when refreshed while stacks < max_stacks.
 /datum/status_effect/limited_buff
 	id = "limited_buff"
-	duration = -1
+	duration = STATUS_EFFECT_PERMANENT
 	status_type = STATUS_EFFECT_REFRESH
+	alert_type = null
 	///How many stacks we currently have
 	var/stacks = 1
 	///How many stacks we can have maximum
diff --git a/code/datums/status_effects/neutral.dm b/code/datums/status_effects/neutral.dm
index a92823293182..062108241f86 100644
--- a/code/datums/status_effects/neutral.dm
+++ b/code/datums/status_effects/neutral.dm
@@ -2,8 +2,8 @@
 
 /datum/status_effect/crusher_damage //tracks the damage dealt to this mob by kinetic crushers
 	id = "crusher_damage"
-	duration = -1
-	tick_interval = -1
+	duration = STATUS_EFFECT_PERMANENT
+	tick_interval = STATUS_EFFECT_NO_TICK
 	status_type = STATUS_EFFECT_UNIQUE
 	alert_type = null
 	var/total_damage = 0
@@ -46,7 +46,7 @@
 
 /datum/status_effect/in_love
 	id = "in_love"
-	duration = -1
+	duration = STATUS_EFFECT_PERMANENT
 	status_type = STATUS_EFFECT_UNIQUE
 	alert_type = /atom/movable/screen/alert/status_effect/in_love
 	var/hearts
@@ -84,6 +84,7 @@
 /datum/status_effect/bounty
 	id = "bounty"
 	status_type = STATUS_EFFECT_UNIQUE
+	alert_type = null
 	var/mob/living/rewarded
 
 /datum/status_effect/bounty/on_creation(mob/living/new_owner, mob/living/caster)
@@ -118,8 +119,8 @@
 // heldup is for the person being aimed at
 /datum/status_effect/grouped/heldup
 	id = "heldup"
-	duration = -1
-	tick_interval = -1
+	duration = STATUS_EFFECT_PERMANENT
+	tick_interval = STATUS_EFFECT_NO_TICK
 	status_type = STATUS_EFFECT_MULTIPLE
 	alert_type = /atom/movable/screen/alert/status_effect/heldup
 
@@ -139,8 +140,8 @@
 // holdup is for the person aiming
 /datum/status_effect/holdup
 	id = "holdup"
-	duration = -1
-	tick_interval = -1
+	duration = STATUS_EFFECT_PERMANENT
+	tick_interval = STATUS_EFFECT_NO_TICK
 	status_type = STATUS_EFFECT_UNIQUE
 	alert_type = /atom/movable/screen/alert/status_effect/holdup
 
@@ -152,8 +153,8 @@
 // this status effect is used to negotiate the high-fiving capabilities of all concerned parties
 /datum/status_effect/offering
 	id = "offering"
-	duration = -1
-	tick_interval = -1
+	duration = STATUS_EFFECT_PERMANENT
+	tick_interval = STATUS_EFFECT_NO_TICK
 	status_type = STATUS_EFFECT_UNIQUE
 	alert_type = null
 	/// The people who were offered this item at the start
@@ -315,8 +316,8 @@
 //this effect gives the user an alert they can use to surrender quickly
 /datum/status_effect/grouped/surrender
 	id = "surrender"
-	duration = -1
-	tick_interval = -1
+	duration = STATUS_EFFECT_PERMANENT
+	tick_interval = STATUS_EFFECT_NO_TICK
 	status_type = STATUS_EFFECT_UNIQUE
 	alert_type = /atom/movable/screen/alert/status_effect/surrender
 
@@ -344,7 +345,7 @@
 /datum/status_effect/caltropped
 	id = "caltropped"
 	duration = 1 SECONDS
-	tick_interval = -1 // monkestation edit
+	tick_interval = STATUS_EFFECT_NO_TICK // monkestation edit
 	status_type = STATUS_EFFECT_REFRESH
 	alert_type = null
 
diff --git a/code/datums/status_effects/stacking_effect.dm b/code/datums/status_effects/stacking_effect.dm
index 783d9334b215..5cc598f8710c 100644
--- a/code/datums/status_effects/stacking_effect.dm
+++ b/code/datums/status_effects/stacking_effect.dm
@@ -1,8 +1,8 @@
 
 /// Status effects that can stack.
 /datum/status_effect/stacking
-	id = "stacking_base"
-	duration = -1 // Only removed under specific conditions.
+	id = STATUS_EFFECT_ID_ABSTRACT
+	duration = STATUS_EFFECT_PERMANENT // Only removed under specific conditions.
 	tick_interval = 1 SECONDS // Deciseconds between decays, once decay starts
 	alert_type = null
 	/// How many stacks are currently accumulated.
diff --git a/code/datums/status_effects/wound_effects.dm b/code/datums/status_effects/wound_effects.dm
index e1929bbdc26b..42843ffbbd46 100644
--- a/code/datums/status_effects/wound_effects.dm
+++ b/code/datums/status_effects/wound_effects.dm
@@ -72,7 +72,7 @@
 	id = "determination_crash"
 	alert_type = null
 	remove_on_fullheal = TRUE
-	tick_interval = -1
+	tick_interval = STATUS_EFFECT_NO_TICK
 	duration = 10 SECONDS
 
 /datum/status_effect/determination_crash/on_apply()
@@ -101,7 +101,7 @@
 /datum/status_effect/limp
 	id = "limp"
 	status_type = STATUS_EFFECT_REPLACE
-	tick_interval = -1 // monkestation edit
+	tick_interval = STATUS_EFFECT_NO_TICK // monkestation edit
 	on_remove_on_mob_delete = TRUE
 	alert_type = /atom/movable/screen/alert/status_effect/limp
 	var/msg_stage = 0//so you dont get the most intense messages immediately
@@ -216,6 +216,7 @@
 	id = "wound"
 	status_type = STATUS_EFFECT_MULTIPLE
 	on_remove_on_mob_delete = TRUE
+	alert_type = null
 	var/obj/item/bodypart/linked_limb
 	var/datum/wound/linked_wound
 
diff --git a/code/modules/antagonists/heretic/magic/ash_ascension.dm b/code/modules/antagonists/heretic/magic/ash_ascension.dm
index c9b6b2ce0458..61b8b66dc403 100644
--- a/code/modules/antagonists/heretic/magic/ash_ascension.dm
+++ b/code/modules/antagonists/heretic/magic/ash_ascension.dm
@@ -33,7 +33,7 @@
 /// Simple status effect for adding a ring of fire around a mob.
 /datum/status_effect/fire_ring
 	id = "fire_ring"
-	tick_interval = 0.1 SECONDS
+	tick_interval = 0.2 SECONDS
 	status_type = STATUS_EFFECT_REFRESH
 	alert_type = null
 	/// The radius of the ring around us.
diff --git a/code/modules/antagonists/heretic/magic/shadow_cloak.dm b/code/modules/antagonists/heretic/magic/shadow_cloak.dm
index ad942c71a328..3aa0b68c7c78 100644
--- a/code/modules/antagonists/heretic/magic/shadow_cloak.dm
+++ b/code/modules/antagonists/heretic/magic/shadow_cloak.dm
@@ -126,7 +126,7 @@
 /datum/status_effect/shadow_cloak
 	id = "shadow_cloak"
 	alert_type = null
-	tick_interval = -1
+	tick_interval = STATUS_EFFECT_NO_TICK
 	/// How much damage we've been hit with
 	var/damage_sustained = 0
 	/// How much damage we can be hit with before it starts rolling reveal chances
diff --git a/code/modules/antagonists/heretic/magic/star_touch.dm b/code/modules/antagonists/heretic/magic/star_touch.dm
index bab07f0871bd..6095dcd01a32 100644
--- a/code/modules/antagonists/heretic/magic/star_touch.dm
+++ b/code/modules/antagonists/heretic/magic/star_touch.dm
@@ -116,7 +116,7 @@
 
 /datum/status_effect/cosmic_beam
 	id = "cosmic_beam"
-	tick_interval = 0.1 SECONDS
+	tick_interval = 0.2 SECONDS
 	duration = 1 MINUTES
 	status_type = STATUS_EFFECT_REPLACE
 	alert_type = null
diff --git a/code/modules/antagonists/heretic/status_effects/buffs.dm b/code/modules/antagonists/heretic/status_effects/buffs.dm
index 46f1d65acfd2..667668ae1bc7 100644
--- a/code/modules/antagonists/heretic/status_effects/buffs.dm
+++ b/code/modules/antagonists/heretic/status_effects/buffs.dm
@@ -109,7 +109,7 @@
 	id = "Silver Knives"
 	alert_type = null
 	status_type = STATUS_EFFECT_MULTIPLE
-	tick_interval = -1
+	tick_interval = STATUS_EFFECT_NO_TICK
 	/// The number of blades we summon up to.
 	var/max_num_blades = 4
 	/// The radius of the blade's orbit.
@@ -242,7 +242,7 @@
 /datum/status_effect/caretaker_refuge
 	id = "Caretaker’s Last Refuge"
 	status_type = STATUS_EFFECT_REFRESH
-	duration = -1
+	duration = STATUS_EFFECT_PERMANENT
 	alert_type = null
 	var/static/list/caretaking_traits = list(TRAIT_HANDS_BLOCKED, TRAIT_IGNORESLOWDOWN, TRAIT_SECLUDED_LOCATION)
 
diff --git a/code/modules/antagonists/heretic/status_effects/debuffs.dm b/code/modules/antagonists/heretic/status_effects/debuffs.dm
index 1088f846525f..9050cb97d247 100644
--- a/code/modules/antagonists/heretic/status_effects/debuffs.dm
+++ b/code/modules/antagonists/heretic/status_effects/debuffs.dm
@@ -31,7 +31,7 @@
 
 /datum/status_effect/void_chill/lasting
 	id = "lasting_void_chill"
-	duration = -1
+	duration = STATUS_EFFECT_PERMANENT
 
 /datum/movespeed_modifier/void_chill
 	multiplicative_slowdown = 0.3
@@ -199,7 +199,7 @@
 	alert_type = /atom/movable/screen/alert/status_effect/heretic_lastresort
 	duration = 12 SECONDS
 	status_type = STATUS_EFFECT_REPLACE
-	tick_interval = -1
+	tick_interval = STATUS_EFFECT_NO_TICK
 
 /atom/movable/screen/alert/status_effect/heretic_lastresort
 	name = "Last Resort"
@@ -219,7 +219,7 @@
 /datum/status_effect/moon_converted
 	id = "moon converted"
 	alert_type = /atom/movable/screen/alert/status_effect/moon_converted
-	duration = -1
+	duration = STATUS_EFFECT_PERMANENT
 	status_type = STATUS_EFFECT_REPLACE
 	///used to track damage
 	var/damage_sustained = 0
diff --git a/code/modules/antagonists/heretic/status_effects/ghoul.dm b/code/modules/antagonists/heretic/status_effects/ghoul.dm
index ff730573da59..4f0d89ee815c 100644
--- a/code/modules/antagonists/heretic/status_effects/ghoul.dm
+++ b/code/modules/antagonists/heretic/status_effects/ghoul.dm
@@ -1,7 +1,7 @@
 /datum/status_effect/ghoul
 	id = "ghoul"
 	status_type = STATUS_EFFECT_UNIQUE
-	duration = -1
+	duration = STATUS_EFFECT_PERMANENT
 	alert_type = /atom/movable/screen/alert/status_effect/ghoul
 	/// The new max health value set for the ghoul, if supplied
 	var/new_max_health
diff --git a/code/modules/mining/equipment/monster_organs/brimdust_sac.dm b/code/modules/mining/equipment/monster_organs/brimdust_sac.dm
index a77e526a9d1f..62a3acdb4472 100644
--- a/code/modules/mining/equipment/monster_organs/brimdust_sac.dm
+++ b/code/modules/mining/equipment/monster_organs/brimdust_sac.dm
@@ -71,7 +71,7 @@
 	id = "brimdust_coating"
 	stacks = 0
 	max_stacks = 3
-	tick_interval = -1
+	tick_interval = STATUS_EFFECT_NO_TICK
 	consumed_on_threshold = FALSE
 	alert_type = /atom/movable/screen/alert/status_effect/brimdust_coating
 	status_type = STATUS_EFFECT_REFRESH // Allows us to add one stack at a time by just applying the effect
diff --git a/code/modules/mob/living/basic/lavaland/basilisk/basilisk_overheat.dm b/code/modules/mob/living/basic/lavaland/basilisk/basilisk_overheat.dm
index c0b49fbdc619..71e938c5164e 100644
--- a/code/modules/mob/living/basic/lavaland/basilisk/basilisk_overheat.dm
+++ b/code/modules/mob/living/basic/lavaland/basilisk/basilisk_overheat.dm
@@ -2,6 +2,7 @@
 /datum/status_effect/basilisk_overheat
 	id = "basilisk_overheat"
 	duration = 3 MINUTES
+	alert_type = null
 	/// Things which will chill us out if we get hit by them
 	var/static/list/chilling_reagents = list(
 		/datum/reagent/medicine/cryoxadone,
diff --git a/code/modules/mob/living/basic/space_fauna/revenant/revenant_effects.dm b/code/modules/mob/living/basic/space_fauna/revenant/revenant_effects.dm
index 0eeec231973e..41be027797fd 100644
--- a/code/modules/mob/living/basic/space_fauna/revenant/revenant_effects.dm
+++ b/code/modules/mob/living/basic/space_fauna/revenant/revenant_effects.dm
@@ -1,5 +1,7 @@
 /// Parent type for all unique revenant status effects
 /datum/status_effect/revenant
+	id = STATUS_EFFECT_ID_ABSTRACT
+	alert_type = null
 
 /datum/status_effect/revenant/on_creation(mob/living/new_owner, duration)
 	if(isnum(duration))
diff --git a/code/modules/mob/living/status_procs.dm b/code/modules/mob/living/status_procs.dm
index 3ee50b9d16cf..3c9ad7ea9fed 100644
--- a/code/modules/mob/living/status_procs.dm
+++ b/code/modules/mob/living/status_procs.dm
@@ -457,7 +457,7 @@
 		return
 	var/datum/status_effect/incapacitating/sleeping/S = IsSleeping()
 	if(S)
-		S.duration = -1
+		S.duration = STATUS_EFFECT_PERMANENT
 	else
 		S = apply_status_effect(/datum/status_effect/incapacitating/sleeping, -1)
 	return S
diff --git a/code/modules/religion/burdened/psyker.dm b/code/modules/religion/burdened/psyker.dm
index e913584081cd..e9cf7793d8fd 100644
--- a/code/modules/religion/burdened/psyker.dm
+++ b/code/modules/religion/burdened/psyker.dm
@@ -270,7 +270,7 @@
 	id = "psychic_projection"
 	alert_type = null
 	remove_on_fullheal = TRUE
-	tick_interval = 0.1 SECONDS
+	tick_interval = 0.2 SECONDS
 	/// Times the target has dry fired a weapon.
 	var/times_dry_fired = 0
 	/// Needs to reach times_dry_fired for the next dry fire to happen.
diff --git a/code/modules/religion/hunt/hunter_pinpointer.dm b/code/modules/religion/hunt/hunter_pinpointer.dm
index d2a69d1d57f5..a2312b265a9f 100644
--- a/code/modules/religion/hunt/hunter_pinpointer.dm
+++ b/code/modules/religion/hunt/hunter_pinpointer.dm
@@ -15,7 +15,7 @@
 	alert_type = /atom/movable/screen/alert/status_effect/agent_pinpointer/hunters_sense
 	minimum_range = HUNTER_MINIMUM_RANGE
 	tick_interval = HUNTER_PING_TIME
-	duration = -1
+	duration = STATUS_EFFECT_PERMANENT
 	range_fuzz_factor = HUNTER_FUZZ_FACTOR
 
 ///Attempting to locate a nearby target to scan and point towards.
diff --git a/code/modules/research/xenobiology/crossbreeding/_status_effects.dm b/code/modules/research/xenobiology/crossbreeding/_status_effects.dm
index 67c92ea362ca..717e56196e88 100644
--- a/code/modules/research/xenobiology/crossbreeding/_status_effects.dm
+++ b/code/modules/research/xenobiology/crossbreeding/_status_effects.dm
@@ -61,8 +61,8 @@
 
 /datum/status_effect/slimerecall
 	id = "slime_recall"
-	duration = -1 //Will be removed by the extract.
-	tick_interval = -1
+	duration = STATUS_EFFECT_PERMANENT //Will be removed by the extract.
+	tick_interval = STATUS_EFFECT_NO_TICK
 	alert_type = null
 	var/interrupted = FALSE
 	var/mob/target
@@ -98,7 +98,7 @@
 /datum/status_effect/frozenstasis
 	id = "slime_frozen"
 	status_type = STATUS_EFFECT_UNIQUE
-	duration = -1 //Will remove self when block breaks.
+	duration = STATUS_EFFECT_PERMANENT //Will remove self when block breaks.
 	alert_type = /atom/movable/screen/alert/status_effect/freon/stasis
 	var/obj/structure/ice_stasis/cube
 
@@ -127,7 +127,7 @@
 /datum/status_effect/slime_clone
 	id = "slime_cloned"
 	status_type = STATUS_EFFECT_UNIQUE
-	duration = -1
+	duration = STATUS_EFFECT_PERMANENT
 	alert_type = null
 	var/mob/living/clone
 	var/datum/mind/originalmind //For when the clone gibs.
@@ -172,7 +172,7 @@
 /datum/status_effect/slime_clone_decay
 	id = "slime_clonedecay"
 	status_type = STATUS_EFFECT_UNIQUE
-	duration = -1
+	duration = STATUS_EFFECT_PERMANENT
 	alert_type = /atom/movable/screen/alert/status_effect/clone_decay
 
 /datum/status_effect/slime_clone_decay/tick()
@@ -229,7 +229,7 @@
 
 /datum/status_effect/rebreathing
 	id = "rebreathing"
-	duration = -1
+	duration = STATUS_EFFECT_PERMANENT
 	alert_type = null
 
 /datum/status_effect/rebreathing/tick()
@@ -439,7 +439,7 @@
 
 /datum/status_effect/stabilized //The base stabilized extract effect, has no effect of its' own.
 	id = "stabilizedbase"
-	duration = -1
+	duration = STATUS_EFFECT_PERMANENT
 	alert_type = null
 	/// Item which provides this buff
 	var/obj/item/slimecross/stabilized/linked_extract
@@ -830,7 +830,7 @@
 
 /datum/status_effect/pinkdamagetracker
 	id = "pinkdamagetracker"
-	duration = -1
+	duration = STATUS_EFFECT_PERMANENT
 	alert_type = null
 	var/damage = 0
 	var/lasthealth
diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm
index c662ec1f4b74..44e3e0078541 100644
--- a/code/modules/unit_tests/_unit_tests.dm
+++ b/code/modules/unit_tests/_unit_tests.dm
@@ -219,6 +219,7 @@
 #include "spritesheets.dm"
 #include "stack_singular_name.dm"
 #include "station_trait_tests.dm"
+#include "status_effect_validity.dm"
 #include "stomach.dm"
 #include "strange_reagent.dm"
 #include "strippable.dm"
diff --git a/code/modules/unit_tests/status_effect_validity.dm b/code/modules/unit_tests/status_effect_validity.dm
new file mode 100644
index 000000000000..29d48c69c6bf
--- /dev/null
+++ b/code/modules/unit_tests/status_effect_validity.dm
@@ -0,0 +1,61 @@
+/// Validates status effect tick interval setup
+/datum/unit_test/status_effect_ticks
+
+/datum/unit_test/status_effect_ticks/Run()
+	for(var/datum/status_effect/checking as anything in subtypesof(/datum/status_effect))
+		if(initial(checking.id) == STATUS_EFFECT_ID_ABSTRACT)
+			continue
+		var/tick_speed = initial(checking.tick_interval)
+		if(tick_speed == STATUS_EFFECT_NO_TICK)
+			continue
+		if(tick_speed == INFINITY)
+			TEST_FAIL("Status effect [checking] has tick_interval set to INFINITY, this is not how you prevent ticks - use tick_interval = STATUS_EFFECT_NO_TICK instead.")
+			continue
+		if(tick_speed == 0)
+			TEST_FAIL("Status effect [checking] has tick_interval set to 0, this is not how you prevent ticks - use tick_interval = STATUS_EFFECT_NO_TICK instead.")
+			continue
+		switch(initial(checking.processing_speed))
+			if(STATUS_EFFECT_FAST_PROCESS, STATUS_EFFECT_PRIORITY) // monkestation edit: STATUS_EFFECT_PRIORITY
+				if(tick_speed < SSfastprocess.wait)
+					TEST_FAIL("Status effect [checking] has tick_interval set to [tick_speed], which is faster than SSfastprocess can tick ([SSfastprocess.wait]).")
+			if(STATUS_EFFECT_NORMAL_PROCESS)
+				if(tick_speed < SSprocessing.wait)
+					TEST_FAIL("Status effect [checking] has tick_interval set to [tick_speed], which is faster than SSprocessing can tick ([SSprocessing.wait]).")
+			else
+				TEST_FAIL("Invalid processing speed for status effect [checking] : [initial(checking.processing_speed)]")
+
+/// Validates status effect alert type setup
+/datum/unit_test/status_effect_alert
+
+/datum/unit_test/status_effect_alert/Run()
+	// The base typepath is used to indicate "I didn't set an alert type"
+	var/bad_alert_type = /datum/status_effect::alert_type
+	TEST_ASSERT_NOTNULL(bad_alert_type, "No alert type defined in /datum/status_effect - This test may be redundant now.")
+
+	for(var/datum/status_effect/checking as anything in subtypesof(/datum/status_effect))
+		if(initial(checking.id) == STATUS_EFFECT_ID_ABSTRACT)
+			continue
+		if(initial(checking.alert_type) != bad_alert_type)
+			continue
+		TEST_FAIL("[checking] has not set alert_type. If you don't want an alert, set alert_type = null - \
+			Otherwise, give it an alert subtype.")
+
+/// Validates status effect id setup
+/datum/unit_test/status_effect_ids
+
+/datum/unit_test/status_effect_ids/Run()
+	// The base id is used to indicate "I didn't set an id"
+	var/bad_id = /datum/status_effect::id
+	TEST_ASSERT_NOTNULL(bad_id, "No id defined in /datum/status_effect - This test may be redundant now.")
+
+	for(var/datum/status_effect/checking as anything in subtypesof(/datum/status_effect))
+		if(initial(checking.id) == STATUS_EFFECT_ID_ABSTRACT)
+			// we are just assuming that a child of an abstract should not be abstract.
+			// of course in practice, this may not always be the case - but if you're
+			// structuring a status effect like this, you can just change the parent id to anything else
+			var/datum/status_effect/checking_parent = initial(checking.parent_type)
+			if(initial(checking_parent.id) != STATUS_EFFECT_ID_ABSTRACT)
+				continue
+		if(initial(checking.id) != bad_id)
+			continue
+		TEST_FAIL("[checking] has not set an id. This is required for status effects.")
diff --git a/monkestation/code/datums/status_effects/buffs.dm b/monkestation/code/datums/status_effects/buffs.dm
index ab6bd0eb4ad7..e5da8e526408 100644
--- a/monkestation/code/datums/status_effects/buffs.dm
+++ b/monkestation/code/datums/status_effects/buffs.dm
@@ -18,7 +18,7 @@
 
 /datum/status_effect/mayhem
 	show_duration = TRUE
-	tick_interval = 0 // Just pass me the SSfastprocess ticks please.
+	tick_interval = STATUS_EFFECT_NO_TICK // Just pass me the SSfastprocess ticks please.
 
 	alert_type = /atom/movable/screen/alert/status_effect/mayhem
 
diff --git a/monkestation/code/datums/status_effects/changeling.dm b/monkestation/code/datums/status_effects/changeling.dm
index 67bbb18c5fe0..1d25b4328871 100644
--- a/monkestation/code/datums/status_effects/changeling.dm
+++ b/monkestation/code/datums/status_effects/changeling.dm
@@ -19,7 +19,7 @@
 	id = "changeling_adrenaline"
 	duration = 20 SECONDS
 	show_duration = TRUE
-	tick_interval = 0
+	tick_interval = 0.2 SECONDS
 	alert_type = /atom/movable/screen/alert/status_effect/changeling/adrenaline
 	status_type = STATUS_EFFECT_REFRESH
 
@@ -69,7 +69,7 @@
 	id = "changeling_panacea"
 	duration = 1 MINUTE
 	show_duration = TRUE
-	tick_interval = 0
+	tick_interval = 0.2 SECONDS
 	alert_type = /atom/movable/screen/alert/status_effect/changeling/panacea
 	status_type = STATUS_EFFECT_REFRESH
 
@@ -158,7 +158,7 @@
 
 /datum/status_effect/changeling_muscles
 	id = "changeling_muscles"
-	tick_interval = 0
+	tick_interval = 1 SECONDS
 	processing_speed = STATUS_EFFECT_NORMAL_PROCESS
 	alert_type = /atom/movable/screen/alert/status_effect/changeling/muscles
 
diff --git a/monkestation/code/datums/status_effects/food_buffs.dm b/monkestation/code/datums/status_effects/food_buffs.dm
index 44ae8e55d659..566fc6f05415 100644
--- a/monkestation/code/datums/status_effects/food_buffs.dm
+++ b/monkestation/code/datums/status_effects/food_buffs.dm
@@ -1,9 +1,9 @@
 /datum/status_effect/food
+	id = STATUS_EFFECT_ID_ABSTRACT
 	duration = 10 MINUTES
 	status_type = STATUS_EFFECT_REPLACE
 	show_duration = TRUE
 
-
 /datum/status_effect/food/proc/apply_quality(quality)
 	return
 
diff --git a/monkestation/code/modules/a_medical_day/lungless.dm b/monkestation/code/modules/a_medical_day/lungless.dm
index 3611d366aa00..73da7035d906 100644
--- a/monkestation/code/modules/a_medical_day/lungless.dm
+++ b/monkestation/code/modules/a_medical_day/lungless.dm
@@ -1,8 +1,8 @@
 /datum/status_effect/lungless
 	id = "no_lungs"
 	alert_type = null
-	duration = -1
-	tick_interval = -1
+	duration = STATUS_EFFECT_PERMANENT
+	tick_interval = STATUS_EFFECT_NO_TICK
 
 /datum/status_effect/lungless/on_apply()
 	if(!iscarbon(owner))
diff --git a/monkestation/code/modules/antagonists/borers/code/status_effects.dm b/monkestation/code/modules/antagonists/borers/code/status_effects.dm
index edfca2eff0ec..060702ba563e 100644
--- a/monkestation/code/modules/antagonists/borers/code/status_effects.dm
+++ b/monkestation/code/modules/antagonists/borers/code/status_effects.dm
@@ -1,6 +1,6 @@
 /datum/status_effect/borer_sugar
 	id = "borer_sugar"
-	tick_interval = -1
+	tick_interval = STATUS_EFFECT_NO_TICK
 	status_type = STATUS_EFFECT_UNIQUE
 	alert_type = /atom/movable/screen/alert/status_effect/borer_sugar
 
diff --git a/monkestation/code/modules/antagonists/clock_cult/status_effects.dm b/monkestation/code/modules/antagonists/clock_cult/status_effects.dm
index 4243d4518a2a..5014a9514d85 100644
--- a/monkestation/code/modules/antagonists/clock_cult/status_effects.dm
+++ b/monkestation/code/modules/antagonists/clock_cult/status_effects.dm
@@ -2,7 +2,7 @@
 	id = "interdicted"
 	duration = 2.5 SECONDS
 	status_type = STATUS_EFFECT_REFRESH
-	tick_interval = 1
+	tick_interval = 0.2 SECONDS
 	alert_type = /atom/movable/screen/alert/status_effect/interdiction
 	/// If we kicked the owner out of running mode
 	var/running_toggled = FALSE
diff --git a/monkestation/code/modules/antagonists/monster_hunters/events/wonderland_apocalypse.dm b/monkestation/code/modules/antagonists/monster_hunters/events/wonderland_apocalypse.dm
index def42a7b2b9b..5627a3fe72b6 100644
--- a/monkestation/code/modules/antagonists/monster_hunters/events/wonderland_apocalypse.dm
+++ b/monkestation/code/modules/antagonists/monster_hunters/events/wonderland_apocalypse.dm
@@ -123,7 +123,7 @@
 /datum/status_effect/wonderland_district
 	id = "wonderland_district"
 	alert_type = /atom/movable/screen/alert/status_effect/wonderland_district
-	tick_interval = -1
+	tick_interval = STATUS_EFFECT_NO_TICK
 	/// List of /datum/action instance that we've registered `COMSIG_ACTION_TRIGGER` on.
 	var/list/datum/action/registered_actions
 	/// Typecache of spells to NOT trigger the effect on.
diff --git a/monkestation/code/modules/antagonists/monster_hunters/tools/bnuuy_mask.dm b/monkestation/code/modules/antagonists/monster_hunters/tools/bnuuy_mask.dm
index 25694199785d..751d1a544fb5 100644
--- a/monkestation/code/modules/antagonists/monster_hunters/tools/bnuuy_mask.dm
+++ b/monkestation/code/modules/antagonists/monster_hunters/tools/bnuuy_mask.dm
@@ -55,7 +55,7 @@
 /datum/status_effect/bnuuy_mask
 	id = "bnuuy_mask"
 	alert_type = null
-	tick_interval = -1
+	tick_interval = STATUS_EFFECT_NO_TICK
 	var/datum/component/glitching_state/wondershift
 
 /datum/status_effect/bnuuy_mask/on_apply()
diff --git a/monkestation/code/modules/antagonists/monster_hunters/weapons/hunter_revolver.dm b/monkestation/code/modules/antagonists/monster_hunters/weapons/hunter_revolver.dm
index 7bf7932ff9cd..ac89464b2049 100644
--- a/monkestation/code/modules/antagonists/monster_hunters/weapons/hunter_revolver.dm
+++ b/monkestation/code/modules/antagonists/monster_hunters/weapons/hunter_revolver.dm
@@ -39,7 +39,7 @@
 /datum/status_effect/silver_bullet
 	id = "silver_bullet"
 	duration = 8 SECONDS
-	tick_interval = -1
+	tick_interval = STATUS_EFFECT_NO_TICK
 	status_type = STATUS_EFFECT_REFRESH
 	alert_type = /atom/movable/screen/alert/status_effect/silver_bullet
 
diff --git a/monkestation/code/modules/art_sci_overrides/artifact_effects/false_rod.dm b/monkestation/code/modules/art_sci_overrides/artifact_effects/false_rod.dm
index 27030a251101..26c4c0501e65 100644
--- a/monkestation/code/modules/art_sci_overrides/artifact_effects/false_rod.dm
+++ b/monkestation/code/modules/art_sci_overrides/artifact_effects/false_rod.dm
@@ -44,7 +44,7 @@
 /datum/status_effect/forced_oath
 	id = "Forced Oath"
 	status_type = STATUS_EFFECT_UNIQUE
-	duration = -1
+	duration = STATUS_EFFECT_PERMANENT
 	tick_interval = 25
 	alert_type = null
 	var/datum/component/aura_healing/our_aura
diff --git a/monkestation/code/modules/bloodsuckers/bloodsucker/bloodsucker_frenzy.dm b/monkestation/code/modules/bloodsuckers/bloodsucker/bloodsucker_frenzy.dm
index 0902dafbbf26..325ec0297d74 100644
--- a/monkestation/code/modules/bloodsuckers/bloodsucker/bloodsucker_frenzy.dm
+++ b/monkestation/code/modules/bloodsuckers/bloodsucker/bloodsucker_frenzy.dm
@@ -31,7 +31,7 @@
 /datum/status_effect/frenzy
 	id = "Frenzy"
 	status_type = STATUS_EFFECT_UNIQUE
-	duration = -1
+	duration = STATUS_EFFECT_PERMANENT
 	tick_interval = 1 SECONDS
 	alert_type = /atom/movable/screen/alert/status_effect/frenzy
 	/// The stored Bloodsucker antag datum
diff --git a/monkestation/code/modules/bloodsuckers/bloodsucker/bloodsucker_sol.dm b/monkestation/code/modules/bloodsuckers/bloodsucker/bloodsucker_sol.dm
index e953d8822cde..51feae965738 100644
--- a/monkestation/code/modules/bloodsuckers/bloodsucker/bloodsucker_sol.dm
+++ b/monkestation/code/modules/bloodsuckers/bloodsucker/bloodsucker_sol.dm
@@ -153,7 +153,7 @@
 
 /datum/status_effect/bloodsucker_sol
 	id = "bloodsucker_sol"
-	tick_interval = -1
+	tick_interval = STATUS_EFFECT_NO_TICK
 	alert_type = /atom/movable/screen/alert/status_effect/bloodsucker_sol
 	var/list/datum/action/cooldown/bloodsucker/burdened_actions
 	var/static/list/sol_traits = list(
diff --git a/monkestation/code/modules/bloodsuckers/powers/masquerade.dm b/monkestation/code/modules/bloodsuckers/powers/masquerade.dm
index 0d3737d97c40..840fcb35487e 100644
--- a/monkestation/code/modules/bloodsuckers/powers/masquerade.dm
+++ b/monkestation/code/modules/bloodsuckers/powers/masquerade.dm
@@ -79,8 +79,8 @@
 
 /datum/status_effect/masquerade
 	id = "masquerade"
-	duration = -1
-	tick_interval = -1
+	duration = STATUS_EFFECT_PERMANENT
+	tick_interval = STATUS_EFFECT_NO_TICK
 	alert_type = /atom/movable/screen/alert/status_effect/masquerade
 
 /atom/movable/screen/alert/status_effect/masquerade
diff --git a/monkestation/code/modules/bloodsuckers/vassals/vassal_pinpointer.dm b/monkestation/code/modules/bloodsuckers/vassals/vassal_pinpointer.dm
index 791b9a8f9f5f..4d0c6b00f213 100644
--- a/monkestation/code/modules/bloodsuckers/vassals/vassal_pinpointer.dm
+++ b/monkestation/code/modules/bloodsuckers/vassals/vassal_pinpointer.dm
@@ -14,7 +14,7 @@
 	alert_type = /atom/movable/screen/alert/status_effect/agent_pinpointer/vassal_edition
 	minimum_range = VASSAL_SCAN_MIN_DISTANCE
 	tick_interval = VASSAL_SCAN_PING_TIME
-	duration = -1
+	duration = STATUS_EFFECT_PERMANENT
 	range_fuzz_factor = 0
 
 /datum/status_effect/agent_pinpointer/vassal_edition/on_creation(mob/living/new_owner, ...)
diff --git a/monkestation/code/modules/blueshift/species/ashwalker.dm b/monkestation/code/modules/blueshift/species/ashwalker.dm
index 773eb4b40f2c..4c7a8ec617b5 100644
--- a/monkestation/code/modules/blueshift/species/ashwalker.dm
+++ b/monkestation/code/modules/blueshift/species/ashwalker.dm
@@ -117,8 +117,8 @@
 
 /datum/status_effect/ashwalker_damage //tracks the damage dealt to this mob by ashwalkers
 	id = "ashwalker_damage"
-	duration = -1
-	tick_interval = -1
+	duration = STATUS_EFFECT_PERMANENT
+	tick_interval = STATUS_EFFECT_NO_TICK
 	status_type = STATUS_EFFECT_UNIQUE
 	alert_type = null
 	/// How much damage has been dealt to the mob
diff --git a/monkestation/code/modules/can_spessmen_feel_pain/pain/status_effects/low_blood_pressure.dm b/monkestation/code/modules/can_spessmen_feel_pain/pain/status_effects/low_blood_pressure.dm
index 30b1d0f40592..a42c1259fad9 100644
--- a/monkestation/code/modules/can_spessmen_feel_pain/pain/status_effects/low_blood_pressure.dm
+++ b/monkestation/code/modules/can_spessmen_feel_pain/pain/status_effects/low_blood_pressure.dm
@@ -1,6 +1,6 @@
 /datum/status_effect/low_blood_pressure
 	id = "low_blood_pressure"
-	tick_interval = -1
+	tick_interval = STATUS_EFFECT_NO_TICK
 	status_type = STATUS_EFFECT_UNIQUE
 	alert_type = /atom/movable/screen/alert/status_effect/low_blood_pressure
 
diff --git a/monkestation/code/modules/datums/status_effects/debuffs.dm b/monkestation/code/modules/datums/status_effects/debuffs.dm
index 60b8f0587b4e..094d18bd5142 100644
--- a/monkestation/code/modules/datums/status_effects/debuffs.dm
+++ b/monkestation/code/modules/datums/status_effects/debuffs.dm
@@ -3,6 +3,9 @@
 	desc = "You've been hit with an EMP! You're malfunctioning!"
 	icon_state = "hypnosis"
 
+/datum/status_effect/ipc
+	id = STATUS_EFFECT_ID_ABSTRACT
+
 /datum/status_effect/ipc/emp
 	id = "ipc_emp"
 	duration = 120 SECONDS
diff --git a/monkestation/code/modules/ghost_players/centcom_grace.dm b/monkestation/code/modules/ghost_players/centcom_grace.dm
index d97f50637089..ade5feb4e73a 100644
--- a/monkestation/code/modules/ghost_players/centcom_grace.dm
+++ b/monkestation/code/modules/ghost_players/centcom_grace.dm
@@ -1,6 +1,6 @@
 /datum/status_effect/centcom_grace
 	id = "centcom_grace"
-	tick_interval = -1
+	tick_interval = STATUS_EFFECT_NO_TICK
 	alert_type = null
 	var/last_active = FALSE
 
diff --git a/monkestation/code/modules/liquids/liquid_status_effect.dm b/monkestation/code/modules/liquids/liquid_status_effect.dm
index 514667fc0272..9b31a66d3da3 100644
--- a/monkestation/code/modules/liquids/liquid_status_effect.dm
+++ b/monkestation/code/modules/liquids/liquid_status_effect.dm
@@ -1,7 +1,7 @@
 /datum/status_effect/water_affected
 	id = "wateraffected"
 	alert_type = null
-	duration = -1
+	duration = STATUS_EFFECT_PERMANENT
 
 /datum/status_effect/water_affected/on_apply()
 	//We should be inside a liquid turf if this is applied
@@ -36,8 +36,9 @@
 	blacklisted_movetypes = FLOATING | FLYING
 
 /datum/status_effect/ocean_affected
+	id = "ocean_affected"
+	duration = STATUS_EFFECT_PERMANENT
 	alert_type = null
-	duration = -1
 
 /datum/status_effect/ocean_affected/tick()
 	var/turf/ocean_turf = get_turf(owner)
diff --git a/monkestation/code/modules/ranching/chickens/tier1/chicken.dm b/monkestation/code/modules/ranching/chickens/tier1/chicken.dm
index 7ecf78db3790..6da6940f9af4 100644
--- a/monkestation/code/modules/ranching/chickens/tier1/chicken.dm
+++ b/monkestation/code/modules/ranching/chickens/tier1/chicken.dm
@@ -4,4 +4,6 @@
 	instability = 25 // 25% more likely to mutate than other chickens
 
 /datum/status_effect/ranching
+	id = STATUS_EFFECT_ID_ABSTRACT
+	alert_type = null
 	show_duration = TRUE
diff --git a/monkestation/code/modules/slimecore/crossbreeding/regenerative/cooldown.dm b/monkestation/code/modules/slimecore/crossbreeding/regenerative/cooldown.dm
index 0614d914d116..431e99388fb5 100644
--- a/monkestation/code/modules/slimecore/crossbreeding/regenerative/cooldown.dm
+++ b/monkestation/code/modules/slimecore/crossbreeding/regenerative/cooldown.dm
@@ -1,7 +1,7 @@
 /datum/status_effect/slime_regen_cooldown
 	id = "slime_regen_cooldown"
 	status_type = STATUS_EFFECT_MULTIPLE
-	tick_interval = -1
+	tick_interval = STATUS_EFFECT_NO_TICK
 	alert_type = null
 	remove_on_fullheal = TRUE
 	heal_flag_necessary = HEAL_ADMIN

From 7f225d04d0033108933a395a3476deb0f518dcc5 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Sun, 22 Dec 2024 19:46:00 +0000
Subject: [PATCH 14/28] Automatic changelog for PR #4580 [ci skip]

---
 html/changelogs/AutoChangeLog-pr-4580.yml | 4 ++++
 1 file changed, 4 insertions(+)
 create mode 100644 html/changelogs/AutoChangeLog-pr-4580.yml

diff --git a/html/changelogs/AutoChangeLog-pr-4580.yml b/html/changelogs/AutoChangeLog-pr-4580.yml
new file mode 100644
index 000000000000..44c2d5bc373d
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-4580.yml
@@ -0,0 +1,4 @@
+author: "Absolucy, Melbert"
+delete-after: True
+changes:
+  - bugfix: "You should be afflicted by the \"Curse of Mundanity\" far, far less."
\ No newline at end of file

From 8fdf51502099e14f1f3963e4381c4e0c9696b046 Mon Sep 17 00:00:00 2001
From: Lucy <lucy@absolucy.moe>
Date: Sun, 22 Dec 2024 15:22:41 -0500
Subject: [PATCH 15/28] All ghosts now see PDA messages, not just observers
 (#4639)

---
 .../file_system/programs/messenger/messenger_program.dm         | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/code/modules/modular_computers/file_system/programs/messenger/messenger_program.dm b/code/modules/modular_computers/file_system/programs/messenger/messenger_program.dm
index d533d98cf3e1..14cf735d7da1 100644
--- a/code/modules/modular_computers/file_system/programs/messenger/messenger_program.dm
+++ b/code/modules/modular_computers/file_system/programs/messenger/messenger_program.dm
@@ -608,7 +608,7 @@
 
 	// Show it to ghosts
 	var/ghost_message = span_name("[sender] [rigged ? "(as [fake_name]) Rigged " : ""]PDA Message --> [span_name("[signal.format_target()]")]: \"[signal.format_message()]\"")
-	for(var/mob/player_mob as anything in GLOB.current_observers_list)
+	for(var/mob/player_mob as anything in GLOB.dead_mob_list)
 		if(player_mob.client && !player_mob.client?.prefs)
 			stack_trace("[player_mob] ([player_mob.ckey]) had null prefs, which shouldn't be possible!")
 			continue

From 1b5ce61b81392e2db930ce5c1af8d8e7717cbe9f Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Sun, 22 Dec 2024 20:22:59 +0000
Subject: [PATCH 16/28] Automatic changelog for PR #4639 [ci skip]

---
 html/changelogs/AutoChangeLog-pr-4639.yml | 4 ++++
 1 file changed, 4 insertions(+)
 create mode 100644 html/changelogs/AutoChangeLog-pr-4639.yml

diff --git a/html/changelogs/AutoChangeLog-pr-4639.yml b/html/changelogs/AutoChangeLog-pr-4639.yml
new file mode 100644
index 000000000000..4587ff00e654
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-4639.yml
@@ -0,0 +1,4 @@
+author: "Absolucy"
+delete-after: True
+changes:
+  - qol: "All ghosts now see PDA messages, not just observers."
\ No newline at end of file

From 8fa9eb92c43bd00232ed54a6f955523f14774eda Mon Sep 17 00:00:00 2001
From: Changelogs <action@github.com>
Date: Sun, 22 Dec 2024 20:24:23 +0000
Subject: [PATCH 17/28] Automatic changelog compile [ci skip]

---
 html/changelogs/AutoChangeLog-pr-4580.yml |  4 ----
 html/changelogs/AutoChangeLog-pr-4632.yml |  4 ----
 html/changelogs/AutoChangeLog-pr-4633.yml |  4 ----
 html/changelogs/AutoChangeLog-pr-4635.yml |  4 ----
 html/changelogs/AutoChangeLog-pr-4638.yml |  4 ----
 html/changelogs/AutoChangeLog-pr-4639.yml |  4 ----
 html/changelogs/archive/2024-12.yml       | 13 +++++++++++++
 7 files changed, 13 insertions(+), 24 deletions(-)
 delete mode 100644 html/changelogs/AutoChangeLog-pr-4580.yml
 delete mode 100644 html/changelogs/AutoChangeLog-pr-4632.yml
 delete mode 100644 html/changelogs/AutoChangeLog-pr-4633.yml
 delete mode 100644 html/changelogs/AutoChangeLog-pr-4635.yml
 delete mode 100644 html/changelogs/AutoChangeLog-pr-4638.yml
 delete mode 100644 html/changelogs/AutoChangeLog-pr-4639.yml

diff --git a/html/changelogs/AutoChangeLog-pr-4580.yml b/html/changelogs/AutoChangeLog-pr-4580.yml
deleted file mode 100644
index 44c2d5bc373d..000000000000
--- a/html/changelogs/AutoChangeLog-pr-4580.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Absolucy, Melbert"
-delete-after: True
-changes:
-  - bugfix: "You should be afflicted by the \"Curse of Mundanity\" far, far less."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-4632.yml b/html/changelogs/AutoChangeLog-pr-4632.yml
deleted file mode 100644
index f88d93e27281..000000000000
--- a/html/changelogs/AutoChangeLog-pr-4632.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Tractor Mann"
-delete-after: True
-changes:
-  - admin: "Admins will no longer be told you dusted someone with power gloves when you didnt, and that you shocked someone with power gloves when you dust them."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-4633.yml b/html/changelogs/AutoChangeLog-pr-4633.yml
deleted file mode 100644
index 172b28ab6c19..000000000000
--- a/html/changelogs/AutoChangeLog-pr-4633.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Absolucy"
-delete-after: True
-changes:
-  - rscadd: "Added tinyfans to the arrivals shuttle and public mining shuttle doors on Blueshift."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-4635.yml b/html/changelogs/AutoChangeLog-pr-4635.yml
deleted file mode 100644
index 07b5b61ef5ce..000000000000
--- a/html/changelogs/AutoChangeLog-pr-4635.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Absolucy"
-delete-after: True
-changes:
-  - bugfix: "Properly marked the message type and confidentiality of a few admin/mentor-related notifications."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-4638.yml b/html/changelogs/AutoChangeLog-pr-4638.yml
deleted file mode 100644
index a90c6aed296c..000000000000
--- a/html/changelogs/AutoChangeLog-pr-4638.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Absolucy"
-delete-after: True
-changes:
-  - bugfix: "The map selected for the next round should actually always be the map that loads next round."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-4639.yml b/html/changelogs/AutoChangeLog-pr-4639.yml
deleted file mode 100644
index 4587ff00e654..000000000000
--- a/html/changelogs/AutoChangeLog-pr-4639.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Absolucy"
-delete-after: True
-changes:
-  - qol: "All ghosts now see PDA messages, not just observers."
\ No newline at end of file
diff --git a/html/changelogs/archive/2024-12.yml b/html/changelogs/archive/2024-12.yml
index 5be305e9ae98..c35e63293239 100644
--- a/html/changelogs/archive/2024-12.yml
+++ b/html/changelogs/archive/2024-12.yml
@@ -363,5 +363,18 @@
   - bugfix: Heretic sacrifice targets will have their blood restored and cleared of
       chems if they are sent to the mansus.
 2024-12-22:
+  Absolucy:
+  - bugfix: Properly marked the message type and confidentiality of a few admin/mentor-related
+      notifications.
+  - bugfix: The map selected for the next round should actually always be the map
+      that loads next round.
+  - rscadd: Added tinyfans to the arrivals shuttle and public mining shuttle doors
+      on Blueshift.
+  - qol: All ghosts now see PDA messages, not just observers.
+  Absolucy, Melbert:
+  - bugfix: You should be afflicted by the "Curse of Mundanity" far, far less.
   Shoddd:
   - bugfix: you can no longer akimbo and fire a wand forever
+  Tractor Mann:
+  - admin: Admins will no longer be told you dusted someone with power gloves when
+      you didnt, and that you shocked someone with power gloves when you dust them.

From abc3ceeedc11afdf0f49a36ce86ecf5e9e299993 Mon Sep 17 00:00:00 2001
From: Lucy <lucy@absolucy.moe>
Date: Sun, 22 Dec 2024 18:56:57 -0500
Subject: [PATCH 18/28] adds a component to make it obvious what items came
 from a gift (#4625)

---
 .../traits/monkestation/declarations.dm       |  2 +
 code/__DEFINES/~monkestation/vv.dm            |  1 +
 code/__HELPERS/~monkestation-helpers/atoms.dm |  8 ++
 code/_globalvars/traits/_traits.dm            | 13 +--
 code/game/objects/items/gift.dm               |  1 +
 code/modules/admin/verbs/admingame.dm         | 12 +++
 .../code/datums/components/gift_item.dm       | 86 +++++++++++++++++++
 tgstation.dme                                 |  2 +
 8 files changed, 119 insertions(+), 6 deletions(-)
 create mode 100644 code/__DEFINES/~monkestation/vv.dm
 create mode 100644 monkestation/code/datums/components/gift_item.dm

diff --git a/code/__DEFINES/traits/monkestation/declarations.dm b/code/__DEFINES/traits/monkestation/declarations.dm
index 8a07f00fb954..a26127eb0ff5 100644
--- a/code/__DEFINES/traits/monkestation/declarations.dm
+++ b/code/__DEFINES/traits/monkestation/declarations.dm
@@ -121,6 +121,8 @@
 #define TRAIT_BYPASS_COMPRESS_CHECK	"can_compress_anyways"
 /// This item is considered "trash" (and will be eaten by cleaner slimes)
 #define TRAIT_TRASH_ITEM			"trash_item"
+/// This item came from a gift.
+#define TRAIT_GIFT_ITEM				"gift_item"
 
 // /atom/movable
 /// Things with this trait can pass through wooden barricades.
diff --git a/code/__DEFINES/~monkestation/vv.dm b/code/__DEFINES/~monkestation/vv.dm
new file mode 100644
index 000000000000..8acf0c7ef495
--- /dev/null
+++ b/code/__DEFINES/~monkestation/vv.dm
@@ -0,0 +1 @@
+#define VV_HK_EXAMINE_GIFT "examine_gift"
diff --git a/code/__HELPERS/~monkestation-helpers/atoms.dm b/code/__HELPERS/~monkestation-helpers/atoms.dm
index 67b81a50dafb..44f26ac804e0 100644
--- a/code/__HELPERS/~monkestation-helpers/atoms.dm
+++ b/code/__HELPERS/~monkestation-helpers/atoms.dm
@@ -26,3 +26,11 @@
 		default_typecache ||= typecacheof(list(/obj/effect, /atom/movable/screen))
 		typecache = default_typecache
 	return typecache_filter_list_reverse(src.contents, typecache)
+
+/// Returns a list of all items in our contents that were obtained from gifts.
+/atom/proc/get_all_gift_contents() as /list
+	RETURN_TYPE(/list/obj/item)
+	. = list()
+	for(var/obj/item/thing as anything in get_all_contents_type(/obj/item))
+		if(!QDELETED(thing) && HAS_TRAIT(thing, TRAIT_GIFT_ITEM))
+			. += thing
diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm
index 7734282dd4d7..12590729049c 100644
--- a/code/_globalvars/traits/_traits.dm
+++ b/code/_globalvars/traits/_traits.dm
@@ -611,24 +611,31 @@ GLOBAL_LIST_INIT(traits_by_type, list(
 	),
 	/obj/item = list(
 		"TRAIT_APC_SHOCKING" = TRAIT_APC_SHOCKING,
+		"TRAIT_ASSISTED_BREATHING" = TRAIT_ASSISTED_BREATHING,
 		"TRAIT_BASIC_QUALITY_BAIT" = TRAIT_BASIC_QUALITY_BAIT,
 		"TRAIT_BELT_SATCHEL" = TRAIT_BELT_SATCHEL,
 		"TRAIT_BLIND_TOOL" = TRAIT_BLIND_TOOL,
 		"TRAIT_BYPASS_COMPRESS_CHECK" = TRAIT_BYPASS_COMPRESS_CHECK,
 		"TRAIT_CUSTOM_TAP_SOUND" = TRAIT_CUSTOM_TAP_SOUND,
 		"TRAIT_DANGEROUS_OBJECT" = TRAIT_DANGEROUS_OBJECT,
+		"TRAIT_FEATHERED" = TRAIT_FEATHERED,
 		"TRAIT_FISHING_BAIT" = TRAIT_FISHING_BAIT,
 		"TRAIT_FOOD_GRILLED" = TRAIT_FOOD_GRILLED,
+		"TRAIT_GIFT_ITEM" = TRAIT_GIFT_ITEM,
 		"TRAIT_GOOD_QUALITY_BAIT" = TRAIT_GOOD_QUALITY_BAIT,
 		"TRAIT_GREAT_QUALITY_BAIT" = TRAIT_GREAT_QUALITY_BAIT,
 		"TRAIT_HAUNTED" = TRAIT_HAUNTED,
 		"TRAIT_HONKSPAMMING" = TRAIT_HONKSPAMMING,
 		"TRAIT_INNATELY_FANTASTICAL_ITEM" = TRAIT_INNATELY_FANTASTICAL_ITEM,
+		"TRAIT_INSTANTLY_PROCESSES_BOULDERS" = TRAIT_INSTANTLY_PROCESSES_BOULDERS,
+		"TRAIT_LABOURED_BREATHING" = TRAIT_LABOURED_BREATHING,
 		"TRAIT_MAT_TRANSMUTED" = TRAIT_MAT_TRANSMUTED,
 		"TRAIT_MAY_CONTAIN_BLENDED_DUST" = TRAIT_MAY_CONTAIN_BLENDED_DUST,
 		"TRAIT_NEEDS_TWO_HANDS" = TRAIT_NEEDS_TWO_HANDS,
 		"TRAIT_NODROP" = TRAIT_NODROP,
+		"TRAIT_NON_IMPORTANT_SHOE_BLOCK" = TRAIT_NON_IMPORTANT_SHOE_BLOCK,
 		"TRAIT_NO_BARCODES" = TRAIT_NO_BARCODES,
+		"TRAIT_NO_ORGAN_DECAY" = TRAIT_NO_ORGAN_DECAY,
 		"TRAIT_NO_STORAGE_INSERT" = TRAIT_NO_STORAGE_INSERT,
 		"TRAIT_NO_TELEPORT" = TRAIT_NO_TELEPORT,
 		"TRAIT_OMNI_BAIT" = TRAIT_OMNI_BAIT,
@@ -639,17 +646,11 @@ GLOBAL_LIST_INIT(traits_by_type, list(
 		"TRAIT_T_RAY_VISIBLE" = TRAIT_T_RAY_VISIBLE,
 		"TRAIT_UNCATCHABLE" = TRAIT_UNCATCHABLE,
 		"TRAIT_WIELDED" = TRAIT_WIELDED,
-		"TRAIT_FEATHERED" = TRAIT_FEATHERED,
-		"TRAIT_NON_IMPORTANT_SHOE_BLOCK" = TRAIT_NON_IMPORTANT_SHOE_BLOCK,
-		"TRAIT_LABOURED_BREATHING" = TRAIT_LABOURED_BREATHING,
-		"TRAIT_ASSISTED_BREATHING" = TRAIT_ASSISTED_BREATHING,
-		"TRAIT_NO_ORGAN_DECAY" = TRAIT_NO_ORGAN_DECAY,
 		/* "TRAIT_BAIT_UNCONSUMABLE" = TRAIT_BAIT_UNCONSUMABLE, */
 		/* "TRAIT_BAKEABLE" = TRAIT_BAKEABLE, */
 		/* "TRAIT_BYPASS_RANGED_ARMOR" = TRAIT_BYPASS_RANGED_ARMOR, */
 		/* "TRAIT_CONTRABAND_BLOCKER" = TRAIT_CONTRABAND_BLOCKER, */
 		/* "TRAIT_GERM_SENSITIVE" = TRAIT_GERM_SENSITIVE, */
-		"TRAIT_INSTANTLY_PROCESSES_BOULDERS" = TRAIT_INSTANTLY_PROCESSES_BOULDERS,
 		/* "TRAIT_ITEM_OBJECTIVE_BLOCKED" = TRAIT_ITEM_OBJECTIVE_BLOCKED, */
 		/* "TRAIT_NO_SIDE_KICK" = TRAIT_NO_SIDE_KICK, */
 	),
diff --git a/code/game/objects/items/gift.dm b/code/game/objects/items/gift.dm
index c1a5e10a1b38..16913a80c04d 100644
--- a/code/game/objects/items/gift.dm
+++ b/code/game/objects/items/gift.dm
@@ -50,6 +50,7 @@ GLOBAL_LIST_EMPTY(possible_gifts)
 		M.investigate_log("has unwrapped a present containing [I.type].", INVESTIGATE_PRESENTS)
 		M.put_in_hands(I)
 		I.add_fingerprint(M)
+		I.AddComponent(/datum/component/gift_item, M) // monkestation edit: gift item info component
 	else
 		M.visible_message(span_danger("Oh no! The present that [M] opened had nothing inside it!"))
 
diff --git a/code/modules/admin/verbs/admingame.dm b/code/modules/admin/verbs/admingame.dm
index 670b2c76ec76..340cc3d17faa 100644
--- a/code/modules/admin/verbs/admingame.dm
+++ b/code/modules/admin/verbs/admingame.dm
@@ -45,6 +45,18 @@
 			full_version = "[M.client.byond_version].[M.client.byond_build ? M.client.byond_build : "xxx"]"
 		body += "<br>\[<b>Byond version:</b> [full_version]\]<br>"
 
+	// monkestation start: gift info
+	var/list/gifts = M.get_all_gift_contents()
+	var/gift_amt = length(gifts)
+	if(gift_amt)
+		body += "<br><br><b>Gift items in contents:</b><br>"
+		for(var/idx in 1 to gift_amt)
+			var/obj/item/gift = gifts[idx]
+			body += VV_HREF_TARGET(gift, VV_HK_EXAMINE_GIFT, "<b>[gift.name]</b> (<i>[gift.type]</i>)")
+			if(idx < gift_amt)
+				body += "<br>"
+	// monkestation end
+
 
 	body += "<br><br>\[ "
 	body += "<a href='?_src_=vars;[HrefToken()];Vars=[REF(M)]'>VV</a> - "
diff --git a/monkestation/code/datums/components/gift_item.dm b/monkestation/code/datums/components/gift_item.dm
new file mode 100644
index 000000000000..d6f82e9fa89a
--- /dev/null
+++ b/monkestation/code/datums/components/gift_item.dm
@@ -0,0 +1,86 @@
+/// Simple thing that marks an items as having come from a gift.
+/datum/component/gift_item
+	/// The ckey of the player who opened the gift.
+	var/ckey
+	/// Weakref to the mob who opened the gift.
+	var/datum/weakref/giftee
+	/// Weakref to the mob who opened the gift.
+	var/datum/weakref/mind
+	/// The (real) name of mob who opened the gift.
+	var/name
+	/// The `world.time` when the gift was opened.
+	var/open_world_time
+	/// The `world.timeofday` when the gift was opened.
+	var/open_timeofday
+
+/datum/component/gift_item/Initialize(mob/living/giftee)
+	if(!isitem(parent))
+		stack_trace("Tried to assign [type] to a non-item")
+		return COMPONENT_INCOMPATIBLE
+	if(!isliving(giftee))
+		stack_trace("Tried to assign [type] to something that wasn't a living mob!")
+		return COMPONENT_INCOMPATIBLE
+	if(!giftee.ckey)
+		stack_trace("Tried to assign [type] to a non-player mob!")
+		return COMPONENT_INCOMPATIBLE
+	src.ckey = giftee.ckey
+	src.giftee = WEAKREF(giftee)
+	src.mind = WEAKREF(giftee.mind)
+	src.name = "[giftee.mind?.name || giftee.real_name || giftee.name || "N/A"]"
+	src.open_world_time = world.time
+	src.open_timeofday = world.timeofday
+
+/datum/component/gift_item/Destroy(force)
+	giftee = null
+	mind = null
+	return ..()
+
+/datum/component/gift_item/RegisterWithParent()
+	RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
+	RegisterSignal(parent, COMSIG_ATOM_EXAMINE_MORE, PROC_REF(on_examine_more))
+	RegisterSignal(parent, COMSIG_VV_TOPIC, PROC_REF(handle_vv_topic))
+	ADD_TRAIT(parent, TRAIT_GIFT_ITEM, type)
+
+/datum/component/gift_item/UnregisterFromParent()
+	UnregisterSignal(parent, list(COMSIG_ATOM_EXAMINE, COMSIG_ATOM_EXAMINE_MORE, COMSIG_VV_TOPIC))
+	REMOVE_TRAIT(parent, TRAIT_GIFT_ITEM, type)
+
+/datum/component/gift_item/proc/on_examine(obj/item/source, mob/examiner, list/examine_text)
+	SIGNAL_HANDLER
+	if(check_rights_for(examiner.client, R_ADMIN))
+		// ensure we always target the right mob for the admin buttons
+		var/mob/target_mob = resolve_opener_mob()
+		examine_text += ""
+		examine_text += span_bold("\[") + span_info(" This item came from a gift opened by [span_name(name)] ([ckey]) [ADMIN_FULLMONTY_NONAME(target_mob)] ") + span_bold("\]")
+		examine_text += span_bold("\[") + span_info(" It was unwrapped from a gift [span_bold(DisplayTimeText(world.time - open_world_time) + " ago")], at server time [span_bold(time2text(open_timeofday, "YYYY-MM-DD hh:mm:ss"))] ") + span_bold("\]")
+		examine_text += ""
+	else if(isobserver(examiner) || HAS_TRAIT(examiner, TRAIT_PRESENT_VISION) || SSticker.current_state >= GAME_STATE_FINISHED)
+		examine_text += ""
+		examine_text += span_bold("\[") + span_info(" This item came from a gift opened by [span_name(name)] [DisplayTimeText(world.time - open_world_time)] ago ") + span_bold("\]")
+		examine_text += ""
+
+/datum/component/gift_item/proc/on_examine_more(obj/item/source, mob/examiner, list/examine_text)
+	SIGNAL_HANDLER
+	examine_text += span_info("This item seems to have been a gift!")
+
+/datum/component/gift_item/proc/resolve_opener_mob() as /mob
+	RETURN_TYPE(/mob)
+	var/mob/opener = giftee.resolve()
+	var/datum/mind/opener_mind = mind.resolve()
+	if(opener?.ckey == ckey)
+		return opener
+	else if(opener_mind?.current?.ckey == ckey)
+		return opener_mind.current
+	else if(GLOB.directory[ckey])
+		var/client/current_client = GLOB.directory[ckey]
+		return current_client.mob
+	else
+		for(var/mob/mob in GLOB.mob_list)
+			if(mob.ckey == ckey)
+				return mob
+
+/datum/component/gift_item/proc/handle_vv_topic(datum/source, mob/user, list/href_list)
+	SIGNAL_HANDLER
+	if(href_list[VV_HK_EXAMINE_GIFT] && check_rights(R_ADMIN))
+		user.examinate(parent)
+		return COMPONENT_VV_HANDLED
diff --git a/tgstation.dme b/tgstation.dme
index 12c9484b8077..830016c00216 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -469,6 +469,7 @@
 #include "code\__DEFINES\~monkestation\twitch.dm"
 #include "code\__DEFINES\~monkestation\uplink.dm"
 #include "code\__DEFINES\~monkestation\virology.dm"
+#include "code\__DEFINES\~monkestation\vv.dm"
 #include "code\__DEFINES\~monkestation\dcs\signals\signals_atom.dm"
 #include "code\__DEFINES\~monkestation\dcs\signals\signals_blueshift.dm"
 #include "code\__DEFINES\~monkestation\dcs\signals\signals_carbon.dm"
@@ -5955,6 +5956,7 @@
 #include "monkestation\code\datums\components\carbon_sprint.dm"
 #include "monkestation\code\datums\components\charge_adjuster.dm"
 #include "monkestation\code\datums\components\crafting.dm"
+#include "monkestation\code\datums\components\gift_item.dm"
 #include "monkestation\code\datums\components\gps.dm"
 #include "monkestation\code\datums\components\irradiated.dm"
 #include "monkestation\code\datums\components\lock_on_cursor.dm"

From 22642ac8a0b64327cf264e221b25efbbad9eb5c1 Mon Sep 17 00:00:00 2001
From: Lucy <lucy@absolucy.moe>
Date: Sun, 22 Dec 2024 18:57:06 -0500
Subject: [PATCH 19/28] Fire alarms no longer burn out lights (#4636)

---
 code/modules/power/lighting/light.dm | 16 +++++++++-------
 1 file changed, 9 insertions(+), 7 deletions(-)

diff --git a/code/modules/power/lighting/light.dm b/code/modules/power/lighting/light.dm
index 09c2e1fb2aa0..abef4540dd71 100644
--- a/code/modules/power/lighting/light.dm
+++ b/code/modules/power/lighting/light.dm
@@ -196,10 +196,10 @@
 
 /obj/machinery/light/proc/handle_fire(area/source, new_fire)
 	SIGNAL_HANDLER
-	update()
+	update(dont_burn_out = TRUE)
 
 // update the icon_state and luminosity of the light depending on its state
-/obj/machinery/light/proc/update(trigger = TRUE)
+/obj/machinery/light/proc/update(trigger = TRUE, dont_burn_out = FALSE)
 	switch(status)
 		if(LIGHT_BROKEN,LIGHT_BURNED,LIGHT_EMPTY)
 			on = FALSE
@@ -230,11 +230,13 @@
 			brightness_set = bulb_outer_range * bulb_major_emergency_brightness_mul
 		var/matching = light && brightness_set == light.light_outer_range && power_set == light.light_power && color_set == light.light_color && FC == light.light_falloff_curve && IR == light.light_inner_range
 		if(!matching)
-			switchcount++
-			if( prob( min(60, (switchcount**2)*0.01) ) )
-				if(trigger)
+			var/should_set = TRUE
+			if(!dont_burn_out)
+				switchcount++
+				if(trigger && prob(min(60, (switchcount ** 2) * 0.01)))
 					burn_out()
-			else
+					should_set = FALSE
+			if(should_set)
 				use_power = ACTIVE_POWER_USE
 				set_light(
 					l_outer_range = brightness_set,
@@ -242,7 +244,7 @@
 					l_power = power_set,
 					l_falloff_curve = FC,
 					l_color = color_set
-					)
+				)
 	else if(has_emergency_power(LIGHT_EMERGENCY_POWER_USE) && !turned_off())
 		use_power = IDLE_POWER_USE
 		low_power_mode = TRUE

From 78a137baaffbf8e7af4f83500b842549d106870c Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Sun, 22 Dec 2024 23:57:17 +0000
Subject: [PATCH 20/28] Automatic changelog for PR #4625 [ci skip]

---
 html/changelogs/AutoChangeLog-pr-4625.yml | 6 ++++++
 1 file changed, 6 insertions(+)
 create mode 100644 html/changelogs/AutoChangeLog-pr-4625.yml

diff --git a/html/changelogs/AutoChangeLog-pr-4625.yml b/html/changelogs/AutoChangeLog-pr-4625.yml
new file mode 100644
index 000000000000..93bfd16bf876
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-4625.yml
@@ -0,0 +1,6 @@
+author: "Absolucy"
+delete-after: True
+changes:
+  - admin: "Admins can now much more easily see which of a player's items came from a gift."
+  - rscadd: "Ghosts, santa, and anyone after roundend can an examine an item to see if its a gift, who opened it, and how long ago they did so."
+  - rscadd: "Closely examining (double-examining) an item will now tell you if it was from a gift."
\ No newline at end of file

From 57c42300c7d75ac7e494c35432ca9367643d8268 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Sun, 22 Dec 2024 23:57:26 +0000
Subject: [PATCH 21/28] Automatic changelog for PR #4636 [ci skip]

---
 html/changelogs/AutoChangeLog-pr-4636.yml | 4 ++++
 1 file changed, 4 insertions(+)
 create mode 100644 html/changelogs/AutoChangeLog-pr-4636.yml

diff --git a/html/changelogs/AutoChangeLog-pr-4636.yml b/html/changelogs/AutoChangeLog-pr-4636.yml
new file mode 100644
index 000000000000..6c44528767ff
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-4636.yml
@@ -0,0 +1,4 @@
+author: "Absolucy"
+delete-after: True
+changes:
+  - qol: "Fire alarms no longer burn out lights."
\ No newline at end of file

From d22da017367ae29a2a605ebf92c45627e893ee66 Mon Sep 17 00:00:00 2001
From: Changelogs <action@github.com>
Date: Mon, 23 Dec 2024 00:08:33 +0000
Subject: [PATCH 22/28] Automatic changelog compile [ci skip]

---
 html/changelogs/AutoChangeLog-pr-4625.yml | 6 ------
 html/changelogs/AutoChangeLog-pr-4636.yml | 4 ----
 html/changelogs/archive/2024-12.yml       | 9 +++++++++
 3 files changed, 9 insertions(+), 10 deletions(-)
 delete mode 100644 html/changelogs/AutoChangeLog-pr-4625.yml
 delete mode 100644 html/changelogs/AutoChangeLog-pr-4636.yml

diff --git a/html/changelogs/AutoChangeLog-pr-4625.yml b/html/changelogs/AutoChangeLog-pr-4625.yml
deleted file mode 100644
index 93bfd16bf876..000000000000
--- a/html/changelogs/AutoChangeLog-pr-4625.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-author: "Absolucy"
-delete-after: True
-changes:
-  - admin: "Admins can now much more easily see which of a player's items came from a gift."
-  - rscadd: "Ghosts, santa, and anyone after roundend can an examine an item to see if its a gift, who opened it, and how long ago they did so."
-  - rscadd: "Closely examining (double-examining) an item will now tell you if it was from a gift."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-4636.yml b/html/changelogs/AutoChangeLog-pr-4636.yml
deleted file mode 100644
index 6c44528767ff..000000000000
--- a/html/changelogs/AutoChangeLog-pr-4636.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Absolucy"
-delete-after: True
-changes:
-  - qol: "Fire alarms no longer burn out lights."
\ No newline at end of file
diff --git a/html/changelogs/archive/2024-12.yml b/html/changelogs/archive/2024-12.yml
index c35e63293239..7d60f635696a 100644
--- a/html/changelogs/archive/2024-12.yml
+++ b/html/changelogs/archive/2024-12.yml
@@ -378,3 +378,12 @@
   Tractor Mann:
   - admin: Admins will no longer be told you dusted someone with power gloves when
       you didnt, and that you shocked someone with power gloves when you dust them.
+2024-12-23:
+  Absolucy:
+  - admin: Admins can now much more easily see which of a player's items came from
+      a gift.
+  - rscadd: Ghosts, santa, and anyone after roundend can an examine an item to see
+      if its a gift, who opened it, and how long ago they did so.
+  - rscadd: Closely examining (double-examining) an item will now tell you if it was
+      from a gift.
+  - qol: Fire alarms no longer burn out lights.

From 338bd96e9725cd740a1a962956e25941cc5c96f3 Mon Sep 17 00:00:00 2001
From: Shoddd <148718717+Shoddd@users.noreply.github.com>
Date: Tue, 24 Dec 2024 01:25:39 -0500
Subject: [PATCH 23/28] free gbp (#4647)

Co-authored-by: shodd <linkmonissussy@gmail.com>
---
 .../code/modules/slimecore/crossbreeding/regenerative/effect.dm | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/monkestation/code/modules/slimecore/crossbreeding/regenerative/effect.dm b/monkestation/code/modules/slimecore/crossbreeding/regenerative/effect.dm
index f1f4878fd76b..eea062382329 100644
--- a/monkestation/code/modules/slimecore/crossbreeding/regenerative/effect.dm
+++ b/monkestation/code/modules/slimecore/crossbreeding/regenerative/effect.dm
@@ -93,7 +93,7 @@
 		ordered_wounds[1]?.remove_wound()
 
 /datum/status_effect/regenerative_extract/get_examine_text()
-	return "[owner.p_They()] have a subtle, gentle glow to [owner.p_their()] skin, with slime soothing [owner.p_their()] wounds."
+	return "[owner.p_They()] [owner.p_have()] a subtle, gentle glow to [owner.p_their()] skin, with slime soothing [owner.p_their()] wounds."
 
 /atom/movable/screen/alert/status_effect/regen_extract
 	name = "Slime Regeneration"

From 6a04546080c755893ca161c825b7e23c5f5b1d90 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Tue, 24 Dec 2024 06:26:06 +0000
Subject: [PATCH 24/28] Automatic changelog for PR #4647 [ci skip]

---
 html/changelogs/AutoChangeLog-pr-4647.yml | 4 ++++
 1 file changed, 4 insertions(+)
 create mode 100644 html/changelogs/AutoChangeLog-pr-4647.yml

diff --git a/html/changelogs/AutoChangeLog-pr-4647.yml b/html/changelogs/AutoChangeLog-pr-4647.yml
new file mode 100644
index 000000000000..3d8845591897
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-4647.yml
@@ -0,0 +1,4 @@
+author: "Shoddd"
+delete-after: True
+changes:
+  - bugfix: "fixes a typo in regen extract"
\ No newline at end of file

From e063aa9f4e5d8635d66abd8b41087c81bdff1250 Mon Sep 17 00:00:00 2001
From: Wisemonster <87689371+Wisemonster@users.noreply.github.com>
Date: Tue, 24 Dec 2024 06:20:31 -0500
Subject: [PATCH 25/28] Fix syndie base delamming (#4653)

---
 .../lavaland_surface_syndicate_base1/mistake_inevitable.dmm     | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/_maps/RandomRuins/LavaRuins/lavaland_surface_syndicate_base1/mistake_inevitable.dmm b/_maps/RandomRuins/LavaRuins/lavaland_surface_syndicate_base1/mistake_inevitable.dmm
index 1deee17aee2e..091c1b2f53b1 100644
--- a/_maps/RandomRuins/LavaRuins/lavaland_surface_syndicate_base1/mistake_inevitable.dmm
+++ b/_maps/RandomRuins/LavaRuins/lavaland_surface_syndicate_base1/mistake_inevitable.dmm
@@ -197,8 +197,8 @@
 /turf/open/floor/engine,
 /area/ruin/syndicate_lava_base/testlab)
 "rX" = (
-/obj/machinery/atmospherics/components/unary/passive_vent,
 /obj/structure/lattice/catwalk,
+/obj/machinery/atmospherics/components/unary/outlet_injector/on,
 /turf/template_noop,
 /area/ruin/syndicate_lava_base/testlab)
 "sa" = (

From 87c6bd0af6ca19b976d11a6dae6db3f174ff49f3 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Tue, 24 Dec 2024 11:20:54 +0000
Subject: [PATCH 26/28] Automatic changelog for PR #4653 [ci skip]

---
 html/changelogs/AutoChangeLog-pr-4653.yml | 4 ++++
 1 file changed, 4 insertions(+)
 create mode 100644 html/changelogs/AutoChangeLog-pr-4653.yml

diff --git a/html/changelogs/AutoChangeLog-pr-4653.yml b/html/changelogs/AutoChangeLog-pr-4653.yml
new file mode 100644
index 000000000000..bc65ae5d60e2
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-4653.yml
@@ -0,0 +1,4 @@
+author: "Wisemonster"
+delete-after: True
+changes:
+  - bugfix: "Fixed the syndicate lavaland base delamming if they spawned with an SM"
\ No newline at end of file

From e26532d5014ae180d7965e9bade79c6ecd505c89 Mon Sep 17 00:00:00 2001
From: Uristthedorf <40842973+Uristthedorf@users.noreply.github.com>
Date: Tue, 24 Dec 2024 03:24:00 -0800
Subject: [PATCH 27/28] Why was this there? (#4659)

---
 .../{objects/items/robot => robot/items}/robot_upgrades.dm      | 0
 tgstation.dme                                                   | 2 +-
 2 files changed, 1 insertion(+), 1 deletion(-)
 rename monkestation/code/game/objects/items/{objects/items/robot => robot/items}/robot_upgrades.dm (100%)

diff --git a/monkestation/code/game/objects/items/objects/items/robot/robot_upgrades.dm b/monkestation/code/game/objects/items/robot/items/robot_upgrades.dm
similarity index 100%
rename from monkestation/code/game/objects/items/objects/items/robot/robot_upgrades.dm
rename to monkestation/code/game/objects/items/robot/items/robot_upgrades.dm
diff --git a/tgstation.dme b/tgstation.dme
index 830016c00216..6d7052a6722a 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -6116,10 +6116,10 @@
 #include "monkestation\code\game\objects\items\guns\SRN.dm"
 #include "monkestation\code\game\objects\items\guns\wt_ammo.dm"
 #include "monkestation\code\game\objects\items\implants\hardlight.dm"
-#include "monkestation\code\game\objects\items\objects\items\robot\robot_upgrades.dm"
 #include "monkestation\code\game\objects\items\rayne_corp\rayne_lantern.dm"
 #include "monkestation\code\game\objects\items\rayne_corp\rayne_mender.dm"
 #include "monkestation\code\game\objects\items\robot\items\hypo.dm"
+#include "monkestation\code\game\objects\items\robot\items\robot_upgrades.dm"
 #include "monkestation\code\game\objects\items\stacks\tile_types.dm"
 #include "monkestation\code\game\objects\items\storage\book.dm"
 #include "monkestation\code\game\objects\items\storage\boxes.dm"

From e02822d1d41c784b59fc2b5ca5ca3a4e954f2807 Mon Sep 17 00:00:00 2001
From: Changelogs <action@github.com>
Date: Wed, 25 Dec 2024 01:39:17 +0000
Subject: [PATCH 28/28] Automatic changelog compile [ci skip]

---
 html/changelogs/AutoChangeLog-pr-4647.yml | 4 ----
 html/changelogs/AutoChangeLog-pr-4653.yml | 4 ----
 html/changelogs/archive/2024-12.yml       | 5 +++++
 3 files changed, 5 insertions(+), 8 deletions(-)
 delete mode 100644 html/changelogs/AutoChangeLog-pr-4647.yml
 delete mode 100644 html/changelogs/AutoChangeLog-pr-4653.yml

diff --git a/html/changelogs/AutoChangeLog-pr-4647.yml b/html/changelogs/AutoChangeLog-pr-4647.yml
deleted file mode 100644
index 3d8845591897..000000000000
--- a/html/changelogs/AutoChangeLog-pr-4647.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Shoddd"
-delete-after: True
-changes:
-  - bugfix: "fixes a typo in regen extract"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-4653.yml b/html/changelogs/AutoChangeLog-pr-4653.yml
deleted file mode 100644
index bc65ae5d60e2..000000000000
--- a/html/changelogs/AutoChangeLog-pr-4653.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Wisemonster"
-delete-after: True
-changes:
-  - bugfix: "Fixed the syndicate lavaland base delamming if they spawned with an SM"
\ No newline at end of file
diff --git a/html/changelogs/archive/2024-12.yml b/html/changelogs/archive/2024-12.yml
index 7d60f635696a..d040f0dd1b7d 100644
--- a/html/changelogs/archive/2024-12.yml
+++ b/html/changelogs/archive/2024-12.yml
@@ -387,3 +387,8 @@
   - rscadd: Closely examining (double-examining) an item will now tell you if it was
       from a gift.
   - qol: Fire alarms no longer burn out lights.
+2024-12-25:
+  Shoddd:
+  - bugfix: fixes a typo in regen extract
+  Wisemonster:
+  - bugfix: Fixed the syndicate lavaland base delamming if they spawned with an SM