diff --git a/code/__DEFINES/components.dm b/code/__DEFINES/components.dm
index f47309dd7344..8fb22c7a7adf 100644
--- a/code/__DEFINES/components.dm
+++ b/code/__DEFINES/components.dm
@@ -17,7 +17,8 @@
// /datum signals
#define COMSIG_COMPONENT_ADDED "component_added" //when a component is added to a datum: (/datum/component)
#define COMSIG_COMPONENT_REMOVING "component_removing" //before a component is removed from a datum because of RemoveComponent: (/datum/component)
-#define COMSIG_PARENT_QDELETED "parent_qdeleted" //before a datum's Destroy() is called: ()
+#define COMSIG_PARENT_PREQDELETED "parent_preqdeleted" //before a datum's Destroy() is called: (force), returning a nonzero value will cancel the qdel operation
+#define COMSIG_PARENT_QDELETED "parent_qdeleted" //after a datum's Destroy() is called: (force, qdel_hint), at this point none of the other components chose to interrupt qdel and Destroy has been called
// /atom signals
#define COMSIG_PARENT_ATTACKBY "atom_attackby" //from base of atom/attackby(): (/obj/item, /mob/living, params)
@@ -81,7 +82,7 @@
#define COMSIG_MOVABLE_BUCKLE "buckle" //from base of atom/movable/buckle_mob(): (mob, force)
#define COMSIG_MOVABLE_UNBUCKLE "unbuckle" //from base of atom/movable/unbuckle_mob(): (mob, force)
#define COMSIG_MOVABLE_THROW "movable_throw" //from base of atom/movable/throw_at(): (datum/thrownthing, spin)
-
+#define COMSIG_MOVABLE_Z_CHANGED "movable_ztransit" //from base of atom/movable/onTransitZ(): (old_z, new_z)
// /obj signals
#define COMSIG_OBJ_DECONSTRUCT "obj_deconstruct" //from base of obj/deconstruct(): (disassembled)
@@ -96,6 +97,7 @@
#define COMSIG_ITEM_DROPPED "item_drop"
#define COMSIG_ITEM_PICKUP "item_pickup" //from base of obj/item/pickup(): (/mob/taker)
#define COMSIG_ITEM_ATTACK_ZONE "item_attack_zone" //from base of mob/living/carbon/attacked_by(): (mob/living/carbon/target, mob/living/user, hit_zone)
+#define COMSIG_ITEM_IMBUE_SOUL "item_imbue_soul" //return a truthy value to prevent ensouling, checked in /obj/effect/proc_holder/spell/targeted/lichdom/cast(): (mob/user)
// /obj/item/clothing signals
#define COMSIG_SHOES_STEP_ACTION "shoes_step_action" //from base of obj/item/clothing/shoes/proc/step_action(): ()
diff --git a/code/__DEFINES/flags.dm b/code/__DEFINES/flags.dm
index 138bfbe44a86..f871ac6a445e 100644
--- a/code/__DEFINES/flags.dm
+++ b/code/__DEFINES/flags.dm
@@ -39,8 +39,6 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204
#define NO_EMP_WIRES_2 (1<<1)
#define HOLOGRAM_2 (1<<2)
#define FROZEN_2 (1<<3)
-#define STATIONLOVING_2 (1<<4)
-#define INFORM_ADMINS_ON_RELOCATE_2 (1<<5)
#define BANG_PROTECT_2 (1<<6)
// An item worn in the ear slot with HEALS_EARS will heal your ears each
diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm
index 3cec4248d2d7..36b25bf6c633 100644
--- a/code/__DEFINES/subsystems.dm
+++ b/code/__DEFINES/subsystems.dm
@@ -99,7 +99,6 @@
#define FIRE_PRIORITY_OBJ 40
#define FIRE_PRIORITY_ACID 40
#define FIRE_PRIOTITY_BURNING 40
-#define FIRE_PRIORITY_INBOUNDS 40
#define FIRE_PRIORITY_DEFAULT 50
#define FIRE_PRIORITY_PARALLAX 65
#define FIRE_PRIORITY_FLIGHTPACKS 80
diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm
index 548cf0489b7f..168863708c2a 100644
--- a/code/_globalvars/bitfields.dm
+++ b/code/_globalvars/bitfields.dm
@@ -123,8 +123,6 @@ GLOBAL_LIST_INIT(bitfields, list(
"NO_EMP_WIRES_2" = NO_EMP_WIRES_2,
"HOLOGRAM_2" = HOLOGRAM_2,
"FRONZE_2" = FROZEN_2,
- "STATIONLOVING_2" = STATIONLOVING_2,
- "INFORM_ADMINS_ON_RELOCATE_2" = INFORM_ADMINS_ON_RELOCATE_2,
"BANG_PROTECT_2" = BANG_PROTECT_2,
"HEALS_EARS_2" = HEALS_EARS_2,
"OMNITONGUE_2" = OMNITONGUE_2,
diff --git a/code/controllers/subsystem/garbage.dm b/code/controllers/subsystem/garbage.dm
index d0e4e1f74db1..f276da972c04 100644
--- a/code/controllers/subsystem/garbage.dm
+++ b/code/controllers/subsystem/garbage.dm
@@ -293,11 +293,13 @@ SUBSYSTEM_DEF(garbage)
if(isnull(D.gc_destroyed))
- D.SendSignal(COMSIG_PARENT_QDELETED)
+ if (D.SendSignal(COMSIG_PARENT_PREQDELETED, force)) // Give the components a chance to prevent their parent from being deleted
+ return
D.gc_destroyed = GC_CURRENTLY_BEING_QDELETED
var/start_time = world.time
var/start_tick = world.tick_usage
var/hint = D.Destroy(arglist(args.Copy(2))) // Let our friend know they're about to get fucked up.
+ D.SendSignal(COMSIG_PARENT_QDELETED, force, hint) // Let the (remaining) components know about the result of Destroy
if(world.time != start_time)
I.slept_destroy++
else
diff --git a/code/controllers/subsystem/inbounds.dm b/code/controllers/subsystem/inbounds.dm
deleted file mode 100644
index 63063c258fbc..000000000000
--- a/code/controllers/subsystem/inbounds.dm
+++ /dev/null
@@ -1,30 +0,0 @@
-SUBSYSTEM_DEF(inbounds)
- name = "Inbounds"
- priority = FIRE_PRIORITY_INBOUNDS
- flags = SS_NO_INIT
- runlevels = RUNLEVEL_GAME
-
- var/list/processing = list()
- var/list/currentrun = list()
-
-/datum/controller/subsystem/inbounds/stat_entry()
- ..("P:[processing.len]")
-
-/datum/controller/subsystem/inbounds/fire(resumed = 0)
- if (!resumed)
- src.currentrun = processing.Copy()
- //cache for sanic speed (lists are references anyways)
- var/list/currentrun = src.currentrun
-
- while(currentrun.len)
- var/atom/movable/thing = currentrun[currentrun.len]
- currentrun.len--
- if(thing)
- thing.check_in_bounds(wait)
- else
- SSinbounds.processing -= thing
- if(MC_TICK_CHECK)
- return
-
-/datum/controller/subsystem/inbounds/Recover()
- processing = SSinbounds.processing
diff --git a/code/datums/components/stationloving.dm b/code/datums/components/stationloving.dm
new file mode 100644
index 000000000000..a999624c3257
--- /dev/null
+++ b/code/datums/components/stationloving.dm
@@ -0,0 +1,83 @@
+/datum/component/stationloving
+ dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
+ var/inform_admins = FALSE
+ var/disallow_soul_imbue = TRUE
+
+/datum/component/stationloving/Initialize(inform_admins = FALSE)
+ if(!ismovableatom(parent))
+ return COMPONENT_INCOMPATIBLE
+ RegisterSignal(list(COMSIG_MOVABLE_Z_CHANGED), .proc/check_in_bounds)
+ RegisterSignal(list(COMSIG_PARENT_PREQDELETED), .proc/check_deletion)
+ RegisterSignal(list(COMSIG_ITEM_IMBUE_SOUL), .proc/check_soul_imbue)
+ src.inform_admins = inform_admins
+ check_in_bounds() // Just in case something is being created outside of station/centcom
+
+/datum/component/stationloving/InheritComponent(datum/component/stationloving/newc, original, list/arguments)
+ if (original)
+ if (istype(newc))
+ inform_admins = newc.inform_admins
+ else if (LAZYLEN(arguments))
+ inform_admins = arguments[1]
+
+/datum/component/stationloving/proc/relocate()
+ var/targetturf = find_safe_turf()
+ if(!targetturf)
+ if(GLOB.blobstart.len > 0)
+ targetturf = get_turf(pick(GLOB.blobstart))
+ else
+ CRASH("Unable to find a blobstart landmark")
+
+ var/atom/movable/AM = parent
+ if(ismob(AM.loc))
+ var/mob/M = AM.loc
+ M.transferItemToLoc(AM, targetturf, TRUE) //nodrops disks when?
+ else if(AM.loc.SendSignal(COMSIG_CONTAINS_STORAGE))
+ AM.loc.SendSignal(COMSIG_TRY_STORAGE_TAKE, src, targetturf, TRUE)
+ else
+ AM.forceMove(targetturf)
+ // move the disc, so ghosts remain orbiting it even if it's "destroyed"
+ return targetturf
+
+/datum/component/stationloving/proc/check_in_bounds()
+ if(in_bounds())
+ return
+ else
+ var/turf/currentturf = get_turf(src)
+ to_chat(get(parent, /mob), "You can't help but feel that you just lost something back there...")
+ var/turf/targetturf = relocate()
+ log_game("[parent] has been moved out of bounds in [COORD(currentturf)]. Moving it to [COORD(targetturf)].")
+ if(inform_admins)
+ message_admins("[parent] has been moved out of bounds in [ADMIN_COORDJMP(currentturf)]. Moving it to [ADMIN_COORDJMP(targetturf)].")
+
+/datum/component/stationloving/proc/check_soul_imbue()
+ return disallow_soul_imbue
+
+/datum/component/stationloving/proc/in_bounds()
+ var/static/list/allowed_shuttles = typecacheof(list(/area/shuttle/syndicate, /area/shuttle/escape, /area/shuttle/pod_1, /area/shuttle/pod_2, /area/shuttle/pod_3, /area/shuttle/pod_4))
+ var/turf/T = get_turf(parent)
+ if (!T)
+ return FALSE
+ if (is_station_level(T.z) || is_centcom_level(T.z))
+ return TRUE
+ if (is_transit_level(T.z))
+ var/area/A = T.loc
+ if (is_type_in_typecache(A, allowed_shuttles))
+ return TRUE
+
+ return FALSE
+
+/datum/component/stationloving/proc/check_deletion(force) // TRUE = interrupt deletion, FALSE = proceed with deletion
+
+ var/turf/T = get_turf(parent)
+
+ if(inform_admins && force)
+ message_admins("[parent] has been !!force deleted!! in [ADMIN_COORDJMP(T)].")
+ log_game("[parent] has been !!force deleted!! in [COORD(T)].")
+
+ if(!force)
+ var/turf/targetturf = relocate()
+ log_game("[parent] has been destroyed in [COORD(T)]. Moving it to [COORD(targetturf)].")
+ if(inform_admins)
+ message_admins("[parent] has been destroyed in [ADMIN_COORDJMP(T)]. Moving it to [ADMIN_COORDJMP(targetturf)].")
+ return TRUE
+ return FALSE
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 67f34cef741e..c64951ca7cd8 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -251,24 +251,6 @@
return 1
/atom/movable/Destroy(force)
- var/inform_admins = (flags_2 & INFORM_ADMINS_ON_RELOCATE_2)
- var/stationloving = (flags_2 & STATIONLOVING_2)
-
- if(inform_admins && force)
- var/turf/T = get_turf(src)
- message_admins("[src] has been !!force deleted!! in [ADMIN_COORDJMP(T)].")
- log_game("[src] has been !!force deleted!! in [COORD(T)].")
-
- if(stationloving && !force)
- var/turf/currentturf = get_turf(src)
- var/turf/targetturf = relocate()
- log_game("[src] has been destroyed in [COORD(currentturf)]. Moving it to [COORD(targetturf)].")
- if(inform_admins)
- message_admins("[src] has been destroyed in [ADMIN_COORDJMP(currentturf)]. Moving it to [ADMIN_COORDJMP(targetturf)].")
- return QDEL_HINT_LETMELIVE
-
- if(stationloving && force)
- STOP_PROCESSING(SSinbounds, src)
QDEL_NULL(proximity_monitor)
QDEL_NULL(language_holder)
@@ -365,6 +347,7 @@
loc = null
/atom/movable/proc/onTransitZ(old_z,new_z)
+ SendSignal(COMSIG_MOVABLE_Z_CHANGED, old_z, new_z)
for (var/item in src) // Notify contents of Z-transition. This can be overridden IF we know the items contents do not care.
var/atom/movable/AM = item
AM.onTransitZ(old_z,new_z)
@@ -632,88 +615,6 @@
animate(src, pixel_y = initial(pixel_y), time = 10)
floating = FALSE
-/* Stationloving
-*
-* A stationloving atom will always teleport back to the station
-* if it ever leaves the station z-levels or CentCom. It will also,
-* when Destroy() is called, will teleport to a random turf on the
-* station.
-*
-* The turf is guaranteed to be "safe" for normal humans, probably.
-* If the station is SUPER SMASHED UP, it might not work.
-*
-* Here are some important procs:
-* relocate()
-* moves the atom to a safe turf on the station
-*
-* check_in_bounds()
-* regularly called and checks if `in_bounds()` returns true. If false, it
-* triggers a `relocate()`.
-*
-* in_bounds()
-* By default, checks that the atom's z is the station z or centcom.
-*/
-
-/atom/movable/proc/set_stationloving(state, inform_admins=FALSE)
- var/currently = (flags_2 & STATIONLOVING_2)
-
- if(inform_admins)
- flags_2 |= INFORM_ADMINS_ON_RELOCATE_2
- else
- flags_2 &= ~INFORM_ADMINS_ON_RELOCATE_2
-
- if(state == currently)
- return
- else if(!state)
- STOP_PROCESSING(SSinbounds, src)
- flags_2 &= ~STATIONLOVING_2
- else
- START_PROCESSING(SSinbounds, src)
- flags_2 |= STATIONLOVING_2
-
-/atom/movable/proc/relocate()
- var/targetturf = find_safe_turf()
- if(!targetturf)
- if(GLOB.blobstart.len > 0)
- targetturf = get_turf(pick(GLOB.blobstart))
- else
- throw EXCEPTION("Unable to find a blobstart landmark")
-
- if(ismob(loc))
- var/mob/M = loc
- M.transferItemToLoc(src, targetturf, TRUE) //nodrops disks when?
- else if(loc.SendSignal(COMSIG_CONTAINS_STORAGE))
- loc.SendSignal(COMSIG_TRY_STORAGE_TAKE, src, targetturf, TRUE)
- else
- forceMove(targetturf)
- // move the disc, so ghosts remain orbiting it even if it's "destroyed"
- return targetturf
-
-/atom/movable/proc/check_in_bounds()
- if(in_bounds())
- return
- else
- var/turf/currentturf = get_turf(src)
- to_chat(get(src, /mob), "You can't help but feel that you just lost something back there...")
- var/turf/targetturf = relocate()
- log_game("[src] has been moved out of bounds in [COORD(currentturf)]. Moving it to [COORD(targetturf)].")
- if(flags_2 & INFORM_ADMINS_ON_RELOCATE_2)
- message_admins("[src] has been moved out of bounds in [ADMIN_COORDJMP(currentturf)]. Moving it to [ADMIN_COORDJMP(targetturf)].")
-
-/atom/movable/proc/in_bounds()
- var/static/list/allowed_shuttles = typecacheof(list(/area/shuttle/syndicate, /area/shuttle/escape, /area/shuttle/pod_1, /area/shuttle/pod_2, /area/shuttle/pod_3, /area/shuttle/pod_4))
- var/turf/T = get_turf(src)
- if (!T)
- return FALSE
- if (is_station_level(T.z) || is_centcom_level(T.z))
- return TRUE
- if (is_transit_level(T.z))
- var/area/A = T.loc
- if (is_type_in_typecache(A, allowed_shuttles))
- return TRUE
-
- return FALSE
-
/* Language procs */
/atom/movable/proc/get_language_holder(shadow=TRUE)
if(language_holder)
diff --git a/code/modules/admin/verbs/onlyone.dm b/code/modules/admin/verbs/onlyone.dm
index 7c9d89e88d69..05f56047cb82 100644
--- a/code/modules/admin/verbs/onlyone.dm
+++ b/code/modules/admin/verbs/onlyone.dm
@@ -8,7 +8,9 @@ GLOBAL_VAR_INIT(highlander, FALSE)
send_to_playing_players("THERE CAN BE ONLY ONE")
for(var/obj/item/disk/nuclear/N in GLOB.poi_list)
- N.relocate() //Gets it out of bags and such
+ var/datum/component/stationloving/component = N.GetComponent(/datum/component/stationloving)
+ if (component)
+ component.relocate() //Gets it out of bags and such
for(var/mob/living/carbon/human/H in GLOB.player_list)
if(H.stat == DEAD || !(H.client))
diff --git a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm
index 28df23a0b0d7..0e588c438042 100644
--- a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm
+++ b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm
@@ -504,15 +504,12 @@ This is here to make the tiles around the station mininuke change when it's arme
/obj/item/disk/nuclear/Initialize()
. = ..()
- var/tell_the_admins
- // Only tell the admins if a REAL nuke disk is relocated
- if(fake)
- tell_the_admins = FALSE
- else
+ if(!fake)
GLOB.poi_list |= src
- tell_the_admins = TRUE
- set_stationloving(TRUE, inform_admins=tell_the_admins)
+/obj/item/disk/nuclear/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/stationloving, !fake)
/obj/item/disk/nuclear/examine(mob/user)
. = ..()
diff --git a/code/modules/spells/spell_types/lichdom.dm b/code/modules/spells/spell_types/lichdom.dm
index d991a3df9c84..0739a7fb843a 100644
--- a/code/modules/spells/spell_types/lichdom.dm
+++ b/code/modules/spells/spell_types/lichdom.dm
@@ -34,7 +34,7 @@
for(var/obj/item in hand_items)
// I ensouled the nuke disk once. But it's probably a really
// mean tactic, so probably should discourage it.
- if((item.flags_1 & ABSTRACT_1) || (item.flags_1 & NODROP_1) || (item.flags_2 & STATIONLOVING_2))
+ if((item.flags_1 & ABSTRACT_1) || (item.flags_1 & NODROP_1) || item.SendSignal(COMSIG_ITEM_IMBUE_SOUL, user))
continue
marked_item = item
to_chat(M, "You begin to focus your very being into [item]...")
diff --git a/tgstation.dme b/tgstation.dme
index a3612d7a359f..0e902be48091 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -206,7 +206,6 @@
#include "code\controllers\subsystem\garbage.dm"
#include "code\controllers\subsystem\icon_smooth.dm"
#include "code\controllers\subsystem\idlenpcpool.dm"
-#include "code\controllers\subsystem\inbounds.dm"
#include "code\controllers\subsystem\input.dm"
#include "code\controllers\subsystem\ipintel.dm"
#include "code\controllers\subsystem\job.dm"
@@ -333,6 +332,7 @@
#include "code\datums\components\slippery.dm"
#include "code\datums\components\spooky.dm"
#include "code\datums\components\squeek.dm"
+#include "code\datums\components\stationloving.dm"
#include "code\datums\components\swarming.dm"
#include "code\datums\components\thermite.dm"
#include "code\datums\components\wet_floor.dm"