From e49b016df48c33c68bb6865d22706542e24d7eec Mon Sep 17 00:00:00 2001
From: SteelSlayer <42044220+SteelSlayer@users.noreply.github.com>
Date: Sat, 30 Apr 2022 09:50:36 -0500
Subject: [PATCH] Updates the proximity monitor component (#15836)
* prox component update
* mochi review
* bug fix, makes proximity sensors always active
* GC fixes and removes an unused proc
* Disposal fixes from TM
* fixes runtimes when objects created in nullspace runtimes, fixes portable flasher
* a fresh perspective
* lewcc review
* adds comment about direct loc setting over forceMove
---
code/__DEFINES/dcs/signals.dm | 3 +-
code/__HELPERS/unsorted.dm | 21 -
.../subsystem/processing/fields.dm | 6 +
code/datums/components/_component.dm | 22 +
code/datums/components/proximity_monitor.dm | 554 +++++++++++++++---
code/game/objects/effects/effects.dm | 10 +
.../objects/items/devices/transfer_valve.dm | 3 -
.../items/weapons/grenades/chem_grenade.dm | 13 +-
code/modules/assembly/bomb.dm | 12 +-
code/modules/assembly/holder.dm | 6 +-
code/modules/assembly/proximity.dm | 2 +-
code/modules/clothing/masks/miscellaneous.dm | 6 -
.../reagent_containers/glass_containers.dm | 3 -
code/modules/reagents/reagent_dispenser.dm | 3 -
code/modules/recycling/disposal.dm | 4 +
paradise.dme | 1 +
16 files changed, 524 insertions(+), 145 deletions(-)
create mode 100644 code/controllers/subsystem/processing/fields.dm
diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index a2c004a7e4d..9bb6b459fab 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -268,7 +268,8 @@
///called when the movable is added to a disposal holder object for disposal movement: (obj/structure/disposalholder/holder, obj/machinery/disposal/source)
#define COMSIG_MOVABLE_DISPOSING "movable_disposing"
-
+///called when the movable is removed from a disposal holder object: /obj/structure/disposalpipe/proc/expel(): (obj/structure/disposalholder/H, turf/T, direction)
+#define COMSIG_MOVABLE_EXIT_DISPOSALS "movable_exit_disposals"
// /datum/mind signals
diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm
index 58f0716907e..d296e8c37c7 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -1966,27 +1966,6 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
return TRUE
return FALSE
-/**
- * Proc which gets all adjacent turfs to `src`, including the turf that `src` is on.
- *
- * This is similar to doing `for(var/turf/T in range(1, src))`. However it is slightly more performant.
- * Additionally, the above proc becomes more costly the more atoms there are nearby. This proc does not care about that.
- */
-/atom/proc/get_all_adjacent_turfs()
- var/turf/src_turf = get_turf(src)
- var/list/_list = list(
- src_turf,
- get_step(src_turf, NORTH),
- get_step(src_turf, NORTHEAST),
- get_step(src_turf, NORTHWEST),
- get_step(src_turf, SOUTH),
- get_step(src_turf, SOUTHEAST),
- get_step(src_turf, SOUTHWEST),
- get_step(src_turf, EAST),
- get_step(src_turf, WEST)
- )
- return _list
-
// Check if the source atom contains another atom
/atom/proc/contains(atom/location)
if(!location)
diff --git a/code/controllers/subsystem/processing/fields.dm b/code/controllers/subsystem/processing/fields.dm
new file mode 100644
index 00000000000..a4c58b883a8
--- /dev/null
+++ b/code/controllers/subsystem/processing/fields.dm
@@ -0,0 +1,6 @@
+PROCESSING_SUBSYSTEM_DEF(fields)
+ name = "Fields"
+ wait = 2
+ priority = FIRE_PRIORITY_FIELDS
+ flags = SS_KEEP_TIMING | SS_NO_INIT
+ runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
diff --git a/code/datums/components/_component.dm b/code/datums/components/_component.dm
index 7a9a37e9395..5d0415054d2 100644
--- a/code/datums/components/_component.dm
+++ b/code/datums/components/_component.dm
@@ -530,6 +530,28 @@
if(C.can_transfer)
target.TakeComponent(comps)
+/**
+ * Transfer a single component from the source datum, to the target.
+ *
+ * Arguments:
+ * * datum/target - the target to move the component to
+ * * component_instance_or_typepath - either an already created component, or a component typepath
+ */
+/datum/proc/TransferComponent(datum/target, component_instance_or_typepath)
+ if(!datum_components)
+ return
+ // If the proc was fed a typepath.
+ var/datum/component/comp = datum_components[component_instance_or_typepath]
+ if(comp?.can_transfer)
+ target.TakeComponent(comp)
+ return
+ // if the proc was fed a component instance.
+ for(var/component in datum_components)
+ var/datum/component/C = datum_components[component]
+ if(istype(C, component_instance_or_typepath) && C.can_transfer)
+ target.TakeComponent(C)
+ return
+
/**
* Return the object that is the host of any UI's that this component has
*/
diff --git a/code/datums/components/proximity_monitor.dm b/code/datums/components/proximity_monitor.dm
index cf5de38b387..445c03a72ae 100644
--- a/code/datums/components/proximity_monitor.dm
+++ b/code/datums/components/proximity_monitor.dm
@@ -1,139 +1,521 @@
/**
- * # Proximity monitor component
+ * # Basic Proximity Monitor
*
- * Attaching this component to an atom means that the atom will be able to detect mobs/objs moving within a 1 tile of it.
+ * Attaching this component to an atom means that the atom will be able to detect mobs or objects moving within a specified radius of it.
*
- * The component creates several `obj/effect/abstract/proximity_checker` objects, which follow the parent atom around, always making sure it's at the center.
- * When something crosses one of these `proximiy_checker`s, the parent has the `HasProximity()` proc called on it, with the crossing mob/obj as the argument.
+ * The component creates several [/obj/effect/abstract/proximity_checker] objects, which follow the `parent` AKA `hasprox_receiver` around, always making sure it's at the center.
+ * When something crosses one of these proximiy checkers, the `hasprox_receiver` will have the `HasProximity()` proc called on it, with the crossing mob/obj as the argument.
*/
/datum/component/proximity_monitor
- var/atom/owner
- /// A list of currently created `/obj/effect/abstract/proximity_checker`s in use with this component.
+ can_transfer = TRUE
+ var/name = "Proximity detection field"
+ /// The primary atom the component is attached to and that will be receiving `HasProximity()` calls. Same as the `parent`.
+ var/atom/hasprox_receiver
+ /**
+ * A list which contains references to movable atoms which the `hasprox_receiver` has moved into.
+ * Used to handle complex situations where the receiver is nested several layers deep into an object.
+ * For example: inside of a box, that's inside of a bag, which is worn on a human. In this situation there are 3 locations we need to listen to for movement.
+ */
+ var/list/nested_receiver_locs
+ /// The radius of the field, in tiles.
+ var/radius
+ /// A list of currently created [/obj/effect/abstract/proximity_checker] in use with this component.
var/list/proximity_checkers
+ /// The type of checker object that should be used for the main field.
+ var/field_checker_type = /obj/effect/abstract/proximity_checker
+ /// Should the parent always detect proximity and update the field on movement, even if it's not on a turf?
+ var/always_active
-/datum/component/proximity_monitor/Initialize()
+/datum/component/proximity_monitor/Initialize(_radius = 1, _always_active = FALSE)
. = ..()
- if(!isatom(parent))
+ if(!ismovable(parent) && !isturf(parent)) // No areas or datums allowed.
return COMPONENT_INCOMPATIBLE
- owner = parent
+ ASSERT(_radius >= 1)
+ hasprox_receiver = parent
+ radius = _radius
+ always_active = _always_active
+ nested_receiver_locs = list()
create_prox_checkers()
+ if(isturf(hasprox_receiver.loc))
+ toggle_checkers(TRUE)
+ else if(always_active)
+ toggle_checkers(TRUE)
+ else
+ toggle_checkers(FALSE)
+
/datum/component/proximity_monitor/Destroy(force, silent)
+ hasprox_receiver = null
+ nested_receiver_locs.Cut()
QDEL_LIST(proximity_checkers)
- owner = null
return ..()
/datum/component/proximity_monitor/RegisterWithParent()
- . = ..()
- if(ismovable(parent))
- RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/HandleMove)
+ if(ismovable(hasprox_receiver))
+ RegisterSignal(hasprox_receiver, COMSIG_MOVABLE_MOVED, .proc/on_receiver_move)
+ RegisterSignal(hasprox_receiver, COMSIG_MOVABLE_DISPOSING, .proc/on_disposal_enter)
+ RegisterSignal(hasprox_receiver, COMSIG_MOVABLE_EXIT_DISPOSALS, .proc/on_disposal_exit)
+ map_nested_locs()
/datum/component/proximity_monitor/UnregisterFromParent()
- . = ..()
- if(ismovable(parent))
- UnregisterSignal(parent, COMSIG_MOVABLE_MOVED)
+ if(ismovable(hasprox_receiver))
+ UnregisterSignal(hasprox_receiver, list(COMSIG_MOVABLE_MOVED, COMSIG_MOVABLE_DISPOSING, COMSIG_MOVABLE_EXIT_DISPOSALS))
+ clear_nested_locs()
/**
- * Called when the `parent` receives the `COMSIG_MOVABLE_MOVED` signal, which occurs when it `Move()`s
- *
- * Code is only ran when there is no `Dir`, which occurs when the parent is teleported, gets placed into a storage item, dropped, or picked up.
- * Normal movement, for example moving 1 tile to the west, is handled by the `proximity_checker` objects.
+ * Called when the `hasprox_receiver` moves.
*
* Arguments:
- * * source - this will be the `parent`
- * * OldLoc - the location the parent just moved from
- * * Dir - the direction the parent just moved in
- * * forced - if we were forced to move
+ * * datum/source - this will be the `hasprox_receiver`
+ * * atom/old_loc - the location the receiver just moved from
+ * * dir - the direction the reciever just moved in
*/
-/datum/component/proximity_monitor/proc/HandleMove(datum/source, atom/OldLoc, Dir, forced)
- if(!Dir) // No dir means the parent teleported, or moved in a non-standard way like getting placed into disposals, onto a table, dropped, picked up, etc.
- recenter_prox_checkers()
+/datum/component/proximity_monitor/proc/on_receiver_move(datum/source, atom/old_loc, dir)
+ SIGNAL_HANDLER
+
+ // It was just a normal tile-based move, so we return here.
+ if(dir)
+ move_prox_checkers(dir)
+ return
+
+ // Moving onto a turf.
+ if(!isturf(old_loc) && isturf(hasprox_receiver.loc))
+ toggle_checkers(TRUE)
+ clear_nested_locs()
+
+ // Moving into an object.
+ else if(always_active)
+ toggle_checkers(TRUE)
+ map_nested_locs()
+
+ // The receiver moved into something, but isn't `always_active`, so deactivate the checkers.
+ else
+ toggle_checkers(FALSE)
+
+ recenter_prox_checkers()
/**
- * Called in Initialize(). Generates a set of `/obj/effect/abstract/proximity_checker` objects around the parent, and registers signals to them.
+ * Called when an atom in `nested_receiver_locs` list moves, if one exists.
+ *
+ * Arguments:
+ * * atom/moved_atom - one of the atoms in `nested_receiver_locs`
+ * * atom/old_loc - the location `moved_atom` just moved from
+ * * dir - the direction `moved_atom` just moved in
+ */
+/datum/component/proximity_monitor/proc/on_nested_loc_move(atom/moved_atom, atom/old_loc, dir)
+ SIGNAL_HANDLER
+
+ // It was just a normal tile-based move, so we return here.
+ if(dir)
+ move_prox_checkers(dir)
+ return
+
+ // Moving onto a turf.
+ if(!isturf(old_loc) && isturf(moved_atom.loc))
+ toggle_checkers(TRUE)
+
+ map_nested_locs()
+ recenter_prox_checkers()
+
+/**
+ * Called when the receiver or an atom in the `nested_receiver_locs` list moves into a disposals holder object.
+ *
+ * This proc recieves arguments, but they aren't needed.
+ */
+/datum/component/proximity_monitor/proc/on_disposal_enter(datum/source)
+ SIGNAL_HANDLER
+
+ toggle_checkers(FALSE)
+
+/**
+ * Called when the receiver or an atom in the `nested_receiver_locs` list moves out of a disposals holder object.
+ *
+ * This proc recieves arguments, but they aren't needed.
+ */
+/datum/component/proximity_monitor/proc/on_disposal_exit(datum/source)
+ SIGNAL_HANDLER
+
+ toggle_checkers(TRUE)
+
+/**
+ * Registers signals to any nested locations the `hasprox_receiver` is in, excluding turfs, so they can be monitored for movement.
+ */
+/datum/component/proximity_monitor/proc/map_nested_locs()
+ clear_nested_locs()
+ var/atom/loc_to_check = hasprox_receiver.loc
+
+ while(loc_to_check && !isturf(loc_to_check))
+ if(loc_to_check in nested_receiver_locs)
+ continue
+ nested_receiver_locs += loc_to_check
+ RegisterSignal(loc_to_check, COMSIG_MOVABLE_MOVED, .proc/on_nested_loc_move)
+ RegisterSignal(loc_to_check, COMSIG_MOVABLE_DISPOSING, .proc/on_disposal_enter)
+ RegisterSignal(loc_to_check, COMSIG_MOVABLE_EXIT_DISPOSALS, .proc/on_disposal_exit)
+ loc_to_check = loc_to_check.loc
+
+/**
+ * Removes and unregisters signals from all objects currently in the `nested_receiver_locs` list.
+ */
+/datum/component/proximity_monitor/proc/clear_nested_locs()
+ for(var/nested_loc in nested_receiver_locs)
+ UnregisterSignal(nested_loc, list(COMSIG_MOVABLE_MOVED, COMSIG_MOVABLE_DISPOSING, COMSIG_MOVABLE_EXIT_DISPOSALS))
+ nested_receiver_locs = list()
+
+/**
+ * Relays basic directional movement from the `hasprox_receiver` or `host`, to all objects in the `proximity_checkers` list.
+ *
+ * Arguments:
+ * * move_dir - the direction the checkers should move in
+ */
+/datum/component/proximity_monitor/proc/move_prox_checkers(move_dir)
+ for(var/obj/P as anything in proximity_checkers)
+ P.loc = get_step(P, move_dir)
+
+/**
+ * Update all of the component's proximity checker's to either become active or not active.
+ *
+ * Arguments:
+ * * new_active - the value to be assigned to the proximity checker's `active` variable
+ */
+/datum/component/proximity_monitor/proc/toggle_checkers(new_active)
+ for(var/obj/effect/abstract/proximity_checker/P as anything in proximity_checkers)
+ P.active = new_active
+
+/**
+ * Specifies a new radius for the field. Creates or deletes proximity_checkers accordingly.
+ *
+ * This proc *can* have a high cost due to the `new`s and `qdel`s of the proximity checkers, depending on the number of calls you need to make to it.
+ *
+ * Arguments:
+ * new_radius - the new value that `proximity_radius` should be set to.
+ */
+/datum/component/proximity_monitor/proc/set_radius(new_radius)
+ ASSERT(new_radius >= 1)
+
+ var/new_field_size = (1 + new_radius * 2) ** 2
+ var/old_field_size = length(proximity_checkers)
+ radius = new_radius
+
+ // Radius is decreasing.
+ if(new_field_size < old_field_size)
+ for(var/i in 1 to (old_field_size - new_field_size))
+ qdel(proximity_checkers[length(proximity_checkers)]) // Pop the last entry.
+ // Radius is increasing.
+ else
+ var/turf/parent_turf = get_turf(parent)
+ for(var/i in 1 to (new_field_size - old_field_size))
+ create_single_prox_checker(parent_turf)
+ recenter_prox_checkers()
+
+/**
+ * Creates a single proximity checker object, at the given location and of the given type. Adds it to the proximity checkers list.
+ *
+ * Arguments:
+ * * turf/T - the turf the checker should be created on
+ * * checker_type - the type of [/obj/item/abstract/proximity_checker] to create
+ */
+/datum/component/proximity_monitor/proc/create_single_prox_checker(turf/T, checker_type = field_checker_type)
+ var/obj/effect/abstract/proximity_checker/P = new checker_type(T, src)
+ proximity_checkers += P
+ return P
+
+/**
+ * Called in Initialize(). Generates a set of [proximity checker][/obj/effect/abstract/proximity_checker] objects around the parent.
*/
/datum/component/proximity_monitor/proc/create_prox_checkers()
- proximity_checkers = list()
- for(var/turf/T in range(1, get_turf(parent)))
- var/obj/effect/abstract/proximity_checker/P = new(T, parent)
- proximity_checkers += P
- // Basic movement for the proximity_checker objects. The objects will move 1 tile in the direction the parent just moved.
- P.RegisterSignal(parent, COMSIG_MOVABLE_MOVED, /obj/effect/abstract/proximity_checker/.proc/HandleMove)
+ LAZYINITLIST(proximity_checkers)
+ var/turf/parent_turf = get_turf(parent)
+ // For whatever reason their turf is null. Create the checkers in nullspace for now. When the parent moves to a valid turf, they can be recenetered.
+ if(!parent_turf)
+ // Since we can't use `in range` in nullspace, we need to calculate the number of checkers to create with the below formula.
+ var/checker_amount = (1 + radius * 2) ** 2
+ for(var/i in 1 to checker_amount)
+ create_single_prox_checker(null)
+ return
+ for(var/T in RANGE_TURFS(radius, parent_turf))
+ create_single_prox_checker(T)
/**
- * Re-centers all of the parent's `proximity_checker`s around its current location.
+ * Re-centers all of the `proximity_checker`s around the parent's current location.
*/
/datum/component/proximity_monitor/proc/recenter_prox_checkers()
- var/list/prox_checkers = owner.get_all_adjacent_turfs()
- for(var/checker in proximity_checkers)
- var/obj/effect/abstract/proximity_checker/P = checker
- P.loc = pick_n_take(prox_checkers)
+ var/turf/parent_turf = get_turf(parent)
+ if(!parent_turf)
+ toggle_checkers(FALSE)
+ return // Need a sanity check here for certain situations like objects moving into disposal holders. Their turf will be null in these cases.
+ var/index = 1
+ for(var/T in RANGE_TURFS(radius, parent_turf))
+ var/obj/checker = proximity_checkers[index++]
+ checker.loc = T
+
/**
- * # Proximity checker abstract object
+ * # Advanced Proximity Monitor
*
- * Inteded for use with the proximity checker component (/datum/component/proximity_monitor).
+ * This component functions similar to the basic version, however it has some extra features:
+ *
+ * First of all, if the field radius is more than 1 tile, you have the option to make a distinction between inner proximity checkers, versus ones along the edge.
+ * You can specifiy which type of [/obj/effect/abstract/proximity_checker] objects you want to use for both inner, and edge checkers.
+ *
+ * Secondly, the advanced proximity monitor has the ability to use processing (the `process` proc). This is optional however.
+ * Each proximity checker object can process itself or other things on it's turf as needed. It's up to you on how you want to use it.
+ * Inner and edge checkers can process thing seperately. You can turn off processing for field checkers and have only edge checkers process, and vice versa.
+ */
+/datum/component/proximity_monitor/advanced
+ name = "Advanced energy field"
+ field_checker_type = /obj/effect/abstract/proximity_checker/advanced/inner_field
+ /// The type of checker object that should be used for the field edges.
+ var/edge_checker_type = /obj/effect/abstract/proximity_checker/advanced/edge_field
+ /// Make a distinction between edge checkers and field checkers seperately.
+ var/uses_edge_checkers = FALSE
+ /// Do any of the proximity_checker objects need to process things sitting on their tile?
+ var/requires_processing = FALSE
+ /// Should the main field checkers process things on their tile?
+ var/process_field_checkers = FALSE
+ /// Should the edge field checkers process things on their tile?
+ var/process_edge_checkers = FALSE
+ /// A list of proximity_checkers in the inner field. Excludes checkers on the edge of the field.
+ var/list/field_checkers
+ /// A list of proximity_checkers on the edge of the field.
+ var/list/edge_checkers
+
+/datum/component/proximity_monitor/advanced/Initialize(_radius = 1, _always_active = FALSE)
+ . = ..()
+ if(requires_processing)
+ START_PROCESSING(SSfields, src)
+
+/datum/component/proximity_monitor/advanced/Destroy(force, silent)
+ STOP_PROCESSING(SSfields, src)
+ QDEL_LIST(field_checkers)
+ QDEL_LIST(edge_checkers)
+ return ..()
+
+/datum/component/proximity_monitor/advanced/create_prox_checkers()
+ if(!uses_edge_checkers)
+ ..() // We don't need to make a distinction between field and edge checkers, use the parent.
+ if(process_field_checkers)
+ field_checkers = proximity_checkers.Copy() // Still allows for field checkers to use processing.
+ return
+
+ LAZYINITLIST(proximity_checkers)
+ LAZYINITLIST(field_checkers)
+ LAZYINITLIST(edge_checkers)
+
+ var/turf/parent_turf = get_turf(parent)
+ for(var/T in RANGE_TURFS(radius, parent_turf))
+ if(get_dist(T, parent_turf) == radius)
+ edge_checkers += create_single_prox_checker(T, edge_checker_type)
+ continue
+ field_checkers += create_single_prox_checker(T)
+
+/datum/component/proximity_monitor/advanced/recenter_prox_checkers()
+ if(!uses_edge_checkers)
+ return ..() // We don't need to make a distinction between field and edge checkers, use the parent.
+
+ var/turf/parent_turf = get_turf(parent)
+ var/inner_index = 1
+ var/edge_index = 1
+
+ for(var/T in RANGE_TURFS(radius, parent_turf))
+ var/obj/checker
+ if(get_dist(T, parent_turf) == radius) // If it's at this distance, it's on the edge of the field.
+ checker = edge_checkers[edge_index++]
+ checker.loc = T
+ continue
+ checker = field_checkers[inner_index++]
+ checker.loc = T
+
+/datum/component/proximity_monitor/advanced/process()
+ if(process_field_checkers)
+ for(var/checker in field_checkers)
+ process_inner_checker(checker)
+ if(process_edge_checkers)
+ for(var/checker in field_checkers)
+ process_edge_checker(checker)
+
+/**
+ * Base proc. All processing-related actions associated with inner proximity checkers should go here.
+ *
+ * Arguments:
+ * * obj/effect/abstract/proximity_checker/advanced/inner_field/F - the proximity checker to process
+ */
+/datum/component/proximity_monitor/advanced/proc/process_inner_checker(obj/effect/abstract/proximity_checker/advanced/inner_field/F)
+ return
+
+/**
+ * Base proc. All processing-related actions associated with edge proximity checkers should go here.
+ *
+ * Arguments:
+ * * obj/effect/abstract/proximity_checker/advanced/edge_field/F - the proximity checker to process
+ */
+/datum/component/proximity_monitor/advanced/proc/process_edge_checker(obj/effect/abstract/proximity_checker/advanced/edge_field/F)
+ return
+
+/**
+ * Base proc. Checks if `AM` can pass the inner field checker.
+ *
+ * Arguments:
+ * * atom/movable/AM - the atom trying to pass the inner field checker object
+ * * obj/effect/abstract/proximity_checker/advanced/inner_field/F - the proximity checker object `AM` is trying to pass
+ * * turf/entering - the turf `AM` is entering from
+ */
+/datum/component/proximity_monitor/advanced/proc/inner_field_canpass(atom/movable/AM, obj/effect/abstract/proximity_checker/advanced/inner_field/F, turf/entering)
+ return TRUE
+
+/**
+ * Base proc. Called when something crosses an inner field checker.
+ *
+ * Arguments:
+ * * atom/movable/AM - the atom crossing the inner field checker object
+ * * obj/effect/abstract/proximity_checker/advanced/inner_field/F - the proximity checker object `AM` getting crossed
+ */
+/datum/component/proximity_monitor/advanced/proc/inner_field_crossed(atom/movable/AM, obj/effect/abstract/proximity_checker/advanced/inner_field/F)
+ return TRUE
+
+/**
+ * Base proc. Called when something uncrosses an inner field checker.
+ *
+ * Arguments:
+ * * atom/movable/AM - the atom uncrossing the inner field checker object
+ * * obj/effect/abstract/proximity_checker/advanced/inner_field/F - the proximity checker object `AM` getting uncrossed
+ */
+/datum/component/proximity_monitor/advanced/proc/inner_field_uncrossed(atom/movable/AM, obj/effect/abstract/proximity_checker/advanced/inner_field/F)
+ return TRUE
+
+/**
+ * Base proc. Checks if `AM` can pass the edge field checker.
+ *
+ * Arguments:
+ * * atom/movable/AM - the atom trying to pass the edge field checker object
+ * * obj/effect/abstract/proximity_checker/advanced/edge_field/F - the proximity checker object `AM` is trying to pass
+ * * turf/entering - the turf `AM` is entering from
+ */
+/datum/component/proximity_monitor/advanced/proc/edge_field_canpass(atom/movable/AM, obj/effect/abstract/proximity_checker/advanced/edge_field/F, turf/entering)
+ return TRUE
+
+/**
+ * Base proc. Called when something crosses an edge field checker.
+ *
+ * Arguments:
+ * * atom/movable/AM - the atom crossing the edge field checker object
+ * * obj/effect/abstract/proximity_checker/advanced/edge_field/F - the proximity checker object `AM` getting crossed
+ */
+/datum/component/proximity_monitor/advanced/proc/edge_field_crossed(atom/movable/AM, obj/effect/abstract/proximity_checker/advanced/edge_field/F)
+ return TRUE
+
+/**
+ * Base proc. Called when something uncrosses an edge field checker.
+ *
+ * Arguments:
+ * * atom/movable/AM - the atom uncrossing the edge field checker object
+ * * obj/effect/abstract/proximity_checker/advanced/edge_field/F - the proximity checker object `AM` getting uncrossed
+ */
+/datum/component/proximity_monitor/advanced/proc/edge_field_uncrossed(atom/movable/AM, obj/effect/abstract/proximity_checker/advanced/edge_field/F)
+ return TRUE
+
+
+/**
+ * # Basic Proximity Checker
+ *
+ * Inteded for use with the proximity checker component [/datum/component/proximity_monitor].
* Whenever a movable atom crosses this object, it calls `HasProximity()` on the object which is listening for proximity (`hasprox_receiver`).
+ *
+ * Because these objects try to make the smallest footprint possible, when these objects move **they should use direct `loc` setting only, and not `forceMove`!**
+ * The overhead for forceMove is very unnecessary, because for example turfs and areas don't need to know when these have entered or exited them, etc.
+ * These are invisible objects who's sole purpose is to simply inform the receiver that something has moved within X tiles of the it.
*/
/obj/effect/abstract/proximity_checker
- name = "Proximity checker"
+ name = "proximity checker"
+ /// The component that this object is in use with, and that will receive `HasProximity()` calls.
+ var/datum/component/proximity_monitor/monitor
/// Whether or not the proximity checker is listening for things crossing it.
var/active
- /// The linked atom which has the proximity_monitor component, and will recieve the `HasProximity()` calls.
- var/atom/hasprox_receiver
-// If this object is initialized without a `_hasprox_receiver` arg, it is qdel'd.
-/obj/effect/abstract/proximity_checker/Initialize(mapload, atom/_hasprox_receiver)
- if(_hasprox_receiver)
- hasprox_receiver = _hasprox_receiver
- RegisterSignal(hasprox_receiver, COMSIG_PARENT_QDELETING, .proc/OnParentDeletion)
- if(isturf(hasprox_receiver.loc)) // if the reciever is inside a locker/crate/etc, they don't detect proximity
- active = TRUE
- else
- stack_trace("/obj/effect/abstract/proximity_checker created without a receiver")
- return INITIALIZE_HINT_QDEL
- return ..()
+/obj/effect/abstract/proximity_checker/Initialize(mapload, datum/component/proximity_monitor/P)
+ . = ..()
+ monitor = P
/obj/effect/abstract/proximity_checker/Destroy()
- hasprox_receiver = null
+ monitor.proximity_checkers -= src
+ monitor = null
return ..()
/**
- * Called when the `hasprox_receiver` receives the `COMSIG_PARENT_QDELETING` signal. When the receiver is deleted, so is this object.
+ * Called when something crossed over the proximity_checker. Notifies the `hasprox_receiver` it has proximity with something.
*
- * Arugments:
- * * source - this will be the `hasprox_receiver`
- * * force - the force flag taken from the qdel proc currently running on `hasprox_receiver`
- */
-/obj/effect/abstract/proximity_checker/proc/OnParentDeletion(datum/source, force = FALSE)
- qdel(src)
-
-/**
- * Something crossed over the proximity_checker. Notify the `hasprox_receiver` it has proximity with something. Only fires if the checker is `active`.
+ * Arguments:
+ * * atom/movable/AM - the atom crossing the proximity checker
+ * * oldloc - the location `AM` used to be at
*/
/obj/effect/abstract/proximity_checker/Crossed(atom/movable/AM, oldloc)
set waitfor = FALSE
- if(active)
- hasprox_receiver.HasProximity(AM)
+ . = ..()
+ if(active && AM != monitor.hasprox_receiver && !(AM in monitor.nested_receiver_locs))
+ monitor.hasprox_receiver.HasProximity(AM)
/**
- * Moves the proximity_checker 1 tile in the `Dir` direction.
+ * # Advanced Proximity Checker
*
- * If `Dir` is null it will be recentered around the receiver via the `recenter_prox_checkers()` proc.
- * If the new location of the receiver is NOT a turf, set `active` to FALSE, so that it does not receive proximity calls.
- * If the new location of the receiver IS a turf, set `active` to TRUE, so that it can receive proximity calls again.
- *
- * Arguments:
- * * source - this will be the `hasprox_receiver`
- * * OldLoc - the location the `hasprox_receiver` just moved from
- * * Dir - the direction the `hasprox_receiver` just moved in
- * * forced - if we were forced to move
+ * Like basic proximity checkers, these objects can also detect proximity.
+ * However these are meant for when you need to have some additional (more advanced) behavior on top of what basic proximity checkers can do.
*/
-/obj/effect/abstract/proximity_checker/proc/HandleMove(datum/source, atom/OldLoc, Dir, forced)
- if(Dir)
- loc = get_step(src, Dir) // Basic movement 1 tile in some direction.
- return
- if(!isturf(hasprox_receiver.loc))
- active = FALSE // Receiver shouldn't detect proximity while picked up, in a backpack, closet, etc.
- else
- active = TRUE // Receiver can detect proximity again because it's on a turf.
+/obj/effect/abstract/proximity_checker/advanced
+ name = "advanced proximity checker"
+ /// `hasprox_receivers`s advanced proximity monitor component.
+ var/datum/component/proximity_monitor/advanced/advanced_monitor
+
+/obj/effect/abstract/proximity_checker/advanced/Initialize(mapload, datum/component/proximity_monitor/advanced/P, _always_active)
+ advanced_monitor = P
+ return ..()
+
+/obj/effect/abstract/proximity_checker/advanced/Destroy()
+ advanced_monitor = null
+ return ..()
+
+/**
+ * # Inner Field Proximity Checker
+ *
+ * An advanced proximity checker object which sits on the the inner tiles of a field.
+ */
+/obj/effect/abstract/proximity_checker/advanced/inner_field
+ name = "inner field"
+
+/obj/effect/abstract/proximity_checker/advanced/inner_field/Destroy()
+ advanced_monitor.field_checkers -= src
+ return ..()
+
+/obj/effect/abstract/proximity_checker/advanced/inner_field/Crossed(atom/movable/AM, oldloc)
+ . = ..()
+ return advanced_monitor.inner_field_crossed(AM, src)
+
+/obj/effect/abstract/proximity_checker/advanced/inner_field/Uncrossed(atom/movable/AM, oldloc)
+ . = ..()
+ return advanced_monitor.inner_field_uncrossed(AM, src)
+
+/obj/effect/abstract/proximity_checker/advanced/inner_field/CanPass(atom/movable/mover, turf/target, height)
+ . = ..()
+ return advanced_monitor.inner_field_canpass(mover, src, target)
+
+/**
+ * # Edge Field Proximity Checker
+ *
+ * An advanced proximity checker object which sits on the outer edge tiles of a field.
+ */
+/obj/effect/abstract/proximity_checker/advanced/edge_field
+ name = "edge field"
+
+/obj/effect/abstract/proximity_checker/advanced/edge_field/Destroy()
+ advanced_monitor.edge_checkers -= src
+ return ..()
+
+/obj/effect/abstract/proximity_checker/advanced/edge_field/Crossed(atom/movable/AM, oldloc)
+ . = ..()
+ return advanced_monitor.edge_field_crossed(AM, src)
+
+/obj/effect/abstract/proximity_checker/advanced/edge_field/Uncrossed(atom/movable/AM)
+ . = ..()
+ return advanced_monitor.edge_field_uncrossed(AM, src)
+
+/obj/effect/abstract/proximity_checker/advanced/edge_field/CanPass(atom/movable/mover, turf/target, height)
+ . = ..()
+ return advanced_monitor.edge_field_canpass(mover, src, target)
diff --git a/code/game/objects/effects/effects.dm b/code/game/objects/effects/effects.dm
index 247a29fa13f..5cdd4261314 100644
--- a/code/game/objects/effects/effects.dm
+++ b/code/game/objects/effects/effects.dm
@@ -61,6 +61,7 @@
density = FALSE
icon = null
icon_state = null
+ armor = list(MELEE = 100, BULLET = 100, LASER = 100, ENERGY = 100, BOMB = 100, BIO = 100, RAD = 100, FIRE = 100, ACID = 100)
// Most of these overrides procs below are overkill, but better safe than sorry.
/obj/effect/abstract/swarmer_act()
@@ -81,6 +82,15 @@
/obj/effect/abstract/ex_act(severity)
return
+/obj/effect/abstract/blob_act()
+ return
+
+/obj/effect/abstract/acid_act()
+ return
+
+/obj/effect/abstract/fire_act()
+ return
+
/obj/effect/decal
plane = FLOOR_PLANE
resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF
diff --git a/code/game/objects/items/devices/transfer_valve.dm b/code/game/objects/items/devices/transfer_valve.dm
index d52f38c79cd..59b7659756a 100644
--- a/code/game/objects/items/devices/transfer_valve.dm
+++ b/code/game/objects/items/devices/transfer_valve.dm
@@ -62,8 +62,6 @@
to_chat(user, "You attach [A] to the valve controls and secure it.")
A.holder = src
A.toggle_secure() //this calls update_icon(), which calls update_icon() on the holder (i.e. the bomb).
- if(istype(attached_device, /obj/item/assembly/prox_sensor))
- AddComponent(/datum/component/proximity_monitor)
investigate_log("[key_name(user)] attached a [A] to a transfer valve.", INVESTIGATE_BOMB)
add_attack_logs(user, src, "attached [A] to a transfer valve", ATKLOG_FEW)
@@ -139,7 +137,6 @@
attached_device.forceMove(get_turf(src))
attached_device.holder = null
attached_device = null
- qdel(GetComponent(/datum/component/proximity_monitor))
update_icon()
else
. = FALSE
diff --git a/code/game/objects/items/weapons/grenades/chem_grenade.dm b/code/game/objects/items/weapons/grenades/chem_grenade.dm
index 519be57446e..44fe1c1a920 100644
--- a/code/game/objects/items/weapons/grenades/chem_grenade.dm
+++ b/code/game/objects/items/weapons/grenades/chem_grenade.dm
@@ -176,7 +176,7 @@
if(I.reagents.total_volume)
to_chat(user, "You add [I] to the assembly.")
user.drop_item()
- I.loc = src
+ I.forceMove(src)
beakers += I
else
to_chat(user, "[I] is empty.")
@@ -190,10 +190,8 @@
user.drop_item()
nadeassembly = A
- if(nadeassembly.has_prox_sensors())
- AddComponent(/datum/component/proximity_monitor)
A.master = src
- A.loc = src
+ A.forceMove(src)
assemblyattacher = user.ckey
stage = WIRED
to_chat(user, "You add [A] to [src]!")
@@ -218,13 +216,12 @@
payload_name = null
label = null
if(nadeassembly)
- nadeassembly.loc = get_turf(src)
+ nadeassembly.forceMove(get_turf(src))
nadeassembly.master = null
nadeassembly = null
- qdel(GetComponent(/datum/component/proximity_monitor))
if(beakers.len)
for(var/obj/O in beakers)
- O.loc = get_turf(src)
+ O.forceMove(get_turf(src))
beakers = list()
update_icon()
@@ -307,8 +304,6 @@
/obj/item/grenade/chem_grenade/proc/CreateDefaultTrigger(typekey)
if(ispath(typekey,/obj/item/assembly))
nadeassembly = new(src)
- if(nadeassembly.has_prox_sensors())
- AddComponent(/datum/component/proximity_monitor)
nadeassembly.a_left = new /obj/item/assembly/igniter(nadeassembly)
nadeassembly.a_left.holder = nadeassembly
nadeassembly.a_left.secured = 1
diff --git a/code/modules/assembly/bomb.dm b/code/modules/assembly/bomb.dm
index 855c113a71a..5846a679e6e 100644
--- a/code/modules/assembly/bomb.dm
+++ b/code/modules/assembly/bomb.dm
@@ -12,10 +12,6 @@
var/obj/item/tank/bombtank = null //the second part of the bomb is a plasma tank
origin_tech = "materials=1;engineering=1"
-/obj/item/onetankbomb/ComponentInitialize()
- . = ..()
- AddComponent(/datum/component/proximity_monitor)
-
/obj/item/onetankbomb/examine(mob/user)
. = ..()
. += bombtank.examine(user)
@@ -41,10 +37,10 @@
if(!I.use_tool(src, user, 0, volume = I.tool_volume))
return
to_chat(user, "You disassemble [src].")
- bombassembly.loc = user.loc
+ bombassembly.forceMove(user.loc)
bombassembly.master = null
bombassembly = null
- bombtank.loc = user.loc
+ bombtank.forceMove(user.loc)
bombtank.master = null
bombtank = null
qdel(src)
@@ -119,11 +115,11 @@
R.bombassembly = S //Tell the bomb about its assembly part
S.master = R //Tell the assembly about its new owner
- S.loc = R //Move the assembly out of the fucking way
+ S.forceMove(R) //Move the assembly out of the fucking way
R.bombtank = src //Same for tank
master = R
- loc = R
+ forceMove(R)
R.update_icon()
return
diff --git a/code/modules/assembly/holder.dm b/code/modules/assembly/holder.dm
index 7f3edf98f46..c664bccdb20 100644
--- a/code/modules/assembly/holder.dm
+++ b/code/modules/assembly/holder.dm
@@ -50,8 +50,6 @@
A2.holder = src
a_left = A1
a_right = A2
- if(has_prox_sensors())
- AddComponent(/datum/component/proximity_monitor)
name = "[A1.name]-[A2.name] assembly"
update_icon()
return TRUE
@@ -183,10 +181,10 @@
return FALSE
if(a_left)
a_left.holder = null
- a_left.loc = T
+ a_left.forceMove(T)
if(a_right)
a_right.holder = null
- a_right.loc = T
+ a_right.forceMove(T)
qdel(src)
diff --git a/code/modules/assembly/proximity.dm b/code/modules/assembly/proximity.dm
index 9568543e912..c28da74e342 100644
--- a/code/modules/assembly/proximity.dm
+++ b/code/modules/assembly/proximity.dm
@@ -15,7 +15,7 @@
/obj/item/assembly/prox_sensor/ComponentInitialize()
. = ..()
- AddComponent(/datum/component/proximity_monitor)
+ AddComponent(/datum/component/proximity_monitor, _always_active = TRUE)
/obj/item/assembly/prox_sensor/describe()
if(timing)
diff --git a/code/modules/clothing/masks/miscellaneous.dm b/code/modules/clothing/masks/miscellaneous.dm
index cb2b402b2a2..ef726ec6da7 100644
--- a/code/modules/clothing/masks/miscellaneous.dm
+++ b/code/modules/clothing/masks/miscellaneous.dm
@@ -146,7 +146,6 @@
trigger.forceMove(src)
trigger.master = src
trigger.holder = src
- AddComponent(/datum/component/proximity_monitor)
to_chat(user, "You attach [W] to [src].")
return TRUE
else if(istype(W, /obj/item/assembly))
@@ -166,7 +165,6 @@
trigger.master = null
trigger.holder = null
trigger = null
- qdel(GetComponent(/datum/component/proximity_monitor))
/obj/item/clothing/mask/muzzle/safety/shock/proc/can_shock(obj/item/clothing/C)
if(istype(C))
@@ -188,10 +186,6 @@
M.Jitter(20)
return
-/obj/item/clothing/mask/muzzle/safety/shock/HasProximity(atom/movable/AM)
- if(trigger)
- trigger.HasProximity(AM)
-
/obj/item/clothing/mask/muzzle/safety/shock/hear_talk(mob/living/M as mob, list/message_pieces)
if(trigger)
diff --git a/code/modules/reagents/reagent_containers/glass_containers.dm b/code/modules/reagents/reagent_containers/glass_containers.dm
index 7dc15e82155..f9817b26b82 100644
--- a/code/modules/reagents/reagent_containers/glass_containers.dm
+++ b/code/modules/reagents/reagent_containers/glass_containers.dm
@@ -170,7 +170,6 @@
to_chat(usr, "You detach [assembly] from [src]")
usr.put_in_hands(assembly)
assembly = null
- qdel(GetComponent(/datum/component/proximity_monitor))
update_icon()
else
to_chat(usr, "There is no assembly to remove.")
@@ -187,8 +186,6 @@
assembly = W
user.drop_item()
W.forceMove(src)
- if(assembly.has_prox_sensors())
- AddComponent(/datum/component/proximity_monitor)
overlays += "assembly"
else
..()
diff --git a/code/modules/reagents/reagent_dispenser.dm b/code/modules/reagents/reagent_dispenser.dm
index 2a0e07b5df3..f556725fbaa 100644
--- a/code/modules/reagents/reagent_dispenser.dm
+++ b/code/modules/reagents/reagent_dispenser.dm
@@ -153,7 +153,6 @@
usr.visible_message("[usr] detaches [rig] from [src].", "You detach [rig] from [src].")
rig.forceMove(get_turf(usr))
rig = null
- qdel(GetComponent(/datum/component/proximity_monitor))
lastrigger = null
overlays.Cut()
@@ -176,8 +175,6 @@
rig = H
user.drop_item()
H.forceMove(src)
- if(rig.has_prox_sensors())
- AddComponent(/datum/component/proximity_monitor)
var/icon/test = getFlatIcon(H)
test.Shift(NORTH, 1)
test.Shift(EAST, 6)
diff --git a/code/modules/recycling/disposal.dm b/code/modules/recycling/disposal.dm
index a00cf583a7d..87e10c00a2b 100644
--- a/code/modules/recycling/disposal.dm
+++ b/code/modules/recycling/disposal.dm
@@ -804,6 +804,8 @@
for(var/atom/movable/AM in H)
AM.forceMove(T)
AM.pipe_eject(direction)
+ SEND_SIGNAL(AM, COMSIG_MOVABLE_EXIT_DISPOSALS)
+
spawn(1)
if(AM)
AM.throw_at(target, 100, 1)
@@ -819,6 +821,8 @@
AM.forceMove(T)
AM.pipe_eject(0)
+ SEND_SIGNAL(AM, COMSIG_MOVABLE_EXIT_DISPOSALS)
+
spawn(1)
if(AM)
AM.throw_at(target, 5, 1)
diff --git a/paradise.dme b/paradise.dme
index 5f4d30db8d3..22ab2ae576c 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -278,6 +278,7 @@
#include "code\controllers\subsystem\weather.dm"
#include "code\controllers\subsystem\processing\dcs.dm"
#include "code\controllers\subsystem\processing\fastprocess.dm"
+#include "code\controllers\subsystem\processing\fields.dm"
#include "code\controllers\subsystem\processing\instruments.dm"
#include "code\controllers\subsystem\processing\obj.dm"
#include "code\controllers\subsystem\processing\processing.dm"