77 lines
5.5 KiB
Markdown
77 lines
5.5 KiB
Markdown
# Multiplayer - plan and status
|
|
|
|
Multiplayer is only available for users of a server (not in the standalone file).
|
|
|
|
## Decisions
|
|
|
|
- Modes: **versus** (every player a master with an own heart), **coop** (every player an own master,
|
|
together against AI masters and/or the humans) and a **coop campaign of 20 missions**.
|
|
- 1 to 4 players per match; free slots are empty unless the host marks them as AI with `set_ai` before the start.
|
|
- The match **pauses while a player is missing** (connection lost); a player who **leaves** is replaced by an AI.
|
|
- Before the start, the host manually marks a free seat as AI with `{"t":"set_ai","side":n,"ai":true}` and clears it again with `ai:false`; side 0 stays human.
|
|
- The humans (hero waves of the Order) as a third party (can be switched off), chat, alliances/teams.
|
|
- Coop is "separate but helpful": own heart, gold, mana, research and creatures; allies never fight each
|
|
other, their creatures help each other, gold can be sent, allied ground can be walked; a fallen heart only
|
|
knocks out that player (who may keep watching); the allies win when all enemies have fallen.
|
|
- Lobby: an open list of matches under *Multiplayer* in the title menu; the host chooses mode, map, AI slots,
|
|
humans on/off and teams; logged-in users join; the match starts when everybody is ready.
|
|
|
|
## Architecture: deterministic lockstep
|
|
|
|
Every client runs the same simulation (the same WASM module, so floating point results are identical).
|
|
The server does not simulate; it relays:
|
|
|
|
1. Clients send their **commands** (dig, build, cast, hand, orders, ...) to the server.
|
|
2. Every 100 ms the server sends **turn N**: the commands of all players for that turn.
|
|
3. Each client applies turn N and simulates five fixed steps of 20 ms. Turns arrive in order; a client waits
|
|
for the next turn, so a missing player stalls everybody - exactly the "pause while missing" rule.
|
|
4. Clients send a **checksum** of the game state every few seconds; a mismatch (desync) is reported.
|
|
|
|
Only the commands travel over the network, so bandwidth is tiny and no game state has to be serialised
|
|
per turn. Fog of war, particles and the camera are local and not part of the shared state.
|
|
|
|
## What is there already
|
|
|
|
- **Per-master state** (`game.rs`): every master has his own gold, mana, hand, research, payday, flag,
|
|
core... `Game::switch_to(side)` puts one master in the active fields, the simulation walks through all
|
|
masters step by step. A single player game has no stored masters and is unaffected.
|
|
- **Commands** (`commands.rs`): every order a player gives is an `Act` (`Dig`, `Room`, `Tower`, `Trap`,
|
|
`Cast`, `Flag`, `Pickup`, `Drop`, `Slap`, `Attack`, `Guard`, `CancelOrders`, ...) wrapped in a
|
|
`Command { side, seq, act }`. Creature targets are *ids*, so they mean the same on every client.
|
|
The tools in `main.rs` only queue commands now - nothing touches the world directly.
|
|
- **Steps**: the simulation runs in fixed steps of `STEP` = 20 ms, 50 steps a second (`Game::update` feeds real
|
|
time into them, `Game::apply_turn(cmds, steps)` is the lockstep entry point; one server turn of 100 ms is five
|
|
steps). Commands are applied at step boundaries, so single player and a match run exactly the same path.
|
|
Particles, effects and the camera keep running in real time: they are local and not part of the shared state.
|
|
- **Who speaks**: messages, hints and interface sounds belong to the master who is being processed
|
|
(`Game::speaking`); what another master orders never shows up on my screen.
|
|
- **Turns and checksums** (`netplay.rs`): `Game::start_match(sides, local)` starts a match,
|
|
`Game::take_orders()` hands the local orders to the server, `Game::receive_turn(cmds)` applies a confirmed
|
|
turn (five steps) and `Game::checksum()` compares the worlds of the clients - a peer with a different number
|
|
is reported once (`net.desync`). A client without the next turn stands still: that is the "pause while
|
|
missing" rule, and the tests run two clients of one match side by side.
|
|
- Savegames, tests and the standalone file are unchanged: `cargo test --release` covers the command path
|
|
(including "the same actions on both paths build the same world").
|
|
|
|
## Steps
|
|
|
|
| # | Step | Status |
|
|
|---|---|---|
|
|
| A | Simulation per master: gold, mana, research, rooms, hand, flag, traps, payday... per side; up to four masters (red, green, blue, yellow) | built |
|
|
| B | All player actions as commands (`Command` + `Game::apply`), single player uses the same path; fixed time step | built |
|
|
| C | Server: lobby and match relay over WebSocket (axum), turns, pause on disconnect, AI takeover on leave, chat | next |
|
|
| D | Client: *Multiplayer* menu (lobby, create, join, ready, teams), WebSocket bridge in `dc_plugin.js`, chat, gold sending | planned |
|
|
| E | Alliances and coop rules, AI masters for free and abandoned slots | planned |
|
|
| F | Coop campaign: 20 missions with story (EN/DE) | planned |
|
|
|
|
Still open on the way to C: heroes and alliances for the new sides (today the hero gate path always leads to
|
|
side 0's core, and in free play 2 and 3 are the rivals' sides), and what a match leaves out of savegames.
|
|
|
|
## Masters and sides
|
|
|
|
A match plays on the four master sides `0, 2, 3, 4` (side 1 is the heroes'): red (side 0, `Owner::Player`),
|
|
green (`Owner::Rival1`), blue (`Owner::Rival2`) and yellow (`Owner::Rival3`). `Game::start_masters(sides,
|
|
local)` carves a 9x9 starting area and a 3x3 soul core for every master and remembers his economy in a
|
|
`MasterState`; the places are worked out from the map size alone, so all clients agree without passing them
|
|
over the network.
|