mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2025-12-19 14:51:27 +00:00
289 lines
11 KiB
Plaintext
289 lines
11 KiB
Plaintext
/**
|
|
* # Basic Proximity Monitor
|
|
*
|
|
* 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` 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
|
|
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(_radius = 1, _always_active = FALSE)
|
|
. = ..()
|
|
if(!ismovable(parent) && !isturf(parent)) // No areas or datums allowed.
|
|
return COMPONENT_INCOMPATIBLE
|
|
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_CONTENTS(proximity_checkers)
|
|
return ..()
|
|
|
|
/datum/component/proximity_monitor/RegisterWithParent()
|
|
if(ismovable(hasprox_receiver))
|
|
RegisterSignal(hasprox_receiver, COMSIG_MOVABLE_MOVED, PROC_REF(on_receiver_move))
|
|
RegisterSignal(hasprox_receiver, COMSIG_MOVABLE_DISPOSING, PROC_REF(on_disposal_enter))
|
|
RegisterSignal(hasprox_receiver, COMSIG_MOVABLE_EXIT_DISPOSALS, PROC_REF(on_disposal_exit))
|
|
map_nested_locs()
|
|
|
|
/datum/component/proximity_monitor/UnregisterFromParent()
|
|
if(ismovable(hasprox_receiver))
|
|
UnregisterSignal(hasprox_receiver, list(COMSIG_MOVABLE_MOVED, COMSIG_MOVABLE_DISPOSING, COMSIG_MOVABLE_EXIT_DISPOSALS))
|
|
clear_nested_locs()
|
|
|
|
/**
|
|
* Called when the `hasprox_receiver` moves.
|
|
*
|
|
* Arguments:
|
|
* * 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/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 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_REF(on_nested_loc_move))
|
|
RegisterSignal(loc_to_check, COMSIG_MOVABLE_DISPOSING, PROC_REF(on_disposal_enter))
|
|
RegisterSignal(loc_to_check, COMSIG_MOVABLE_EXIT_DISPOSALS, PROC_REF(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()
|
|
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 `proximity_checker`s around the parent's current location.
|
|
*/
|
|
/datum/component/proximity_monitor/proc/recenter_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
|
|
|
|
/**
|
|
* # 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"
|
|
/// 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
|
|
|
|
/obj/effect/abstract/proximity_checker/Initialize(mapload, datum/component/proximity_monitor/P)
|
|
. = ..()
|
|
monitor = P
|
|
|
|
/obj/effect/abstract/proximity_checker/Destroy()
|
|
monitor.proximity_checkers -= src
|
|
monitor = null
|
|
return ..()
|
|
|
|
/**
|
|
* Called when something crossed over the proximity_checker. Notifies the `hasprox_receiver` it has proximity with something.
|
|
*
|
|
* 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 && AM != monitor.hasprox_receiver && !(AM in monitor.nested_receiver_locs))
|
|
monitor.hasprox_receiver.HasProximity(AM)
|