/
proximity_monitor.dm
288 lines (254 loc) · 11 KB
/
proximity_monitor.dm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
/**
* # 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)