Working on server

This commit is contained in:
Vladislav Khorev committed 2026-11-09 17:58:20 +00:00
1 parent 423d8b8f52
commit 8c4eeb9793
7 files changed
+290 -80

No files matched your search

+123 -62
View File
@@ -1,11 +1,37 @@
# Headless GameServer
This target runs the authoritative `InternalGameServer` without SDL, OpenGL,
audio, Lua, or rendering dependencies. On Windows it exposes a deterministic
lockstep session through a Boost.Beast WebSocket listener.
audio, Lua, or rendering dependencies. It exposes a deterministic lockstep
session through a Boost.Beast WebSocket listener (Windows and Linux).
## Requirements
- CMake >= 3.16, a C++17 compiler (MSVC, or GCC/Clang on Linux)
- Ubuntu: `sudo apt install build-essential cmake git unzip curl`
- Internet access on the first configure (see below)
Eigen and Boost are **downloaded automatically** by
`cmake/FetchDependenciesServer.cmake` into `thirdparty/` the first time CMake
configures the project (Boost zip is ~200 MB). A system Eigen3 is used if
installed; Boost is always taken from `thirdparty/boost_1_90_0` so versions match
the Windows build. Nothing else (SDL, zlib, libzip, ...) is needed.
## Build
Linux (recommended helper, 1 compile job to stay within low RAM):
```bash
server/scripts/build.sh # JOBS=2 server/scripts/build.sh for more
```
Or manually:
```bash
cmake -S server -B server/build -DCMAKE_BUILD_TYPE=Release
cmake --build server/build --target GameServer --parallel 1
ctest --test-dir server/build --output-on-failure
```
Windows:
```powershell
@@ -14,91 +40,126 @@ cmake --build server/build --config Release --target GameServer
ctest --test-dir server/build -C Release --output-on-failure
```
Linux:
> **Memory note:** compiling Boost.Beast code can use 1-1.5 GB per translation
> unit. On a small VDS keep `--parallel 1`; if the compiler gets OOM-killed, add
> temporary swap (`sudo fallocate -l 2G /swapfile && sudo chmod 600 /swapfile &&
> sudo mkswap /swapfile && sudo swapon /swapfile`). The running server itself
> needs only a few tens of MB.
After every build the repository's `resources/` directory is copied next to the
binary (`server/build/resources`, or `server/build/Release/resources` on
Windows). Content paths are resolved from the process working directory, so the
server must be started from the directory that contains `resources/`.
## Run
```bash
cmake -S server -B server/build -DCMAKE_BUILD_TYPE=Release
cmake --build server/build --target GameServer --parallel
ctest --test-dir server/build --output-on-failure
server/scripts/run-server.sh
```
Eigen3 must either be installed or be available at
`thirdparty/eigen-5.0.0`. The headless target reads ordinary filesystem resources
only; ZIP-backed campaign loading belongs to the later content-loading
milestone.
Run until interrupted:
This does `cd server/build` and runs
```bash
./server/build/GameServer
./GameServer --campaign battle_zone_multiplayer --address 127.0.0.1 --port 8081
```
For a multi-configuration Windows build, the executable is normally
`server/build/Release/GameServer.exe`.
Environment overrides: `CAMPAIGN`, `LOCATION`, `ADDRESS`, `PORT`, `BUILD_DIR`.
For smoke tests, `--ticks N` limits execution and `--no-sleep` skips the
real-time delay.
Start the multiplayer lockstep server:
Windows equivalent:
```powershell
.\server\build\Release\GameServer.exe --campaign battle_zone_multiplayer --location uni_exterior
.\server\build\Release\GameServer.exe --campaign battle_zone_multiplayer
```
The listener defaults to `ws://0.0.0.0:8080`. Launch the Windows graphical game
afterward and select `battle_zone_multiplayer`; that campaign connects to
`ws://127.0.0.1:8080`. Other campaigns continue to use the in-process server.
Command line options: `--campaign ID`, `--location ID`, `--campaigns PATH`
(default `resources/config/campaigns.json`), `--address IP` (default `0.0.0.0`),
`--port N` (default `8080`), `--ticks N` and `--no-sleep` (smoke tests).
The server sends one state snapshot when a player connects, then broadcasts one
numbered command bundle every 100 ms. Bundles may be empty. Destination input is
scheduled two ticks (200 ms) in the future. Tick IDs, rather than server or
client wall-clock values, define simulation time.
If `--location` is omitted, the campaign's configured start location is used.
`--campaigns PATH` selects a different campaign registry. Content paths are
resolved from the process working directory, so run from the repository root
when using the bundled `resources/` tree.
## Production deployment (Ubuntu, systemd + nginx)
Available startup options:
The server listens on loopback only (`127.0.0.1:8081`); nginx terminates TLS and
proxies WebSocket traffic.
```text
--campaign ID
--location ID
--campaigns PATH
--address IP
--port N
--tick-ms 100
--ticks N
--no-sleep
### 1. systemd service
```bash
sudo cp server/deploy/shadow-gameserver.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now shadow-gameserver
systemctl status shadow-gameserver
journalctl -u shadow-gameserver -f # logs
```
Network mode requires a 100 ms tick; another `--tick-ms` value is rejected.
The selected campaign's `networkPlayerSlots` lists pre-existing character IDs
assigned to connections in order. `networkCompatibility` is a release token
that must be changed whenever deterministic content changes; clients reject a
different token. The bundled multiplayer Battle Zone has two slots (`player`
and `player2`) and rejects additional clients. Disconnecting
releases the slot. The bot's random autonomous controller is disabled in
multiplayer.
The unit caps memory at 512 MB and restarts on failure. After rebuilding run
`sudo systemctl restart shadow-gameserver`. Edit `User=`/paths in the unit if the
repository lives elsewhere. To run a second server, copy the unit with another
`PORT` (and campaign) and add a matching `/server002/` location.
The graphical client executes presentation behavior after building its runtime
`Location`; the headless content pass loads authoritative navigation, NPC,
interactive-object, trigger, teleport, taxi, and animation metadata. Lua and
dialogue-driven shared multiplayer behavior is not yet supported.
### 2. nginx
## Tests
```bash
sudo cp server/deploy/nginx-api.shadowoverbishkek.com.conf \
/etc/nginx/sites-available/api.shadowoverbishkek.com
sudo ln -s /etc/nginx/sites-available/api.shadowoverbishkek.com \
/etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
```
CTest covers:
`location /server001/` proxies to `http://127.0.0.1:8081/` with WebSocket
upgrade headers and a 1 hour read/send timeout (the trailing slashes strip the
`/server001` prefix).
- repeated `LocationGameState::setup()` preserving mutable runtime state;
- ticking a prepared location with zero players;
- deterministic fixed-100 ms replay behavior;
- protocol-v2 parsing, validation, target normalization, state hashing, and
two-tick command ordering;
- a WebSocket initial-state/slot/destination/tick integration test;
- idle server startup;
- headless loading of `battle_zone_multiplayer/uni_exterior`.
### 3. HTTPS certificate
The lockstep implementation currently targets identical executable and content
builds. Diagnostic state hashes detect divergence, but there is no automatic
checkpoint resynchronization or reconnect input history yet.
DNS for `api.shadowoverbishkek.com` must point at the VDS first.
```bash
sudo certbot --nginx -d api.shadowoverbishkek.com
```
Certbot issues a Let's Encrypt certificate, adds the `443 ssl` block and an HTTP
to HTTPS redirect, and installs auto-renewal (`systemctl list-timers | grep certbot`).
## Connecting
Public endpoint: **`wss://api.shadowoverbishkek.com/server001/`**
Handshake check from any machine (expects `HTTP/1.1 101 Switching Protocols`):
```bash
curl -i -N --max-time 3 \
-H "Connection: Upgrade" -H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" -H "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==" \
https://api.shadowoverbishkek.com/server001/
```
Or interactively: `npx wscat -c wss://api.shadowoverbishkek.com/server001/`.
Locally on the VDS, bypassing nginx: `ws://127.0.0.1:8081`.
> **Client limitation:** `src/game_server/WebsocketGameClient.cpp` currently
> speaks plain `ws://host:port` to `/` (no TLS, no path). Until TLS and path
> support are added to the client, the graphical game can only connect directly
> to a non-proxied server (e.g. `ws://127.0.0.1:8080`), not to the `wss://` URL.
## Troubleshooting
- `Animation metadata failed ... Invalid animation file`: the `.anim` files are
Git LFS pointer stubs (`version https://git-lfs...`). Fetch the real files with
`git lfs pull` (or copy `resources/` from a full checkout) and rebuild.
- `GameServer.LocationState` test currently fails on every platform: it expects
`battle_zone` and `battle_zone_multiplayer` to share a start-state file, but
`campaigns.json` points them to different files.
- `Server content setup failed`: the server was not started from the directory
containing `resources/` (use `run-server.sh`).
- `WebSocket listener setup failed`: port already in use (`ss -ltnp | grep 8081`).
- `502 Bad Gateway` from nginx: service not running (`systemctl status
shadow-gameserver`).
- Connection drops after ~60 s: check `proxy_read_timeout` in the nginx location.
- Compiler "Killed" / `internal compiler error`: out of memory, see the memory note.