MoveLib3 implements an array of improvements to BYOND's native pixel movement utilities that will make working with coordinates and implementing common quality of life features much more accessible.
MoveLib3 implements the following behaviors:
* Collision reciprocity - Movers are given the final say in what they collide with, and are informed when they start or stop overlapping on-map atoms or existing within other atoms.
* Vector2 and coord data types - Working with coordinates requires much less boilerplate.
* Step() and Relocate() - force movables to make slides or jumps at any distance and restore their step_size once the movement is finished.
* Angular, vector2, coord, and directional forms for Move(), Step(), and Relocate() - Makes moving things over pixel distances much less of a pain.
* Trace() - a highly configurable collision detection function, which simulates a movement with options to ignore Cross()/Uncross() results and calls, as well as to disable Crossed()/Uncrossed()/Entered()/Exited()/Bump() calls. It can ignore a passed list of atoms, as well as return a /trace datum which allows movements to be stored and finalized after inspection.
* atom/movable/New() override allows passing /coord datums for pixel-perfect on-map initialization without extra arguments.
* bounds_edge() function which allows getting all objects just past the bounding area of an atom in a specified direction.
Check out the Documentation for more information.
ID:2860944
Mar 20 2023, 10:00 pm
|
|
atom/movable.New():
atom/movable.New() can take a /coord as its first argument. Provided the atom has no location upon atom/movable/New()'s default behavior, it will be moved into place where the coordinate specifies. If you override New() on a subtype of movable, you will likely want to ensure that you do not cut off this default behavior. Movement changes: Move() now takes several new forms: Move(vector2,Dir) Move(coord,Dir) Passing a coord datum will attempt to end the move with the bottom-left corner of the movable's bounding box at the location the coordinate references. This could either be a slide or a jump, depending on step_size. Passing a vector2 datum will attempt to end the move with the bottom-left corner of the movable's bounding box at the location on the movable's current z layer at the location the vector2 references. This could be either a slide or a jump, depending on step_size. In both cases, these are absolute coordinates, but they are 0-indexed by default instead of 1-indexed. The bound_x and bound_y values of the movable are taken into account when finding the destination. atom/movable.Step() has been added, which is shorthand for Move(), which forces a slide. Step(turf/NewLoc,[Dir,step_x,step_y]) - turf form Step(coord,[Dir]) - coord form Step(vector2,[Dir]) - vector2 form Step(direction,[Dir,dist=step_size]) - directional form Step(ang=0..360,[face=0,dist=step_siz e]) - angular form The various forms for Step() have a rigid structure. If you are using the turf form, you must not use any of the named arguments. Only the NewLoc, Dir,step_x, and step_y arguments are valid in this form. If you are using the coord or vector form, only the NewLoc and Dir argument are valid, where NewLoc is the coord or vector2 datum, and Dir is the forced facing direction of the mover following the movement. 0 by default will make the mob face whatever direction they appeared to have moved. If you are using the directional form, the direction of movement is the first argument, and the second positional argument, if present, is the forced facing direction. Otherwise this will be 0. You may use the dist named argument to pass a step distance in that direction, which is mover.step_size by default. If you are using the angular form, no positional arguments are valid, and the face and dist arguments have been provided instead. Note: Step() sets the mover's step_size to 1#INF to ensure that the movement will always be a slide. This can result in bugs if you call another movement function during a movement-derived hook, such as Cross()/Crossed(),Bump(),Enter()/Entered(), or their antonyms. It is best practice to avoid doing this. Once the Step() is resolved, step_size is restored provided it is still 1#INF. This means it is safe to change step_size within a movement-derived hook. If you need to access the mob's real step_size during a move_derived hook, step_size will be unreliable, and you should use __restore_step_size instead to gather that information. atom/movable.Relocate() has been added, which is shorthand for Move(), which forces a jump. Relocate(turf/ NewLoc,[Dir,step_x,step_y]) - turf form Relocate(coord,[Dir]) - coord form Relocate(vector2,[Dir]) - vector2 form Relocate(direction,[Dir,dist=step_siz e]) - directional form Relocate(ang=0..360,[face=0,dist=step _size]) - angular form Relocate() is similar to Step(), in that it takes all the same argument structure, but instead of forcing a slice, it forces a jump. Note: Step() sets the mover's step_size to 1 to ensure the movement will be a jump where possible. This can result in bugs if you call another movement function during a movement-derived hook. It is best practice to avoid doing this. Once the Step() is resolved, step_size is restored provided it is still 1. This means it is safe to change_step_size to any value but 1 within a movement-derived hook. If you want to change step_size to 1 permanently during a move derived hook, also set __restore_step_size to 1 to ensure it isn't changed back when a Jump() finishes moving the mover. Similarly, this makes step_size unreliable for the real movement speed of the mover. You may wish to store this information in your own variable, or you may have to read __restore_step_size instead. |
/vector2 datum
The /vector2 datum stores an x (float), y (float), variable, and represents an arbitrary point in 2d space. It provides basic arithmetic operations on those points, as well as some convenience functions for getting information from the world using a vector2. operators supported:
vector2 procs
isvector2() is a shorthand for istype(v,/vector2) that's included in the library. You can get a vector2 from any atom by calling atom.vector2(anchor) anchor may be one of: NORTH (1) SOUTH (2) EAST (4) WEST (8) NORTHEAST (5) NORTHWEST (9) SOUTHEAST (6) SOUTHWEST (10) CENTER (0) The anchored position will result in a vector referencing one of the nine points along the left, center, right, top, middle, and bottom anchor points of the atom's bounding box. |
/coord datum
The coord datum is similar to the vector2 datum, but also contains a z coordinate. z coordinates ignore arithmetic, which is how they are distinct from a vector3. operators supported:
conversions: coord[1],coord["absolute_x"],coord["abs_x"]: x coord[2],coord["absolute_y"],coord["abs_y"]: y coord[3],coord["z"]: z coord["x"],coord["tile_x"]: round(x / TILE_WIDTH) + 1 coord["y"],coord["tile_y"]: round(y / TILE_HEIGHT) + 1 coord["step_x"]: x - round(x / TILE_WIDTH) * TILE_WIDTH coord["step_y"]: y - round(y / TILE_HEIGHT) * TILE_HEIGHT coord procs
iscoord() is a shorthand for istype(v,/coord) that's included in the library. You can get a coord from any atom by calling atom.coord(anchor) anchor may be one of: NORTH (1) SOUTH (2) EAST (4) WEST (8) NORTHEAST (5) NORTHWEST (9) SOUTHEAST (6) SOUTHWEST (10) CENTER (0) The anchored position will result in a coord referencing one of the nine points along the left, center, right, top, middle, and bottom anchor points of the atom's bounding box. |
atom/movable.Trace()
Trace() is a handy little function that acts quite a lot like a Step() or a Relocate() call. But it can return a /trace datum with information about the movement, and can be passed a list of atoms it will ignore collision with, or it can be passed a series of flags that will customize how it functions. Flags: TRACE_SLIDE (1) If on, the movement will act like Slide() TRACE_JUMP (2) If on, the movement will act like Relocate() --If both are off, it will just act like Move(), using step_size to determine jumping or sliding. TRACE_MOVE (4) If on, the movement will finalize immediately. TRACE_IGNORE_CROSS (8) Cross failures won't stop this movement, and atom.Cross()/Enter() won't be called by the mover. TRACE_IGNORE_UNCROSS (16) Uncross failures won't stop this movement, and atom.Uncross()/Exit() won't be called by the mover. TRACE_DISABLE_CROSSED (32) The mover won't call Crossed()/Entered() on finalization. TRACE_DISABLE_UNCROSSED (64) The mover won't call Uncrossed()/Exited() on finalization. TRACE_DISABLE_BUMP (128) The mover won't call Bump() on finalization. TRACE_RESULTS (256) If on, Trace() returns a /trace datum containing information about the attempted movement. If off, Trace() returns whatever Move() would have (the distance it's possible to move, or 0 on failure) Trace() Forms: Trace(turf/ NewLoc,[Dir,step_x,step_y,flags=TRACE_MOVE,ignore=null]) - turf form Trace(coord,[Dir,flags=TRACE_MOVE,ignore=null]) - coord form Trace(vector2,[Dir,flags=TRACE_MOVE,ignore=null]) - vector2 form Trace(direction,[Dir,dist=step_size,flags=TRACE_M OVE,ignore=null]) - directional form Trace(ang=0..360,[face=0,dist=step_size,flags=TRA CE_MOVE,ignore=null]) - angular form /trace datum Trace may return the number of pixels that it was possible to move, 0, or a /trace datum. The trace datum contains a list of variables that will provide information about the movement. atom/movable/mover - The mover that the trace was run on coord/old_loc - The mover's coords when the trace was started coord/new_loc - Where the mover attempted to trace a movement to coord/loc - The mover's coords when the trace completed or failed list/crossing - any atoms that would be crossed over during the trace list/uncrossing - any atoms that would be crossed out of during the trace atom/collider - the atom that caused the trace to fail result - what the traced movement would have returned (the number of pixels moved) Finalize() - This immediately places the mover at the final position of the trace, and calls the Crossed()/Uncrossed()/Entered()/Exited()/Bump() hooks for any crossing or uncrossing atoms in the correct sequence with the correct arguments, as though the movement were really happening. If the trace has already been finalized, nothing happens. As the trace already did the required calls to Cross()/Uncross()/Enter()/Exit() have already been simulated by the Trace(), there is no need to call them again here. |
COLLISION_BASIC (0)
COLLISION_ADVANCED (1)
Marks this movable as using basic or advanced collision. Advanced collision causes collision reciprocity functions to be considered during movement.
atom/o - The object that we are about to cross
allow - The value about to be returned by Cross()
Called by atom.Cross() when the movable has advanced collision turned on. See the style guide for more information.
atom/o - The object that we are about to uncross
allow - The value about to be returned by Uncross()
Called by atom.Uncross() when the movable has advanced collision turned on. See the style guide for more information.
atom/o - The object that we are about to enter
atom/oldloc - The location src is trying to enter from
allow - The value about to be returned by Enter()
Called by atom.Enter() when the movable has advanced collision turned on. See the style guide for more information.
atom/o - The object that we are about to exit
atom/newloc - The location src is trying to exit to
allow - The value about to be returned by Exit()
Called by atom.Exit() when the movable has advanced collision turned on. See the style guide for more information.
atom/o - The object that we just crossed
Called by atom.Crossed() when the movable has advanced collision turned on. See the style guide for more information.
atom/o - The object that we just uncrossed
Called by atom.Uncrossed() when the movable has advanced collision turned on. See the style guide for more information.
atom/o - The object that we just entered
Called by atom.Entered() when the movable has advanced collision turned on. See the style guide for more information.
atom/o - The object that we just exited
Called by atom.Exited() when the movable has advanced collision turned on. See the style guide for more information.
Style guide:
Collision reciprocity really needs to be baked into the engine by default. Unfortunately, the best we can do is a pretty hacky workaround that allows us to integrate it ourselves. The reason that this is a problem, is because it's brittle. Anywhere we override Cross(), we can break collision reciprocity with that object without meaning to. That's because we have no way to access the return value of the call that preceded a supercall. This means that your Cross()/Uncross()/Enter()/Exit() calls must always supercall, and they must always pass their return value as an additional argument of the supercall. You should build your overrides with this in mind.
Preserving reciprocity is important for the time being. If and when collision reciprocity is integrated into BYOND properly, this kind of pattern will become unnecessary. If you don't care for this pattern, you can just call the reciprocal yourself with a convenience macro:
Reciprocal macros:
recipCross(mover,return_value)
recipUncross(mover,return_value)
recipEnter(mover,oldloc,return_value)
recipExit(mover,newloc,return_value)
recipCrossed(mover)
recipUncrossed(mover)
recipEntered(mover,oldloc)
recipExited(mover,newloc)