Skip to content

Movement Signals

Background

Traditionally, BYOND games rely on several native procs to respond to certain kinds of in-game movement:

  • If one atom attempts to overlap another one, /atom/proc/Cross is called, allowing the overlapped atom to either return TRUE to allow the cross, or FALSE to prevent it. When Move() is called and an atom overlaps another one, /atom/proc/Crossed is called, allowing the overlapped atom to react to the cross.
  • Similarly, when an atom attempts to stop overlapping another, /atom/proc/Uncross is called to permit or deny it, then /atom/proc/Uncrossed is called to allow the atom to react to it.
  • When a movable attempts to enter a turf, /turf/proc/Enter is called, allowing the turf to return TRUE to permit the entry, or FALSE to deny it. When a movable succeeds in entering a turf, /turf/proc/Entered is called, allowing the turf to react to the entry.
  • Similarly, when an atom attempts to exit a turf, /turf/proc/Exit is called to permit or deny it, then /turf/proc/Exited is called to allow the turf to react to the exit.
  • When an atom attempts to enter the contents of another one, /atom/proc/Enter is called, allowing the containing atom to either return TRUE to allow the entry, or FALSE to prevent it. When an atom successfully enters the contents of another one, /atom/proc/Entered is called.
  • Similarly, when an atom attempts to exit another atom’s contents, /atom/proc/Exit is called to permit or deny it, then /atom/proc/Exited is called to allow the turf to react to the exit.

As one can imagine, with a lot of objects moving around in the game world, calling all of these procs can be expensive, especially if we don’t care about what happens most of the time.

In order to avoid making all of these calls, and provide more fine-grained control over what happens on atom/turf interactions, we implement our own version of the native /atom/movable/Move, and use signal handlers and components to process the interactions we care about.

Signal Handlers

There are several signal handlers used to replicate the native BYOND atom crossover/entry behavior:

  • COMSIG_MOVABLE_PRE_MOVE is sent when trying to determine if an atom can enter a new turf. Any subscribed handler can return COMPONENT_MOVABLE_BLOCK_PRE_MOVE to prevent the move.
  • COMSIG_MOVABLE_MOVED is sent after a successful move.
  • COMSIG_MOVABLE_CHECK_CROSS is sent when trying to determine if an atom can cross over another one. Any subscribed handler can return COMPONENT_BLOCK_CROSS to prevent the cross.
  • Similarly, COMSIG_MOVABLE_CHECK_CROSS_OVER is sent if an atom will allow another one to cross over it–the reverse of COMSIG_MOVABLE_CHECK_CROSS. Again, any subscribed handler can return COMPONENT_BLOCK_CROSS to prevent it.
  • COMSIG_ATOM_ENTERED is sent if an atom enters this atom’s contents. Similarly, COMSIG_ATOM_EXITED is sent if an atom exits this atom’s contents.

The connect_loc Element

The above signals are not appropriate for all cases. For example, it may seem like beartraps should listen to COMSIG_MOVABLE_MOVED to see if they should activate. However, this will fire every time a movement crossing occurs, even if it wouldn’t normally trigger the trap, such as if the trap is being thrown and intersects abstract lighting objects. Instead, we use COMSIG_ATOM_ENTERED with the connect_loc element. As a refresher, COMSIG_ATOM_ENTERED fires when an atom enters the contents of another. By using the connect_loc element, we can have the beartrap listen for signals on the turf it’s located on. Then, if the turf has a COMSIG_ATOM_ENTERED signal fire, we can have the beartrap respond.

Before:

/obj/item/restraints/legcuffs/beartrap/proc/Crossed(mob/living/crossed)
    if(istype(crossed))
        spring_trap()

After:

/obj/item/restraints/legcuffs/beartrap/Initialize(mapload)
    . = ..()
    var/static/list/loc_connections = list(
        COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
    )
    AddElement(/datum/element/connect_loc, loc_connections)

/obj/item/restraints/legcuffs/beartrap/proc/on_atom_entered(datum/source, mob/living/entered)
    if(istype(entered))
        spring_trap()

Note that the list of connections is static so that a new element instance isn’t created for every new beartrap.

Summary

The procs /atom/movable/Cross, /atom/movable/Crossed, /atom/movable/Uncross, and /atom/movable/Uncrossed, should not be used for new code.

If you care about every time a movable attempts to overlap you, listen to COMSIG_MOVABLE_PRE_MOVE, and return COMPONENT_MOVABLE_BLOCK_PRE_MOVE to prevent it from happening.

If you want to prevent a movable from crossing you, listen to COMSIG_MOVABLE_CHECK_CROSS and return COMPONENT_BLOCK_CROSS to prevent it. If you want to prevent a movable from being crossed by you, listen to COMSIG_MOVABLE_CHECK_CROSS_OVER and return COMPONENT_BLOCK_CROSS to prevent it.

If you care about an atom entering or exiting your contents, listen to COMSIG_ATOM_ENTERED or COMSIG_ATOM_EXITED.

If you care about an atom entering or exiting your location, or any other signal firing on your location, create a static list of signals mapped to procs and add a /datum/element/connect_loc to yourself.