diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index 2a337c44563..e7c4089812c 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -1997,5 +1997,26 @@ 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 + /// Waits at a line of code until X is true #define UNTIL(X) while(!(X)) stoplag() diff --git a/code/datums/components/proximity_monitor.dm b/code/datums/components/proximity_monitor.dm new file mode 100644 index 00000000000..cf5de38b387 --- /dev/null +++ b/code/datums/components/proximity_monitor.dm @@ -0,0 +1,139 @@ +/** + * # Proximity monitor component + * + * Attaching this component to an atom means that the atom will be able to detect mobs/objs moving within a 1 tile 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. + */ +/datum/component/proximity_monitor + var/atom/owner + /// A list of currently created `/obj/effect/abstract/proximity_checker`s in use with this component. + var/list/proximity_checkers + +/datum/component/proximity_monitor/Initialize() + . = ..() + if(!isatom(parent)) + return COMPONENT_INCOMPATIBLE + owner = parent + create_prox_checkers() + +/datum/component/proximity_monitor/Destroy(force, silent) + QDEL_LIST(proximity_checkers) + owner = null + return ..() + +/datum/component/proximity_monitor/RegisterWithParent() + . = ..() + if(ismovable(parent)) + RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/HandleMove) + +/datum/component/proximity_monitor/UnregisterFromParent() + . = ..() + if(ismovable(parent)) + UnregisterSignal(parent, COMSIG_MOVABLE_MOVED) + +/** + * 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. + * + * 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/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() + +/** + * Called in Initialize(). Generates a set of `/obj/effect/abstract/proximity_checker` objects around the parent, and registers signals to them. + */ +/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) + +/** + * Re-centers all of the parent's `proximity_checker`s around its 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) + +/** + * # Proximity checker abstract object + * + * 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`). + */ +/obj/effect/abstract/proximity_checker + name = "Proximity checker" + /// 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/Destroy() + hasprox_receiver = null + return ..() + +/** + * Called when the `hasprox_receiver` receives the `COMSIG_PARENT_QDELETING` signal. When the receiver is deleted, so is this object. + * + * 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`. + */ +/obj/effect/abstract/proximity_checker/Crossed(atom/movable/AM, oldloc) + set waitfor = FALSE + if(active) + hasprox_receiver.HasProximity(AM) + +/** + * Moves the proximity_checker 1 tile in the `Dir` direction. + * + * 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 + */ +/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. diff --git a/code/game/area/ai_monitored.dm b/code/game/area/ai_monitored.dm index 723574ee949..abfcb7210b0 100644 --- a/code/game/area/ai_monitored.dm +++ b/code/game/area/ai_monitored.dm @@ -9,6 +9,7 @@ for(var/obj/machinery/camera/M in src) if(M.isMotion()) motioncameras.Add(M) + M.AddComponent(/datum/component/proximity_monitor) M.set_area_motion(src) /area/ai_monitored/Entered(atom/movable/O) diff --git a/code/game/machinery/camera/motion.dm b/code/game/machinery/camera/motion.dm index 7d33361e4b9..8a32c6d6374 100644 --- a/code/game/machinery/camera/motion.dm +++ b/code/game/machinery/camera/motion.dm @@ -58,7 +58,7 @@ detectTime = -1 return TRUE -/obj/machinery/camera/HasProximity(atom/movable/AM as mob|obj) +/obj/machinery/camera/HasProximity(atom/movable/AM) // Motion cameras outside of an "ai monitored" area will use this to detect stuff. if(!area_motion) if(isliving(AM)) diff --git a/code/game/machinery/flasher.dm b/code/game/machinery/flasher.dm index 37766db58fb..369d43e5d94 100644 --- a/code/game/machinery/flasher.dm +++ b/code/game/machinery/flasher.dm @@ -25,20 +25,17 @@ base_state = "pflash" density = 1 -/* -/obj/machinery/flasher/New() - sleep(4) //<--- What the fuck are you doing? D= - sd_set_light(2) -*/ +/obj/machinery/flasher/portable/ComponentInitialize() + . = ..() + AddComponent(/datum/component/proximity_monitor) + /obj/machinery/flasher/power_change() if( powered() ) stat &= ~NOPOWER icon_state = "[base_state]1" -// sd_set_light(2) else stat |= ~NOPOWER icon_state = "[base_state]1-p" -// sd_set_light(0) //Let the AI trigger them directly. /obj/machinery/flasher/attack_ai(mob/user) @@ -81,7 +78,7 @@ flash() ..(severity) -/obj/machinery/flasher/portable/HasProximity(atom/movable/AM as mob|obj) +/obj/machinery/flasher/portable/HasProximity(atom/movable/AM) if((disable) || (last_flash && world.time < last_flash + 150)) return @@ -109,7 +106,7 @@ if(anchored) WRENCH_ANCHOR_MESSAGE overlays.Cut() - else if(anchored) + else WRENCH_UNANCHOR_MESSAGE overlays += "[base_state]-s" diff --git a/code/game/objects/effects/effects.dm b/code/game/objects/effects/effects.dm index 7497b148788..625d2ad8fd9 100644 --- a/code/game/objects/effects/effects.dm +++ b/code/game/objects/effects/effects.dm @@ -45,6 +45,42 @@ if(prob(25)) qdel(src) +/** + * # The abstract object + * + * This is an object that is intended to able to be placed, but that is completely invisible. + * The object should be immune to all forms of damage, or things that can delete it, such as the singularity, or explosions. + */ +/obj/effect/abstract + name = "Abstract object" + invisibility = INVISIBILITY_ABSTRACT + layer = TURF_LAYER + density = FALSE + icon = null + icon_state = null + +// Most of these overrides procs below are overkill, but better safe than sorry. +/obj/effect/abstract/swarmer_act() + return + +/obj/effect/abstract/bullet_act(obj/item/projectile/P) + return + +/obj/effect/abstract/decompile_act(obj/item/matter_decompiler/C, mob/user) + return + +/obj/effect/abstract/tesla_act(power) + return + +/obj/effect/abstract/singularity_act() + return + +/obj/effect/abstract/narsie_act() + return + +/obj/effect/abstract/ex_act(severity) + 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 1eba4e54056..261b6ba109b 100644 --- a/code/game/objects/items/devices/transfer_valve.dm +++ b/code/game/objects/items/devices/transfer_valve.dm @@ -62,6 +62,8 @@ to_chat(user, "You attach the [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) @@ -137,6 +139,7 @@ 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/caution.dm b/code/game/objects/items/weapons/caution.dm index b1b573f77ad..b84f8eba6f4 100644 --- a/code/game/objects/items/weapons/caution.dm +++ b/code/game/objects/items/weapons/caution.dm @@ -15,6 +15,10 @@ var/armed = 0 var/timepassed = 0 +/obj/item/caution/proximity_sign/ComponentInitialize() + . = ..() + AddComponent(/datum/component/proximity_monitor) + /obj/item/caution/proximity_sign/attack_self(mob/user as mob) if(ishuman(user)) var/mob/living/carbon/human/H = user @@ -40,7 +44,7 @@ armed = 1 timing = 0 -/obj/item/caution/proximity_sign/HasProximity(atom/movable/AM as mob|obj) +/obj/item/caution/proximity_sign/HasProximity(atom/movable/AM) if(armed) if(istype(AM, /mob/living/carbon) && !istype(AM, /mob/living/carbon/brain)) var/mob/living/carbon/C = AM diff --git a/code/game/objects/items/weapons/grenades/chem_grenade.dm b/code/game/objects/items/weapons/grenades/chem_grenade.dm index 3aa658b0812..f756929e5ab 100644 --- a/code/game/objects/items/weapons/grenades/chem_grenade.dm +++ b/code/game/objects/items/weapons/grenades/chem_grenade.dm @@ -190,6 +190,8 @@ user.drop_item() nadeassembly = A + if(nadeassembly.has_prox_sensors()) + AddComponent(/datum/component/proximity_monitor) A.master = src A.loc = src assemblyattacher = user.ckey @@ -219,6 +221,7 @@ nadeassembly.loc = 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) @@ -304,6 +307,8 @@ /obj/item/grenade/chem_grenade/proc/CreateDefaultTrigger(var/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/game/objects/structures/aliens.dm b/code/game/objects/structures/aliens.dm index 033787cce75..4661ff125cc 100644 --- a/code/game/objects/structures/aliens.dm +++ b/code/game/objects/structures/aliens.dm @@ -280,12 +280,14 @@ /obj/structure/alien/egg/proc/Grow() icon_state = "egg" status = GROWN + AddComponent(/datum/component/proximity_monitor) /obj/structure/alien/egg/proc/Burst(kill = TRUE) //drops and kills the hugger if any is remaining if(status == GROWN || status == GROWING) icon_state = "egg_hatched" flick("egg_opening", src) status = BURSTING + qdel(GetComponent(/datum/component/proximity_monitor)) spawn(15) status = BURST var/obj/item/clothing/mask/facehugger/child = GetFacehugger() diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 2f90e4d7cc4..8170f9fbe5f 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -166,13 +166,6 @@ if(!O.lastarea) O.lastarea = get_area(O.loc) - var/loopsanity = 100 - for(var/atom/A in range(1)) - if(loopsanity == 0) - break - loopsanity-- - A.HasProximity(M) - // If an opaque movable atom moves around we need to potentially update visibility. if(M.opacity) has_opaque_atom = TRUE // Make sure to do this before reconsider_lights(), incase we're on instant updates. Guaranteed to be on in this case. diff --git a/code/modules/assembly/bomb.dm b/code/modules/assembly/bomb.dm index 33755fc54e8..855c113a71a 100644 --- a/code/modules/assembly/bomb.dm +++ b/code/modules/assembly/bomb.dm @@ -12,6 +12,10 @@ 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) diff --git a/code/modules/assembly/holder.dm b/code/modules/assembly/holder.dm index 6a6cd7e8d8e..87fefe4fc65 100644 --- a/code/modules/assembly/holder.dm +++ b/code/modules/assembly/holder.dm @@ -41,19 +41,25 @@ if(!A1.remove_item_from_storage(src)) if(user) user.remove_from_mob(A1) - A1.loc = src + A1.forceMove(src) if(!A2.remove_item_from_storage(src)) if(user) user.remove_from_mob(A2) - A2.loc = src + A2.forceMove(src) A1.holder = src 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 +/obj/item/assembly_holder/proc/has_prox_sensors() + if(istype(a_left, /obj/item/assembly/prox_sensor) || istype(a_right, /obj/item/assembly/prox_sensor)) + return TRUE + return FALSE /obj/item/assembly_holder/update_icon() overlays.Cut() diff --git a/code/modules/assembly/proximity.dm b/code/modules/assembly/proximity.dm index caefabcb1f8..ced9fbca932 100644 --- a/code/modules/assembly/proximity.dm +++ b/code/modules/assembly/proximity.dm @@ -13,6 +13,10 @@ var/timing = 0 var/time = 10 +/obj/item/assembly/prox_sensor/ComponentInitialize() + . = ..() + AddComponent(/datum/component/proximity_monitor) + /obj/item/assembly/prox_sensor/describe() if(timing) return "The proximity sensor is arming." diff --git a/code/modules/clothing/masks/miscellaneous.dm b/code/modules/clothing/masks/miscellaneous.dm index 5cc891b56af..15895046dbc 100644 --- a/code/modules/clothing/masks/miscellaneous.dm +++ b/code/modules/clothing/masks/miscellaneous.dm @@ -146,6 +146,7 @@ trigger.forceMove(src) trigger.master = src trigger.holder = src + AddComponent(/datum/component/proximity_monitor) to_chat(user, "You attach the [W] to [src].") return TRUE else if(istype(W, /obj/item/assembly)) @@ -165,6 +166,7 @@ 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)) @@ -186,7 +188,7 @@ M.Jitter(20) return -/obj/item/clothing/mask/muzzle/safety/shock/HasProximity(atom/movable/AM as mob|obj) +/obj/item/clothing/mask/muzzle/safety/shock/HasProximity(atom/movable/AM) if(trigger) trigger.HasProximity(AM) diff --git a/code/modules/mob/living/carbon/alien/special/facehugger.dm b/code/modules/mob/living/carbon/alien/special/facehugger.dm index 1dcd76c0358..b3f6d73a163 100644 --- a/code/modules/mob/living/carbon/alien/special/facehugger.dm +++ b/code/modules/mob/living/carbon/alien/special/facehugger.dm @@ -28,6 +28,10 @@ var/attached = 0 +/obj/item/clothing/mask/facehugger/ComponentInitialize() + . = ..() + AddComponent(/datum/component/proximity_monitor) + /obj/item/clothing/mask/facehugger/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir) ..() if(obj_integrity < 90) @@ -78,7 +82,7 @@ return HasProximity(finder) return 0 -/obj/item/clothing/mask/facehugger/HasProximity(atom/movable/AM as mob|obj) +/obj/item/clothing/mask/facehugger/HasProximity(atom/movable/AM) if(CanHug(AM) && Adjacent(AM)) return Attach(AM) return 0 @@ -210,6 +214,7 @@ icon_state = "[initial(icon_state)]_dead" item_state = "facehugger_inactive" stat = DEAD + qdel(GetComponent(/datum/component/proximity_monitor)) visible_message("[src] curls up into a ball!") diff --git a/code/modules/reagents/reagent_containers/glass_containers.dm b/code/modules/reagents/reagent_containers/glass_containers.dm index 175ae757df4..48534c7ab73 100644 --- a/code/modules/reagents/reagent_containers/glass_containers.dm +++ b/code/modules/reagents/reagent_containers/glass_containers.dm @@ -179,6 +179,7 @@ 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.") @@ -194,7 +195,9 @@ return ..() assembly = W user.drop_item() - W.loc = src + 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 ff01fc34506..79ba8deccbd 100644 --- a/code/modules/reagents/reagent_dispenser.dm +++ b/code/modules/reagents/reagent_dispenser.dm @@ -126,6 +126,7 @@ 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() @@ -148,7 +149,8 @@ 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/paradise.dme b/paradise.dme index b7e13ffe768..efddfaea144 100644 --- a/paradise.dme +++ b/paradise.dme @@ -307,6 +307,7 @@ #include "code\datums\components\label.dm" #include "code\datums\components\material_container.dm" #include "code\datums\components\paintable.dm" +#include "code\datums\components\proximity_monitor.dm" #include "code\datums\components\slippery.dm" #include "code\datums\components\spawner.dm" #include "code\datums\components\spooky.dm"