ID:1545242
 
You need to check if something can move from one turf to another, right? It's annoying, and you have to write all this code to deal with the fake movement, or write your own copy of what Move() does internally, right?

Well, there's an easier way to set this all up, still use built-in behavior, as well as access information about the individual movers, while still differentiating between a test move and a real move. All the while avoiding the excess overhead of creating fake objects every time you want to test for movement.

//first, some defines. Feel free to add more where needed.
//brush up on your binary logic, folks.
#define COLLIDE_AREAS 1
#define COLLIDE_TURFS 2
#define COLLIDE_OBJS 4
#define COLLIDE_MOBS 8
//if you add more, make sure you update the all collision mask.
#define COLLIDE_ALL 15

//just a quick shortcut.
#define iscollider(x) istype((x),/collider)

//Collider objects just hold data for the Entry/Exit/Cross/Uncross testing.
//We shouldn't need to keep many of these around.
collider
parent_type = /atom/movable
density = 1
var/tmp
atom/movable/proxy
collision_mask = COLLIDE_ALL
list/collisions = list()


Now that we have our object, and our definitions, we can start thinking about how they will work. We're going to change the way that Enter(), Exit(), Cross(), and Uncross() work just a little bit. Essentially, we just want to make it so that these functions perform their default behavior, but we then ignore the collision provided the supplied collision mask doesn't match.

//override the default behavior just a bit.
atom
var/tmp
collision_layer = 0

Enter(atom/movable/o)
. = ..(o)
if(iscollider(o))
var/collider/c = o
if(!. && c.collision_mask & collision_layer)
c.collisions += src
else if(!c.collisions.len)
. = 1

Exit(atom/movable/o)
. = ..(o)
if(iscollider(o))
var/collider/c = o
if(!. && c.collision_mask & collision_layer)
c.collisions += src
else if(!c.collisions.len)
. = 1

area
collision_layer = COLLIDE_AREAS

turf
collision_layer = COLLIDE_TURFS

atom/movable
Enter(atom/movable/o)
return 1

Exit(atom/movable/o)
return 1

Cross(atom/movable/o)
if(iscollider(o))
var/collider/c = o
if(c.collision_mask & collision_layer)
. = ..(o)
if(!.)
c.collisions += src
else
. = 1
else
. = ..(o)

Uncross(atom/movable/o)
if(iscollider(o))
var/collider/c = o
if(c.collision_mask & collision_layer)
. = ..(o)
if(!.)
c.collisions += src
else
. = 1
else
. = ..(o)

mob
collision_layer = COLLIDE_MOBS

obj
collision_layer = COLLIDE_OBJS


Now, this is where the magic happens. We need to create two global functions that we can quickly and easily call in order to test for collision, without all the set-up and breakdown that balloons our function calls:

//you should never have to think about this object again.
//we only need one. There will never be any reason to initialize another.

var/collider/__collider = new/collider()

proc
can_move(atom/movable/ref,turf/fromturf,turf/toturf,layer)
__collider.proxy = ref
__collider.collision_mask = layer
__collider.loc = fromturf
. = __collider.Move(toturf)
__collider.loc = null
__collider.proxy = null
__collider.collisions.Cut(1,0)

get_blockage(atom/movable/ref,turf/fromturf,turf/toturf,layer)
__collider.proxy = ref
__collider.collision_mask = layer
__collider.loc = fromturf
__collider.Move(toturf)
__collider.loc = null
__collider.proxy = null
. = __collider.collisions
__collider.collisions = list()


Now, in using this, you should understand a bit about binary math. You need to specify all the layers that the collider will collide with. I only create 4 layers by default, as well as a shortcut for all four combined.

If you want to include multiple layers, you should use the OR operator to combine them.
I included the shortcut for all four so that you could exclude specific layers using the XOR operator.

COLLIDE_ALL ^ COLLIDE_MOBS //collide with everything but mobs
COLLIDE_ALL ^ (COLLIDE_MOBS | COLLIDE_OBJS) //collide with everything but mobs and objs
(COLLIDE_TURFS | COLLIDE_OBJS) //collide with turfs and objs only
(COLLIDE_AREAS | COLLIDE_TURFS) //collide with areas and turfs only


As for the two global functions:

canMove()

Arguments:
ref - A reference to a movable atom. Can be null. Supplies a proxy.
fromturf - the turf the move starts from.
toturf - the turf the move is attempting to enter.
layer - bitflags representing the types of objects we will trigger collision with.

Returns:

1 if succeeds, 0 if failed.


getBlockage()


Arguments:
ref - A reference to a movable atom. Can be null. Supplies a proxy.
fromturf - the turf the move starts from.
toturf - the turf the move is attempting to enter.
layer - bitflags representing the types of objects we will trigger collision with.

Returns:

the list of objects we have collided with, or an empty list if successful.
Special thanks goes to Reformist for pointing out an issue where the collider's location was not properly being set back to null after completing the movement test.

Also helped me diagnose an issue where turfs were incorrectly not ignoring collisions where instructed.
Starting to get the hang of how to use this properly. Very nice as always.
Updated to obey BYOND's built-in naming convention for global procs.