Cauldron2D.Sectors.Move (Cauldron2D v0.2.2)

Copy Markdown View Source

Moving a body through a sector map: whether it fits where it is going, what floor and ceiling it finds there, and sliding along a wall it runs into.

case Move.try(world, body, {x + dx, y + dy}, step: 24.0) do
  {:ok, moved} -> moved.x
  {:blocked, why} -> why.reason
end

{moved, momentum} = Move.slide(world, body, {dx, dy}, step: 24.0)

world is %{map: map, blockmap: blockmap, sectors: sectors} — the sectors as they are now, since floors and ceilings move. A body is %{x:, y:, z:, radius:, height:} with z its feet; its box, radius each way from its centre, is what collides.

A body fits at a point when no line it may not pass crosses its box, no body it may not pass overlaps it, the opening there is as tall as it is, its head is below the lowest ceiling, the floor is no more than :step above its feet, and — unless it may drop — no more than :step above the lowest floor under its box.

Options

  • :step — how far up a body steps without climbing. Default 24.0
  • :dropoff — whether a body may walk off a ledge. Default true
  • :line_blocks? — fn line_index -> boolean for two-sided lines that stop this body; a one-sided line always does. Default: lines flagged :blocks
  • :body_blocks? — fn {id, point, radius} -> boolean for overlapping bodies that stop it. Default: every body
  • :except — the moving body's own id in the blockmap. Default nil

Summary

Functions

What a body would find at point, without moving it. See place/0.

Whether line index crosses the box of radius round point.

The gap through two-sided line index: %{top:, bottom:, low:, range:} — the lower ceiling, the higher floor, the lower floor and the height between — or nil for a one-sided line.

Move the body by momentum, sliding along whatever wall stops it, as Doom's player does. Gives {moved, momentum}: moved as try/4's success, or the body where it was with crossed: [], and the momentum left after sliding.

Move the body to point if it fits there: {:ok, %{x:, y:, floor:, ceiling:, dropoff:, sector:, crossed:, lines:, bodies:}}, where crossed lists the lines whose side the body's centre changed, lines the two-sided lines its box touches and bodies the bodies it overlaps without being stopped, or {:blocked, %{reason:, place:}} with reason one of :line, :body, :tight, :ceiling, :step and :dropoff.

Types

body()

@type body() :: %{
  x: number(),
  y: number(),
  z: number(),
  radius: number(),
  height: number()
}

place()

@type place() :: %{
  sector: non_neg_integer(),
  floor: float(),
  ceiling: float(),
  dropoff: float(),
  blocked: nil | {:line, non_neg_integer()} | {:body, term()},
  lines: [non_neg_integer()],
  bodies: [Cauldron2D.Sectors.Blockmap.body()]
}

world()

@type world() :: %{
  map: Cauldron2D.Sectors.Map.t(),
  blockmap: Cauldron2D.Sectors.Blockmap.t(),
  sectors: tuple()
}

Functions

check(world, body, point, opts \\ [])

@spec check(world(), body(), {number(), number()}, keyword()) :: place()

What a body would find at point, without moving it. See place/0.

crosses_box?(map, index, arg, radius)

@spec crosses_box?(
  Cauldron2D.Sectors.Map.t(),
  non_neg_integer(),
  {number(), number()},
  number()
) ::
  boolean()

Whether line index crosses the box of radius round point.

opening(world, index)

@spec opening(world(), non_neg_integer()) ::
  %{top: float(), bottom: float(), low: float(), range: float()} | nil

The gap through two-sided line index: %{top:, bottom:, low:, range:} — the lower ceiling, the higher floor, the lower floor and the height between — or nil for a one-sided line.

slide(world, body, momentum, opts \\ [])

@spec slide(world(), body(), {number(), number()}, keyword()) ::
  {map(), {float(), float()}}

Move the body by momentum, sliding along whatever wall stops it, as Doom's player does. Gives {moved, momentum}: moved as try/4's success, or the body where it was with crossed: [], and the momentum left after sliding.

try(world, body, point, opts \\ [])

@spec try(world(), body(), {number(), number()}, keyword()) ::
  {:ok, map()} | {:blocked, map()}

Move the body to point if it fits there: {:ok, %{x:, y:, floor:, ceiling:, dropoff:, sector:, crossed:, lines:, bodies:}}, where crossed lists the lines whose side the body's centre changed, lines the two-sided lines its box touches and bodies the bodies it overlaps without being stopped, or {:blocked, %{reason:, place:}} with reason one of :line, :body, :tight, :ceiling, :step and :dropoff.