Cauldron2D.Drafter.Client (Cauldron2D.Drafter v0.2.2)

Copy Markdown View Source

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.

Summary

Functions

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

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.

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

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

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.

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

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

Whether the player's own world is paused.

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

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.

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

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

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

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.

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

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.

Functions

default_keys()

@spec default_keys() :: %{required(atom()) => 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(state, id, props \\ %{})

@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(game, scene)

@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(arg1, arg2)

@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(state, arg2)

@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(map)

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

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

keybindings()

look(state)

@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?(map)

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

Whether the player's own world is paused.

scene(state)

@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(read, look)

@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(state)

@spec to_guide(map()) :: map()

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

to_lobby(state)

@spec to_lobby(map()) :: map()

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

to_title(state)

@spec to_title(map()) :: map()

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

turns_with_pointer?(look)

@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(state)

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

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

zoom_levels(state)

@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.