QuakeServer.Runner (quake_server v0.1.0)

Copy Markdown View Source

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

serverdata()

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

snapshot()

@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

begin(runner, level, opts \\ [])

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

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

command(runner, command)

@spec command(pid(), QuakeServer.Server.command() | map()) :: :ok

Use command for the player from the next frame on.

console(runner, text)

@spec console(pid(), String.t()) :: :ok

Run console commands text in the level (QuakeServer.Console.run/2).

handoff(runner)

@spec handoff(pid()) :: :ets.tid()

The table the runner hands its events, the player's eye and view angles over in.

load(runner)

@spec load(pid()) :: :ok

Load the level a runner started with held: true was given.

request(runner, name, values)

@spec request(pid(), String.t(), [{String.t(), term()}]) :: :ok

Run the player's client program request name with values (QuakeServer.Requests).

start_link(opts)

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

subscribe(runner, client)

@spec subscribe(pid(), pid()) :: :ok

Send the player's packets to client from now on, starting with the level as it stands.

take(table)

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