# `Cauldron2D.Drafter.Client`
[🔗](https://github.com/jaman/cauldron/blob/v0.2.2/cauldron_2d_drafter/lib/cauldron_2d/drafter/client.ex#L1)

The drafter application that puts a `Cauldron2D.World` in front of one player.

Runs a game's client module — one implementing `Cauldron2D.Client.Game` —
through its screens: a title, a lobby listing the worlds, settings, the world itself,
and a summary when the game's `outcome/1` says play is over for this player. It holds the player's input, their audio, and their connection to the world.

A world whose `world` is `{module, opts}` is this player's alone: the client starts
a `Cauldron2D.World` for it when the player joins and stops it when they leave, and
`p` pauses and resumes it.

## Flow

Where the client starts and where leaving a world goes is the game's `start/0`, or
the `:start` mount prop over it:

  * `:lobby` — the title, then the lobby, then a world; back retraces it. The default
  * `{:lobby, title: false}` — the lobby first; back from it stops the client
  * `{:play, id}` — straight into world `id` of `worlds/1` (`:first` for the first
    listed); leaving it stops the client
  * `{:play, id, title: true}` — the title, then that world; back returns to the title
  * a function of the client's state, called once the client is ready, for a flow of
    the game's own: `enter/3`, `to_lobby/1`, `to_title/1` and `to_guide/1` are the
    moves it can make; leaving a world then stops the client

A game with `lobby?/0` false and no `start/0` is read as `{:play, :first, title: true}`.

    Drafter.Server.start_ssh(Cauldron2D.Drafter.Client,
      auth: {:accounts, accounts},
      tunnel: true,
      mount_props: %{game: MyGame.Client, accounts: accounts}
    )

## Mount props

  * `:game` — the client module. Required
  * `:username` — the player's name, set by the ssh transport. Default `"player"`
  * `:sink` — a `TuningFork.Sink` module or `{module, opts}` this player's audio plays
    through. Default: the game's `sink/1` given all the mount props, else for a session
    served over ssh (`:served_by`) raw PCM to `127.0.0.1:<:pulse_port>`, the account's
    tunnel (`TuningFork.Sink.Tcp`), or silence without one; for any other session
    `TuningFork.Sink.Speaker` when that package is present, else silence
  * `:settings` — the map `Cauldron2D.Drafter.Client.Settings.to_props/1` produced,
    from the account props; a settings file for the player overrides it. Default: the
    game's first preset
  * `:accounts` — the `Drafter.Accounts` server settings are saved to. Default: none
  * `:kind` — the kind of client this is, the key its settings are kept under (see
    `Cauldron2D.Drafter.Client.Settings`). Default `:terminal`
  * `:start` — the flow, as the Flow section describes it, over the game's `start/0`.
    Default: the game's, else `:lobby`
  * `:picture` — how a game's picture reaches a terminal that keeps images, as
    `Cauldron2D.Drafter.Client.Picture` reads it. Default: the application env
    `config :cauldron_2d_drafter, picture: ...`, else `CAULDRON_PICTURE`, else the
    game's look, else `Cauldron2D.Drafter.Client.Picture.default/0`
  * `:mute` — `true` plays no sound, whatever sink the game or the session would have
    had. Default: the application env `config :cauldron_2d_drafter, mute: true`, else
    `CAULDRON_MUTE` set to `1` or `true`, else `false`

Every other mount prop is the game's, read by its `sink/1`, `pages/1` and `worlds/1`.

## Embedding

A game with screens of its own — its own title, a menu, pages that take input —
runs its own `Drafter.App` and drives this one from it: it keeps the client's state
from `mount/1` (with `start: {:lobby, title: false}`), and hands it its events, messages and timers
through `handle_event/2`, `handle_event/3`, `on_message/2` and `on_ready/1`, draws
`render/1`, and calls `unmount/1` when done. A `{:stop, reason}` from the client
is its wish to end: the game takes its screens back. The game's `use Drafter.App`
needs the same options this module's has — `key_release: true, cell_size: true,
controller: true, mouse_hover: true, frame_pacing: :always` — and its
`refresh_rate/0` should be `refresh_rate/0` here.

## Tracing

With `CAULDRON_TRACE` set (see `Cauldron2D.Trace`), the client writes every key and
mouse event it steers with (`key`, `mouse`), every input it sends the world (`input`,
with the actions held and the aim in tiles) and every frame the world sends back
(`frame`, with its tick), so `mix cauldron.report --from mouse --to frame` measures
the way round.

## Keys

The client's own keys are `default_keys/0`, each replaceable or dropped by the game's
`client_keys/0`; what follows describes the defaults. In the world `t` opens a line
to talk to everyone in it — `Enter` sends, `Esc` cancels, and no key steers while it
is open. `Esc` goes back: settings to where it was opened from, world to lobby, lobby
to title, title out of the program. `q` quits the world for the lobby too, and goes
to the title from the lobby. `^Q` and `^C` quit from anywhere. `?` shows the world's key hints and hides
them again; held, it shows them until released. The `fps` key, bound only when the
game binds it, shows under the world how many frames a second the world sends and
hides it again. `=` (or `+`) and `-` zoom the world in and out through the levels
`Cauldron2D.Drafter.Surface.zoom_levels/2` gives for the display in use, the wheel
too (one step for the events a notch sends within 150 ms) unless the game binds the
wheel (`{:mouse, :wheel_up}`, `{:mouse, :wheel_down}`), and `0` puts the zoom
back; the zoom shows under the world while it is not
native. Watching (joined with `w`), the arrows and a drag with the left button pan
the view, the wheel zooms about the pointer, and `0` recentres; flying, the view
stays on the ship and the wheel zooms about it. A page the game adds (`pages/1`)
opens from the title on its key; `Esc` or `q` closes it.

The view: `v` cycles the projection (top-down, oblique, isometric, mode 7, first
person, as the game's `look/0` lists them), `[` and `]`
turn it — the isometric facing, or the eye by 15° in first person when the scene
gives no heading — `'` and `;` tilt the isometric diamond or pitch the eye. Four
keys are unbound until a game binds them through `client_keys/0`: `display` toggles
pictures against block characters, `glyphs` cycles the glyph sets, `menu` opens the game's
own menu (its `open_menu/1`; see `Cauldron2D.Client.Game`), `mouse_look` lets
the pointer turn and pitch the eye in first person; a drag turns and pitches it there
whether or not `mouse_look` is on, and a click still reaches the game. What the view is, and how it is
drawn, shows under the world when it is not the top-down default. A game's `look/0`
sets the view it starts in.

Each connected controller plays one of the client's players — the player itself, then
its seats — as `Cauldron2D.Drafter.Client.Controllers` assigns them from the settings'
`pads`, and the settings' `controllers` list them for the game's menus; a controller
that plays no one is ignored during play, and while the game's menu is open any
controller works it. A controller moved to another player first lets go of what it
held for the one before.

A controller: the left stick and d-pad hold the game's directions (`padmap/1`), the
right stick, unless the padmap binds its axes to actions, nudges the aim a tile at a time top-down and isometric and looks around
in first person, the triggers tilt the isometric diamond or pitch the eye — right up,
left down, as far as they are pulled — `Start` cycles the projection and the bumpers
turn; `client_pad/0` rebinds the client's own actions to buttons, and in the lobby
the d-pad chooses and `A` joins. A stick the padmap binds to `:aim` aims instead,
eight tiles from the focus in the direction it is pushed (turned with the isometric
facing), letting go when it comes back to centre; a stick that aims does not nudge.

Title: `Enter` plays, `s` opens settings, `h` the guide, `q` quits. Lobby: `↑`/`↓`
pick a world, `t` a team where the world has them, `Enter` joins, `w` watches, `s`
opens settings, `c` chats and `Enter` on an empty line leaves the chat. Settings: `↑`/`↓` move over
the items — preset, each action's binding, pointer steering, effects level, music
level, display (pixels where the terminal has them, braille, a block-character glyph
set, or glyphs), frame rate
(auto picks the terminal's best) — `←`/`→` change the item under the cursor, `Enter` on a binding then
a key, a mouse button or a notch of the wheel adds it to the binding, `Backspace` removes the binding's last
key, `Esc` during a rebind cancels it. World: the game's bindings; `Tab`
opens settings without leaving the ship; the pointer steers only when pointer
steering is on, and a key bound to one of the game's `steering/0` actions takes over
from it until the pointer moves again.

# `default_keys`

```elixir
@spec default_keys() :: %{required(atom()) =&gt; atom() | [atom()]}
```

The client's keys by action — a key, or a list of keys — before the game's `client_keys/0` is applied.

# `enter`

```elixir
@spec enter(map(), term(), map()) :: map()
```

Join world `id` of the game's `worlds/1`, or the first when `id` is `:first`, with
`props` merged into the join; the client then shows the world. With no such world the
state comes back unchanged.

# `eye_height`

```elixir
@spec eye_height(module(), Cauldron2D.Client.Game.scene()) :: number() | nil
```

How far above the ground the camera sits for a scene, in tiles, or `nil` to leave the
projection its own.

The scene's `:eye` says it: a height in tiles, or the art whose eyes they are, whose
own `Cauldron2D.Atlas.eye_height/2` is then used. A game that draws figures of
different heights gives the art, so each player sees the world from their own height.

# `focus`

```elixir
@spec focus(map(), Cauldron2D.Client.Game.scene()) :: {number(), number()}
```

The point the world is drawn around: the scene's focus moved by the pan.

# `follow`

```elixir
@spec follow(map(), Cauldron2D.Client.Game.scene()) :: map()
```

The state with its pan dropped when the scene's `:subject` is not the one the pan was taken on; a scene with no subject keeps the pan.

# `home`

```elixir
@spec home(map()) :: :lobby | :title | :stop
```

Where leaving the world goes for the flow in force: the lobby, the title, or out.

# `keybindings`

# `look`

```elixir
@spec look(map()) :: map()
```

The client's projection settings for the world: projection, facing, tilt, heading and pitch, as `Cauldron2D.Drafter.Surface` takes them.

# `paused?`

```elixir
@spec paused?(map()) :: boolean()
```

Whether the player's own world is paused.

# `scene`

```elixir
@spec scene(map()) :: Cauldron2D.Client.Game.scene()
```

The game's scene for the client's view and look: `scene/2` where the game defines it, else `scene/1`.

# `steered`

```elixir
@spec steered(Cauldron2D.Input.state(), map()) :: map()
```

The player's input as the world should see it for the projection in use. A view that
does not turn sends it as held: top-down always, and the oblique view at its default
single facing. A view at one of four facings turns the four direction actions with it,
and of the directions a stick holds keeps only the one pushed hardest.

On a finer `facings` a quarter turn is no longer the step, so the directions go as held
and the input carries `:heading` instead, as it does in first person and mode 7: a
heading in radians with `0` along `+x`, along which the game walks `:up`, against which
it walks `:down`, and across which it walks `:left` and `:right`. A game offering more
than four facings must read `:heading`; pressing up then moves the same way across the
picture however far the view has turned. Turning the view is the client's `turn_left`
and `turn_right`, not a game action.

# `to_guide`

```elixir
@spec to_guide(map()) :: map()
```

Show the guide, returning to the screen the client is on.

# `to_lobby`

```elixir
@spec to_lobby(map()) :: map()
```

Show the lobby, leaving any world the player is in.

# `to_title`

```elixir
@spec to_title(map()) :: map()
```

Show the title, leaving any world the player is in.

# `turns_with_pointer?`

```elixir
@spec turns_with_pointer?(map()) :: boolean()
```

Whether a drag with the left button turns the view the look describes: it does in first
person and mode 7, and in the isometric view when the game offers more than four
facings. At four facings the view steps in quarters and there is nothing to drag to.

Only the left button. A drag with any other belongs to the game, which may have bound it
to an action whose aim must stay where the player put it: turning the view under a held
button moves the world out from under the pointer, so the release lands somewhere the
player never pointed at.

# `worlds`

```elixir
@spec worlds(map()) :: [Cauldron2D.Client.Game.world()]
```

The game's worlds, from its `worlds/1` given the mount props.

# `zoom_levels`

```elixir
@spec zoom_levels(map()) :: [number()]
```

The zoom levels this client steps through: `Cauldron2D.Drafter.Surface.zoom_levels/3` for the game's atlas, the display in use and the projection in view.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
