diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 2b887226fa8..2980f32b45d 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -334,7 +334,9 @@ // /turf signals -///from base of turf/ChangeTurf(): (path, list/new_baseturfs, flags, list/transferring_comps) +/// from base of turf/ChangeTurf(): (path, list/new_baseturfs, flags, list/post_change_callbacks). +/// `post_change_callbacks` is a list that signal handlers can mutate to append `/datum/callback` objects. +/// They will be called with the new turf after the turf has changed. #define COMSIG_TURF_CHANGE "turf_change" ///from base of atom/has_gravity(): (atom/asker, list/forced_gravities) #define COMSIG_TURF_HAS_GRAVITY "turf_has_gravity" @@ -358,18 +360,12 @@ #define COMSIG_MOVABLE_CROSS "movable_cross" ///from base of atom/movable/Crossed(): (/atom/movable) #define COMSIG_MOVABLE_CROSSED "movable_crossed" -///from base of atom/movable/Uncross(): (/atom/movable) -#define COMSIG_MOVABLE_UNCROSS "movable_uncross" - #define COMPONENT_MOVABLE_BLOCK_UNCROSS (1<<0) ///from base of atom/movable/Uncrossed(): (/atom/movable) #define COMSIG_MOVABLE_UNCROSSED "movable_uncrossed" ///from base of atom/movable/Cross(): (/atom/movable) #define COMSIG_MOVABLE_CROSS_OVER "movable_cross_am" ///from base of atom/movable/Crossed(): (/atom/movable) #define COMSIG_MOVABLE_CROSSED_OVER "movable_crossed_am" -///from base of atom/movable/Uncross(): (/atom/movable) -#define COMSIG_MOVABLE_UNCROSS_OVER "movable_uncross_am" - #define COMPONENT_MOVABLE_BLOCK_UNCROSS_OVER (1<<0) ///from base of atom/movable/Uncrossed(): (/atom/movable) #define COMSIG_MOVABLE_UNCROSSED_OVER "movable_uncross_am" ///from base of atom/movable/Bump(): (/atom) diff --git a/code/__DEFINES/flags.dm b/code/__DEFINES/flags.dm index a9f5860838a..58f0cea14f3 100644 --- a/code/__DEFINES/flags.dm +++ b/code/__DEFINES/flags.dm @@ -142,7 +142,7 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204 #define FLYING (1<<1) #define VENTCRAWLING (1<<2) #define FLOATING (1<<3) -/// When moving, will Cross()/Uncross() everything, but won't stop or Bump() anything. +/// When moving, will Cross() everything, but won't stop or Bump() anything. #define PHASING (1<<4) //Fire and Acid stuff, for resistance_flags diff --git a/code/datums/elements/connect_loc.dm b/code/datums/elements/connect_loc.dm new file mode 100644 index 00000000000..15082db5326 --- /dev/null +++ b/code/datums/elements/connect_loc.dm @@ -0,0 +1,77 @@ +/// This element hooks a signal onto the loc the current object is on. +/// When the object moves, it will unhook the signal and rehook it to the new object. +/datum/element/connect_loc + element_flags = ELEMENT_BESPOKE | ELEMENT_DETACH + id_arg_index = 2 + + /// An assoc list of signal -> procpath to register to the loc this object is on. + var/list/connections + + /// An assoc list of locs that are being occupied and a list of targets that occupy them. + var/list/targets = list() + +/datum/element/connect_loc/Attach(datum/target, list/connections) + . = ..() + if (!ismovable(target)) + return ELEMENT_INCOMPATIBLE + + src.connections = connections + RegisterSignal(target, COMSIG_MOVABLE_MOVED, .proc/on_moved) + update_signals(target) + +/datum/element/connect_loc/Detach(datum/source, force) + . = ..() + + if (!ismovable(source)) + return + + var/atom/movable/movable_source = source + + if (!isnull(movable_source.loc)) + unregister_signals(source, movable_source.loc) + +/datum/element/connect_loc/proc/update_signals(atom/movable/target) + if (isnull(target.loc)) + return + + LAZYSET(targets[target.loc], target, TRUE) + + for (var/signal in connections) + target.RegisterSignal(target.loc, signal, connections[signal]) + + if (isturf(target.loc) && length(targets[target.loc]) == 1) + RegisterSignal(target.loc, COMSIG_TURF_CHANGE, .proc/on_turf_change) + +/datum/element/connect_loc/proc/unregister_signals(atom/movable/target, atom/old_loc) + LAZYREMOVE(targets[old_loc], target) + + for (var/signal in connections) + target.UnregisterSignal(old_loc, signal) + + if (isturf(old_loc) && !(target.loc in targets)) + UnregisterSignal(old_loc, COMSIG_TURF_CHANGE) + +/datum/element/connect_loc/proc/on_moved(atom/movable/source, atom/old_loc) + SIGNAL_HANDLER + + if (!isnull(old_loc)) + unregister_signals(source, old_loc) + + update_signals(source) + +/datum/element/connect_loc/proc/on_turf_change( + turf/source, + path, + new_baseturfs, + flags, + list/post_change_callbacks, +) + SIGNAL_HANDLER + + post_change_callbacks += CALLBACK(src, .proc/post_turf_change) + +/datum/element/connect_loc/proc/post_turf_change(turf/new_turf) + SHOULD_NOT_SLEEP(TRUE) + + for (var/atom/movable/target as anything in targets[new_turf]) + update_signals(target) diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 5bacfced21b..b7d058feb92 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -515,9 +515,6 @@ /atom/proc/AllowDrop() return FALSE -/atom/proc/CheckExit() - return TRUE - ///Is this atom within 1 tile of another atom /atom/proc/HasProximity(atom/movable/AM as mob|obj) return @@ -1282,15 +1279,16 @@ * An atom is attempting to exit this atom's contents * * Default behaviour is to send the [COMSIG_ATOM_EXIT] - * - * Return value should be set to FALSE if the moving atom is unable to leave, - * otherwise leave value the result of the parent call */ /atom/Exit(atom/movable/AM, atom/newLoc) - . = ..() + // Don't call `..()` here, otherwise `Uncross()` gets called. + // See the doc comment on `Uncross()` to learn why this is bad. + if(SEND_SIGNAL(src, COMSIG_ATOM_EXIT, AM, newLoc) & COMPONENT_ATOM_BLOCK_EXIT) return FALSE + return TRUE + /** * An atom has exited this atom's contents * diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index ffeffa5f058..94873398849 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -531,14 +531,29 @@ SEND_SIGNAL(src, COMSIG_MOVABLE_CROSSED, AM) SEND_SIGNAL(AM, COMSIG_MOVABLE_CROSSED_OVER, src) -/atom/movable/Uncross(atom/movable/AM, atom/newloc) - . = ..() - if(SEND_SIGNAL(src, COMSIG_MOVABLE_UNCROSS, AM) & COMPONENT_MOVABLE_BLOCK_UNCROSS) - return FALSE - if(SEND_SIGNAL(AM, COMSIG_MOVABLE_UNCROSS_OVER, src) & COMPONENT_MOVABLE_BLOCK_UNCROSS_OVER) - return FALSE - if(isturf(newloc) && !CheckExit(AM, newloc)) - return FALSE +/** + * `Uncross()` is a default BYOND proc that is called when something is *going* + * to exit this atom's turf. It is prefered over `Uncrossed` when you want to + * deny that movement, such as in the case of border objects, objects that allow + * you to walk through them in any direction except the one they block + * (think side windows). + * + * While being seemingly harmless, most everything doesn't actually want to + * use this, meaning that we are wasting proc calls for every single atom + * on a turf, every single time something exits it, when basically nothing + * cares. + * + * This overhead caused real problems on Sybil round #159709, where lag + * attributed to Uncross was so bad that the entire master controller + * collapsed and people made Among Us lobbies in OOC. + * + * If you want to replicate the old `Uncross()` behavior, the most apt + * replacement is [`/datum/element/connect_loc`] while hooking onto + * [`COMSIG_ATOM_EXIT`]. + */ +/atom/movable/Uncross() + SHOULD_NOT_OVERRIDE(TRUE) + CRASH("Uncross() should not be being called, please read the doc-comment for it for why.") /atom/movable/Uncrossed(atom/movable/AM) SEND_SIGNAL(src, COMSIG_MOVABLE_UNCROSSED, AM) diff --git a/code/game/machinery/doors/firedoor.dm b/code/game/machinery/doors/firedoor.dm index 67b7c4da187..296da61c3d5 100644 --- a/code/game/machinery/doors/firedoor.dm +++ b/code/game/machinery/doors/firedoor.dm @@ -266,15 +266,26 @@ opacity = TRUE density = TRUE +/obj/machinery/door/firedoor/border_only/Initialize() + . = ..() + + var/static/list/loc_connections = list( + COMSIG_ATOM_EXIT = .proc/on_exit, + ) + + AddElement(/datum/element/connect_loc, loc_connections) + /obj/machinery/door/firedoor/border_only/CanAllowThrough(atom/movable/mover, turf/target) . = ..() if(!(get_dir(loc, target) == dir)) //Make sure looking at appropriate border return TRUE -/obj/machinery/door/firedoor/border_only/CheckExit(atom/movable/mover as mob|obj, turf/target) - if(get_dir(loc, target) == dir) - return !density - return TRUE +/obj/machinery/door/firedoor/border_only/proc/on_exit(datum/source, atom/movable/leaving, atom/new_location) + SIGNAL_HANDLER + + if(get_dir(leaving.loc, new_location) == dir && density) + leaving.Bump(src) + return COMPONENT_ATOM_BLOCK_EXIT /obj/machinery/door/firedoor/border_only/CanAtmosPass(turf/T) if(get_dir(loc, T) == dir) diff --git a/code/game/machinery/doors/windowdoor.dm b/code/game/machinery/doors/windowdoor.dm index 3bf44179e70..56a07072780 100644 --- a/code/game/machinery/doors/windowdoor.dm +++ b/code/game/machinery/doors/windowdoor.dm @@ -42,6 +42,12 @@ RegisterSignal(src, COMSIG_COMPONENT_NTNET_RECEIVE, .proc/ntnet_receive) + var/static/list/loc_connections = list( + COMSIG_ATOM_EXIT = .proc/on_exit, + ) + + AddElement(/datum/element/connect_loc, loc_connections) + /obj/machinery/door/window/ComponentInitialize() . = ..() AddElement(/datum/element/atmos_sensitive) @@ -132,12 +138,15 @@ /obj/machinery/door/window/CanAStarPass(obj/item/card/id/ID, to_dir) return !density || (dir != to_dir) || (check_access(ID) && hasPower()) -/obj/machinery/door/window/CheckExit(atom/movable/mover, turf/target) - if((pass_flags_self & mover.pass_flags) || ((pass_flags_self & LETPASSTHROW) && mover.throwing)) - return TRUE - if(get_dir(loc, target) == dir) - return !density - return TRUE +/obj/machinery/door/window/proc/on_exit(datum/source, atom/movable/leaving, atom/new_location) + SIGNAL_HANDLER + + if((pass_flags_self & leaving.pass_flags) || ((pass_flags_self & LETPASSTHROW) && leaving.throwing)) + return + + if(get_dir(loc, new_location) == dir && density) + leaving.Bump(src) + return COMPONENT_ATOM_BLOCK_EXIT /obj/machinery/door/window/open(forced=FALSE) if (operating) //doors can still open when emag-disabled diff --git a/code/game/objects/structures/railings.dm b/code/game/objects/structures/railings.dm index ee4384e5fac..fdbeb65fb73 100644 --- a/code/game/objects/structures/railings.dm +++ b/code/game/objects/structures/railings.dm @@ -15,17 +15,15 @@ density = FALSE climbable = FALSE -/obj/structure/railing/ComponentInitialize() - . = ..() - AddComponent(/datum/component/simple_rotation,ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS ,null,CALLBACK(src, .proc/can_be_rotated),CALLBACK(src,.proc/after_rotation)) - - /obj/structure/railing/Initialize() . = ..() ini_dir = dir if(climbable) AddElement(/datum/element/climbable) + AddComponent(/datum/component/simple_rotation,ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS ,null,CALLBACK(src, .proc/can_be_rotated),CALLBACK(src,.proc/after_rotation)) + init_connect_loc_element() + /obj/structure/railing/attackby(obj/item/I, mob/living/user, params) ..() add_fingerprint(user) @@ -81,15 +79,37 @@ ..() return TRUE -/obj/structure/railing/CheckExit(atom/movable/mover, turf/target) - ..() - if(get_dir(loc, target) & dir) - var/checking = PHASING | FLYING | FLOATING - return !density || mover.throwing || mover.movement_type & checking || mover.move_force >= MOVE_FORCE_EXTREMELY_STRONG - return TRUE +/obj/structure/railing/proc/init_connect_loc_element() + var/static/list/loc_connections = list( + COMSIG_ATOM_EXIT = .proc/on_exit, + ) -/obj/structure/railing/corner/CheckExit() - return TRUE + AddElement(/datum/element/connect_loc, loc_connections) + +/obj/structure/railing/proc/on_exit(datum/source, atom/movable/leaving, atom/new_location) + SIGNAL_HANDLER + + if(!(get_dir(leaving.loc, new_location) & dir)) + return + + if (!density) + return + + if (leaving.throwing) + return + + if (leaving.movement_type & (PHASING | FLYING | FLOATING)) + return + + if (leaving.move_force >= MOVE_FORCE_EXTREMELY_STRONG) + return + + leaving.Bump(src) + return COMPONENT_ATOM_BLOCK_EXIT + +// Corner railings don't block anything, so they don't create the element. +/obj/structure/railing/corner/init_connect_loc_element() + return /obj/structure/railing/proc/can_be_rotated(mob/user,rotation_type) if(anchored) diff --git a/code/game/objects/structures/stairs.dm b/code/game/objects/structures/stairs.dm index 63998e84774..27c8bbef6ad 100644 --- a/code/game/objects/structures/stairs.dm +++ b/code/game/objects/structures/stairs.dm @@ -33,6 +33,13 @@ force_open_above() build_signal_listener() update_surrounding() + + var/static/list/loc_connections = list( + COMSIG_ATOM_EXIT = .proc/on_exit, + ) + + AddElement(/datum/element/connect_loc, loc_connections) + return ..() /obj/structure/stairs/Destroy() @@ -53,13 +60,13 @@ if(S) S.update_appearance() -/obj/structure/stairs/Uncross(atom/movable/AM, atom/newloc) - if(!newloc || !AM) - return ..() - if(!isobserver(AM) && isTerminator() && (get_dir(src, newloc) == dir)) - stair_ascend(AM) - return FALSE - return ..() +/obj/structure/stairs/proc/on_exit(datum/source, atom/movable/leaving, atom/new_location) + SIGNAL_HANDLER + + if(!isobserver(leaving) && isTerminator() && (get_dir(src, new_location) == dir)) + INVOKE_ASYNC(src, .proc/stair_ascend, leaving) + leaving.Bump(src) + return COMPONENT_ATOM_BLOCK_EXIT /obj/structure/stairs/Cross(atom/movable/AM) if(isTerminator() && (get_dir(src, AM) == dir)) diff --git a/code/game/objects/structures/windoor_assembly.dm b/code/game/objects/structures/windoor_assembly.dm index 05c20c8ace8..3501bd09c67 100644 --- a/code/game/objects/structures/windoor_assembly.dm +++ b/code/game/objects/structures/windoor_assembly.dm @@ -29,12 +29,18 @@ var/state = "01" //How far the door assembly has progressed CanAtmosPass = ATMOS_PASS_PROC -/obj/structure/windoor_assembly/New(loc, set_dir) - ..() +/obj/structure/windoor_assembly/Initialize(loc, set_dir) + . = ..() if(set_dir) setDir(set_dir) air_update_turf(TRUE, TRUE) + var/static/list/loc_connections = list( + COMSIG_ATOM_EXIT = .proc/on_exit, + ) + + AddElement(/datum/element/connect_loc, loc_connections) + /obj/structure/windoor_assembly/Destroy() density = FALSE air_update_turf(TRUE, FALSE) @@ -68,13 +74,15 @@ else return 1 -/obj/structure/windoor_assembly/CheckExit(atom/movable/mover, turf/target) - if(mover.pass_flags & pass_flags_self) - return TRUE - if(get_dir(loc, target) == dir) - return !density - else - return TRUE +/obj/structure/windoor_assembly/proc/on_exit(datum/source, atom/movable/leaving, atom/new_location) + SIGNAL_HANDLER + + if (leaving.pass_flags & pass_flags_self) + return + + if (get_dir(loc, new_location) == dir && density) + leaving.Bump(src) + return COMPONENT_ATOM_BLOCK_EXIT /obj/structure/windoor_assembly/attackby(obj/item/W, mob/user, params) //I really should have spread this out across more states but thin little windoors are hard to sprite. diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm index 1747c96a7b0..36a81ca0034 100644 --- a/code/game/objects/structures/window.dm +++ b/code/game/objects/structures/window.dm @@ -68,6 +68,13 @@ flags_1 |= ALLOW_DARK_PAINTS_1 RegisterSignal(src, COMSIG_OBJ_PAINTED, .proc/on_painted) + var/static/list/loc_connections = list( + COMSIG_ATOM_EXIT = .proc/on_exit, + ) + + if (flags_1 & ON_BORDER_1) + AddElement(/datum/element/connect_loc, loc_connections) + /obj/structure/window/ComponentInitialize() . = ..() AddComponent(/datum/component/simple_rotation,ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS ,null,CALLBACK(src, .proc/can_be_rotated),CALLBACK(src,.proc/after_rotation)) @@ -117,13 +124,18 @@ return TRUE -/obj/structure/window/CheckExit(atom/movable/O, turf/target) - if(istype(O) && (O.pass_flags & pass_flags_self)) - return TRUE - if(!fulltile && get_dir(O.loc, target) == dir) - return !density - return TRUE +/obj/structure/window/proc/on_exit(datum/source, atom/movable/leaving, atom/new_location) + SIGNAL_HANDLER + if (istype(leaving) && (leaving.pass_flags & pass_flags_self)) + return + + if (fulltile) + return + + if(get_dir(leaving.loc, new_location) == dir && density) + leaving.Bump(src) + return COMPONENT_ATOM_BLOCK_EXIT /obj/structure/window/attack_tk(mob/user) user.changeNext_move(CLICK_CD_MELEE) diff --git a/code/game/turfs/change_turf.dm b/code/game/turfs/change_turf.dm index 7ee053d750e..b5bc7f1be6f 100644 --- a/code/game/turfs/change_turf.dm +++ b/code/game/turfs/change_turf.dm @@ -89,18 +89,15 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list( var/list/old_baseturfs = baseturfs var/old_type = type - var/list/transferring_comps = list() - SEND_SIGNAL(src, COMSIG_TURF_CHANGE, path, new_baseturfs, flags, transferring_comps) - for(var/i in transferring_comps) - var/datum/component/comp = i - comp.RemoveComponent() + var/list/post_change_callbacks = list() + SEND_SIGNAL(src, COMSIG_TURF_CHANGE, path, new_baseturfs, flags, post_change_callbacks) changing_turf = TRUE qdel(src) //Just get the side effects and call Destroy var/turf/W = new path(src) - for(var/i in transferring_comps) - W.TakeComponent(i) + for(var/datum/callback/callback as anything in post_change_callbacks) + callback.InvokeAsync(W) if(new_baseturfs) W.baseturfs = baseturfs_string_list(new_baseturfs, W) diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index ec01a47bdfb..b8002b9d8f0 100755 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -325,12 +325,6 @@ GLOBAL_LIST_EMPTY(station_turfs) for(var/i in contents) if(i == mover) continue - var/atom/movable/thing = i - if(!thing.Uncross(mover, newloc)) - if(thing.flags_1 & ON_BORDER_1) - mover.Bump(thing) - if(!(mover.movement_type & PHASING)) - return FALSE if(QDELETED(mover)) return FALSE //We were deleted. diff --git a/code/modules/fields/fields.dm b/code/modules/fields/fields.dm index 7e25d500fd1..beabc526c9d 100644 --- a/code/modules/fields/fields.dm +++ b/code/modules/fields/fields.dm @@ -129,9 +129,6 @@ /datum/proximity_monitor/advanced/proc/field_turf_canpass(atom/movable/AM, obj/effect/abstract/proximity_checker/advanced/field_turf/F, turf/entering) return TRUE -/datum/proximity_monitor/advanced/proc/field_turf_uncross(atom/movable/AM, obj/effect/abstract/proximity_checker/advanced/field_turf/F) - return TRUE - /datum/proximity_monitor/advanced/proc/field_turf_crossed(atom/movable/AM, obj/effect/abstract/proximity_checker/advanced/field_turf/F) return TRUE @@ -141,9 +138,6 @@ /datum/proximity_monitor/advanced/proc/field_edge_canpass(atom/movable/AM, obj/effect/abstract/proximity_checker/advanced/field_edge/F, turf/entering) return TRUE -/datum/proximity_monitor/advanced/proc/field_edge_uncross(atom/movable/AM, obj/effect/abstract/proximity_checker/advanced/field_edge/F) - return TRUE - /datum/proximity_monitor/advanced/proc/field_edge_crossed(atom/movable/AM, obj/effect/abstract/proximity_checker/advanced/field_edge/F) return TRUE diff --git a/code/modules/fields/turf_objects.dm b/code/modules/fields/turf_objects.dm index d82febe6f79..1617803884c 100644 --- a/code/modules/fields/turf_objects.dm +++ b/code/modules/fields/turf_objects.dm @@ -34,11 +34,6 @@ return parent.field_turf_crossed(AM, src) return TRUE -/obj/effect/abstract/proximity_checker/advanced/field_turf/Uncross(atom/movable/AM) - if(parent) - return parent.field_turf_uncross(AM, src) - return TRUE - /obj/effect/abstract/proximity_checker/advanced/field_turf/Uncrossed(atom/movable/AM) if(parent) return parent.field_turf_uncrossed(AM, src) @@ -59,11 +54,6 @@ return parent.field_edge_crossed(AM, src) return TRUE -/obj/effect/abstract/proximity_checker/advanced/field_edge/Uncross(atom/movable/AM) - if(parent) - return parent.field_edge_uncross(AM, src) - return TRUE - /obj/effect/abstract/proximity_checker/advanced/field_edge/Uncrossed(atom/movable/AM) if(parent) return parent.field_edge_uncrossed(AM, src) diff --git a/code/modules/ruins/objects_and_mobs/necropolis_gate.dm b/code/modules/ruins/objects_and_mobs/necropolis_gate.dm index 4f50f428e6a..308a88194b9 100644 --- a/code/modules/ruins/objects_and_mobs/necropolis_gate.dm +++ b/code/modules/ruins/objects_and_mobs/necropolis_gate.dm @@ -46,6 +46,12 @@ dais_overlay.layer = CLOSED_TURF_LAYER add_overlay(dais_overlay) + var/static/list/loc_connections = list( + COMSIG_ATOM_EXIT = .proc/on_exit, + ) + + AddElement(/datum/element/connect_loc, loc_connections) + /obj/structure/necropolis_gate/Destroy(force) if(force) qdel(sight_blocker, TRUE) @@ -61,10 +67,12 @@ if(!(get_dir(loc, target) == dir)) return TRUE -/obj/structure/necropolis_gate/CheckExit(atom/movable/O, target) - if(get_dir(O.loc, target) == dir) - return !density - return 1 +/obj/structure/necropolis_gate/proc/on_exit(datum/source, atom/movable/leaving, atom/new_location) + SIGNAL_HANDLER + + if (get_dir(leaving.loc, new_location) == dir && density) + leaving.Bump(src) + return COMPONENT_ATOM_BLOCK_EXIT /obj/structure/opacity_blocker icon = 'icons/effects/96x96.dmi' diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index 47b0a6bf64e..584456d9111 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -48,6 +48,7 @@ #include "chain_pull_through_space.dm" #include "combat.dm" #include "component_tests.dm" +#include "connect_loc.dm" #include "confusion.dm" #include "crayons.dm" #include "designs.dm" diff --git a/code/modules/unit_tests/connect_loc.dm b/code/modules/unit_tests/connect_loc.dm new file mode 100644 index 00000000000..3e33270d306 --- /dev/null +++ b/code/modules/unit_tests/connect_loc.dm @@ -0,0 +1,62 @@ +#define COMSIG_MOCK_SIGNAL "mock_signal" + +/// Test that the connect_loc element handles basic movement cases +/datum/unit_test/connect_loc_basic + +/datum/unit_test/connect_loc_basic/Run() + var/obj/item/watches_mock_calls/watcher = allocate(/obj/item/watches_mock_calls) + + var/turf/current_turf = get_turf(watcher) + + SEND_SIGNAL(current_turf, COMSIG_MOCK_SIGNAL) + TEST_ASSERT_EQUAL(watcher.times_called, 1, "After firing mock signal, connect_loc didn't send it") + + watcher.forceMove(run_loc_floor_top_right) + + SEND_SIGNAL(current_turf, COMSIG_MOCK_SIGNAL) + TEST_ASSERT_EQUAL(watcher.times_called, 1, "Mock signal was fired on old turf, but connect_loc still picked it up") + + current_turf = get_turf(watcher) + SEND_SIGNAL(current_turf, COMSIG_MOCK_SIGNAL) + TEST_ASSERT_EQUAL(watcher.times_called, 2, "Mock signal was fired after turf move, but it wasn't picked up") + +/// Test that the connect_loc element handles turf changes +/datum/unit_test/connect_loc_change_turf + var/old_turf_type + +/datum/unit_test/connect_loc_change_turf/Run() + var/obj/item/watches_mock_calls/watcher = allocate(/obj/item/watches_mock_calls, run_loc_floor_bottom_left) + + var/turf/current_turf = get_turf(watcher) + old_turf_type = current_turf.type + + SEND_SIGNAL(current_turf, COMSIG_MOCK_SIGNAL) + TEST_ASSERT_EQUAL(watcher.times_called, 1, "After firing mock signal, connect_loc didn't send it") + + current_turf.ChangeTurf(/turf/closed/wall) + + current_turf = get_turf(watcher) + SEND_SIGNAL(current_turf, COMSIG_MOCK_SIGNAL) + TEST_ASSERT_EQUAL(watcher.times_called, 2, "After changing turf, connect_loc didn't reconnect it") + +/datum/unit_test/connect_loc_change_turf/Destroy() + run_loc_floor_bottom_left.ChangeTurf(old_turf_type) + return ..() + +/obj/item/watches_mock_calls + var/times_called + +/obj/item/watches_mock_calls/Initialize() + . = ..() + + var/static/list/connections = list( + COMSIG_MOCK_SIGNAL = .proc/on_receive_mock_signal, + ) + + AddElement(/datum/element/connect_loc, connections) + +/obj/item/watches_mock_calls/proc/on_receive_mock_signal(datum/source) + SIGNAL_HANDLER + times_called += 1 + +#undef COMSIG_MOCK_SIGNAL diff --git a/tgstation.dme b/tgstation.dme index 777a77e6ab5..b97d7f71a7b 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -649,6 +649,7 @@ #include "code\datums\elements\caltrop.dm" #include "code\datums\elements\cleaning.dm" #include "code\datums\elements\climbable.dm" +#include "code\datums\elements\connect_loc.dm" #include "code\datums\elements\decal.dm" #include "code\datums\elements\deferred_aquarium_content.dm" #include "code\datums\elements\digitalcamo.dm"