A level running in a process of its own, on its own clock, as FTE's listen server runs
one for its own client (SV_Frame, SV_Physics): the host clock follows real time,
and physics frames catch up to it whenever it is at least the cvar sv_mintic ahead,
each frame as long as it is ahead up to sv_maxtic; after sv_limittics frames of
sv_maxtic the rest is dropped. A player's command runs as it arrives
(QuakeServer.Server.client_command/3), covering its msec milliseconds when given,
else its timelength in whole milliseconds, up to 250. A command's sequence (the
client's number for it) is the one the server acknowledges in every packet after it
runs, as FTE's server sends a client's last_sequence (PEXT2_PREDINFO): 0 when the
program does not run its players' commands itself, telling the client it cannot
predict.
{:ok, runner} = QuakeServer.Runner.start_link(map: "c0a0", server: [source: MyGame.Source])
QuakeServer.Runner.subscribe(runner, self())
receive do
{QuakeServer.Runner, {:serverdata, %{level: level}}} ->
QuakeServer.Runner.begin(runner, level, csqc: true)
end
QuakeServer.Runner.command(runner, command)
receive do
{QuakeServer.Runner, %{level: level, packets: packets}} -> ...
end
table = QuakeServer.Runner.handoff(runner)
%{events: events, listener: at, setangle: angles} = QuakeServer.Runner.take(table)A level starts without the player, as FTE's SV_SpawnServer does. The subscribed
client is sent {QuakeServer.Runner, {:serverdata, serverdata}} (serverdata/0),
what FTE sends a client from svc_serverdata to the spawn command: the level, a
number counting up from 1 with each level started, its map and name, the model and
sound lists, the server's info keys but mapname (FTE's fullserverinfo), and the
signon messages (QuakeServer.Signon); then a message with no packets carrying every
light style. A client subscribing later is sent the same for the level as it stands.
The client loaded, begin/3 brings the player in (QuakeServer.Server.connect/3),
as FTE's begin does, and sets the view (FTE's svc_setangle, FIXANGLE_FIXED): the
player's angles while its fixangle is set, else its v_angle, roll included;
fixangle is then cleared. A begin for a level other than the current one is
ignored, and one for a player already in the game runs the program's RestoreGame and
sets the view again.
Until the player is in the game the level is paused, as FTE pauses a single player server with nobody spawned: no physics frames run, the host clock moves on only while it stays at or below 1.5, and commands and requests are ignored.
Each server frame ends with QuakeServer.Network.frame/2 and
QuakeServer.Animation.sample/1; when a command has arrived since the last packet,
the player is sent one, as FTE sends a client a packet for each it receives. The
subscribed client gets each as the message {QuakeServer.Runner, snapshot}, a
snapshot/0: the packet in packets, as %{reliable: [binary], entities: [update], animation: %{entity => state}, datagram: [binary], sequence: integer} (the reliable
messages queued for the player, QuakeServer.Outbox, the entity updates, the
animation states the player lacks, QuakeServer.Animation.take/2, the datagram, and
the sequence of the last command the server ran), the
light styles set or changed since the last message (%{style => pattern}, as FTE
sends svc_lightstyle) and their colours (lightstyle_colours, %{style => {r, g, b}}), the server's info keys (serverinfo), the server's time, its model list, and
the level with its map.
A level change the program asks for is followed between frames, the clock waiting, as
FTE's changelevel (SV_Map_f): the player's spawn parameters and parm_string
(QuakeServer.Server.save_spawn_parms/1), the program's serverflags, its written
files with their entity tables (QuakeServer.EntityTable), the server's info keys and
the player's info keys are carried. With a landmark
the level left is kept for a return, as FTE's level cache keeps it: a program that
saves levels itself has it saved (QuakeServer.Server.save_level/1), and on a return
through a landmark the level starts afresh with the console variables of the save and
is restored from it (QuakeServer.Server.load_level/2); for another program the
player is taken out (QuakeServer.Server.disconnect/1), the level is kept as it
stands and brought back as it was left (QuakeServer.Server.resume/2). Without a
landmark the level left and the kept levels are released
(QuakeServer.Server.release/1) and the saves forgotten. The reliable messages still queued for the
player are sent as a last packet, then {QuakeServer.Runner, {:changing, map}}, as
FTE stuffs changing; the next level starts without the player.
After every frame and command the runner puts in its handoff table (handoff/1) the
events since, where the player's eye is, and the view angles the server set
(fixangle): {pitch, yaw, roll}, {:turn, yaw} for a turn to add (fixangle 2).
take/1 empties it of events and angles without waiting on the runner. The handoff
also holds whether the player stands on the ground and how fast they move.
console/2 runs console commands in the level's server, as FTE's host console
does. Every level the runner starts uses one console variable table, as FTE's one
process has one: the server option :cvar_table, or a table of the runner's own.
A program error (QuakeC.Exec.Error, raised by the program's error builtin with
developer 0 or by the machine) ends the server, as FTE's SV_Error and
Host_EndGame do: the level and the kept levels are released, the reason
"SV_Error: " <> message <> "\n" is put in the handoff table (ended in take/1) and
the subscribed client is sent {QuakeServer.Runner, {:ended, reason}}, as is a client
that subscribes after, and the table no longer counts a server as running
(QuakeServer.Cvars.running?/2). The runner then stays up with no server: commands, requests and
console commands are ignored and the clock stops.
Summary
Types
What a client is sent when a level starts, before it begins.
What a client takes of the world (snapshot/1).
Functions
Bring the player into level level, its client having loaded it, as FTE's begin;
option csqc: true when the client runs a client program (FTE's enablecsqc).
Returns a specification to start this module under a supervisor.
Use command for the player from the next frame on.
Run console commands text in the level (QuakeServer.Console.run/2).
The table the runner hands its events, the player's eye and view angles over in.
Load the level a runner started with held: true was given.
Run the player's client program request name with values (QuakeServer.Requests).
Start level :map with Server.start/2 options :server; with held: true the level
is not loaded until load/1.
Send the player's packets to client from now on, starting with the level as it stands.
The events put in table since the last take, oldest first, the player's eye, the
last view angles the server set since, or nil, whether the player stands on the
ground (onground) and their velocity, the reason the server ended (ended), or
nil while it runs, and the level's map (map), or nil before the first.
Types
@type serverdata() :: %{ level: pos_integer(), map: String.t(), levelname: String.t(), model_names: [String.t()], sound_names: [String.t()], serverinfo: %{required(String.t()) => String.t()}, signon: [binary()] }
What a client is sent when a level starts, before it begins.
@type snapshot() :: %{ level: pos_integer(), map: String.t(), model_names: [String.t()], packets: [ %{ reliable: [binary()], entities: list(), animation: %{required(pos_integer()) => QuakeServer.Animation.state()}, datagram: [binary()], sequence: non_neg_integer() } ], lightstyles: %{required(non_neg_integer()) => term()}, lightstyle_colours: %{required(non_neg_integer()) => tuple()}, serverinfo: %{required(String.t()) => String.t()}, time: float() }
What a client takes of the world (snapshot/1).
Functions
@spec begin(pid(), pos_integer(), keyword()) :: :ok
Bring the player into level level, its client having loaded it, as FTE's begin;
option csqc: true when the client runs a client program (FTE's enablecsqc).
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec command(pid(), QuakeServer.Server.command() | map()) :: :ok
Use command for the player from the next frame on.
Run console commands text in the level (QuakeServer.Console.run/2).
The table the runner hands its events, the player's eye and view angles over in.
@spec load(pid()) :: :ok
Load the level a runner started with held: true was given.
Run the player's client program request name with values (QuakeServer.Requests).
@spec start_link(keyword()) :: GenServer.on_start()
Start level :map with Server.start/2 options :server; with held: true the level
is not loaded until load/1.
Send the player's packets to client from now on, starting with the level as it stands.
@spec take(:ets.tid()) :: %{ events: [term()], listener: Cauldron3D.Vec.t(), setangle: term(), onground: boolean(), velocity: Cauldron3D.Vec.t(), ended: String.t() | nil, map: String.t() | nil }
The events put in table since the last take, oldest first, the player's eye, the
last view angles the server set since, or nil, whether the player stands on the
ground (onground) and their velocity, the reason the server ended (ended), or
nil while it runs, and the level's map (map), or nil before the first.