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 worldidofworlds/1(:firstfor 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/1andto_guide/1are 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— aTuningFork.Sinkmodule or{module, opts}this player's audio plays through. Default: the game'ssink/1given all the mount props, else for a session served over ssh (:served_by) raw PCM to127.0.0.1:<:pulse_port>, the account's tunnel (TuningFork.Sink.Tcp), or silence without one; for any other sessionTuningFork.Sink.Speakerwhen that package is present, else silence:settings— the mapCauldron2D.Drafter.Client.Settings.to_props/1produced, from the account props; a settings file for the player overrides it. Default: the game's first preset:accounts— theDrafter.Accountsserver settings are saved to. Default: none:kind— the kind of client this is, the key its settings are kept under (seeCauldron2D.Drafter.Client.Settings). Default:terminal:start— the flow, as the Flow section describes it, over the game'sstart/0. Default: the game's, else:lobby:picture— how a game's picture reaches a terminal that keeps images, asCauldron2D.Drafter.Client.Picturereads it. Default: the application envconfig :cauldron_2d_drafter, picture: ..., elseCAULDRON_PICTURE, else the game's look, elseCauldron2D.Drafter.Client.Picture.default/0:mute—trueplays no sound, whatever sink the game or the session would have had. Default: the application envconfig :cauldron_2d_drafter, mute: true, elseCAULDRON_MUTEset to1ortrue, elsefalse
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
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.
@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.
@spec focus(map(), Cauldron2D.Client.Game.scene()) :: {number(), number()}
The point the world is drawn around: the scene's focus moved by the pan.
@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.
@spec home(map()) :: :lobby | :title | :stop
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.
@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.
@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.
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.
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.
@spec worlds(map()) :: [Cauldron2D.Client.Game.world()]
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.