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"