New

dot-game

The server-side wiring every game in the collection was repeating — netcode, identity, services, a roster, one authoritative tick and a teardown in reverse — extracted once.

A game in this family is a DotModule the server loads. Before this addon, every one of them contained the same two hundred lines.

Requires dot-core, dot-server and dot-net — all three, and it names all three freely.

The sequence, written once

Find the game object, build a DotNetManager with the same four non-obvious settings, construct a bridge, attach it, open its link under the server node, seal the message schema, build an identity layer and await it, load the platform module by path, build a services layer and hand it the link, keep track of who is actually playing, drive one authoritative tick, and take it all down in the reverse order.

The parts that genuinely differed between five games were the class names and a handful of numbers. DotGameModule is that sequence; what is left for a game to write is what is actually its own:

extends DotGameModule

const MyBridge := preload("net/my_bridge.gd")

func _module_name() -> String: return "mygame"
func _game_service() -> StringName: return MyGame.SERVICE

func _net_config() -> DotNetConfig:
    var config := DotNetConfig.new()
    config.tick_rate = game.tick_rate
    config.snapshot_rate = 32
    return config

func _make_bridge() -> Node: return MyBridge.new()
func _make_identity() -> Node: return MyIdentity.new()
func _make_services() -> Node: return MyServices.new()

func _game_load() -> DotResult:
    add_command("mygame_status", _cmd_status, "Show the match state")
    return DotResult.success(null)

What is in it

DotGameModule The sequence, the tick, the teardown. Subclass it.
DotGameNetcode Builds a DotNetManager and attaches a game’s bridge. Usable on its own.
DotGameRoster Who is in the game as opposed to who is connected, and everything that has to be told when that changes — including telling followers to forget a player who leaves.
DotGameServices Chat, a relay, moderation and voice, built in the one order that works. Subclass it for a game’s channels, rules and voice range.

The identity layer _make_identity() returns is not this addon’s: it is dot-platform’s DotPlatformIdentity, because every type it builds is dot-platform’s dependency and none is this one’s. A game returns one configured with its avatar schema, and with it on the module loads dot-platform’s own module beside it.

What uses it: every minigame (mg-buses-from-hell was the first), hide-n-seek, murder-mystery, zombie-zone and the starter game in dot-game-template subclass both DotGameModule and DotGameServices. game-playground subclasses only DotGameServices so far. arena, g2gfast and hungario are not converted yet and still carry their own copies of both.

The services layer

Five games had services layers of five to seven hundred lines each, differing in their channels, their rules and a voice range. The relay was identical in all five, comments included; so were the admission check, the peer fan-out, the punishment subject — and the order, which is the part with a bug behind it. DotGameServices builds chat, a relay, moderation and voice, and a subclass answers only what is its own.

It also builds dot-moderation’s live tools — noclip, freeze, slay, blind and the rest — and binds their commands to the server’s console, so a game gets the community admin set by answering _mod_abilities with a table of callables. Reasons for what it refuses, the teleport verbs, and whether a target is alive or on a team are virtuals too. The commands are unbound when the layer leaves the tree, because the console outlives it.

dot-chat, dot-voice and dot-moderation are not dependencies, and must not become them: a game with no chat is a legitimate game, and one with no voice is most of them. Each layer is loaded by path and driven through set() and call(), so a missing addon is a layer skipped with a log line rather than a server that will not boot. What a subclass hands back — channels, rules, a voice config — it may name freely, because a game that configures chat has dot-chat.

Hooks, each defaulting to the old behaviour

Moving the first hand-written game onto both bases found five places the base had no question to ask:

Hook What it is for
DotGameServices.setup(null, …) Offline play. Only what is bound to a server is skipped — the tool commands, the relay telling clients, the admin and audit seams.
_can_hear(listener, speaker, listener_at, speaker_at) One rule for who hears whom, wired into both chat’s and voice’s can_hear_fn. Asked only of listeners already in a proximity channel’s range.
_mod_configure_tools(tools) Adjust the live tools after their handlers are set, before they enter the tree or a command is bound.
_peer_can_receive(peer) and send_backlog(peer) Hold the chat backlog for a client that builds its scene after it is seated — a line sent before then is a “Node not found”.
DotGameModule._can_tick() Skip the tick, asked before the tick counter moves — for instance while a game change has freed the world.

Telling the server which map you are on

A server listing — A2S, DQP and the backbone’s stats report — prints a map. Only the game knows what it calls one (a course, an arena from a rotation, a round’s layout), so the module says it: call report_map("surf_mesa") whenever your map changes, and report_map("") to clear it. A game that never calls it is listed under its own display name, which is right for a game with one map. The server forgets the map whenever a game unloads, so report it on every change, not once at load. On a server too old to take a map the call returns false and logs one line; nothing breaks.

The client half of the same extraction is DotGameClient: see Moving a client onto DotGameClient.

What is deliberately not in it

No rules. Whether a round has ended, what a team is, what the objective is, who won — those belong to dot-match and dot-objective, and they stay there. A game is free to use neither and still use this.

The test for whether something belongs here is whether every game would write it the same way with only the class names changed. A map rotation would not: some games have one, some do not, and the ones that do wire it differently. So DotGameRoster will tell a rotation that somebody left — by duck-typed follow() — and this addon does not know what a rotation is.

The four netcode settings, and why they are not defaults

DotGameNetcode fixes four things and leaves the rest to the game:

auto_tick = false

A game’s tick happens inside the netcode’s, between applying each peer’s inputs and building the snapshot, which is what the bridge arranges. A manager ticking itself as well moves every player twice — and what that looks like is a game running at double speed only when somebody is connected.

config_file = ""

The manager reads a JSON file by default, so a stale one in user:// sets a tick rate the game did not choose and nothing says so.

local_peer_id = 1

The authority’s id in Godot’s multiplayer, which the bridge routes by.

messages.seal() last

Wire ids come from a sort of the registered type names, so a type registered after the seal renumbers every id above it and silently reinterprets every message.

A client whose message schema this server cannot play with is kicked. dot-net refuses the pair but owns no socket, so DotGameNetcode connects its peer_schema_refused to the server’s refusal, and the client is kicked with dot-net’s sentence rather than left to fail on the first message it misreads.

Tick rate, snapshot rate and world extent have no default here, because there is no default that is right for two different games — and a game running at a rate it did not choose is the kind of wrong that nothing reports.

Everything optional is reached without naming it

A map director and a vote are duck-typed; dot-platform’s module and the services layer’s chat, voice and moderation are loaded by path. In GDScript a script that so much as mentions a class_name the project does not have fails to parse and takes down everything referencing it, so an addon that named an optional dependency would make it mandatory. See Conventions.