|
|
|
@@ -0,0 +1,85 @@
|
|
|
|
|
# Network Features
|
|
|
|
|
|
|
|
|
|
BungeeCord-style admin commands and cross-server player movement, built on top of NetworkCore's MessageBus and ServerRegistry.
|
|
|
|
|
|
|
|
|
|
## What's in this layer
|
|
|
|
|
|
|
|
|
|
| Piece | Purpose |
|
|
|
|
|
|---|---|
|
|
|
|
|
| `PlayerPresence` API + `PlayerPresenceService` | Network-wide "who is on which server" tracking. |
|
|
|
|
|
| `RefRequestService` | Cross-server "please send this player to host:port" wrapper around `PlayerRef.referToServer`. |
|
|
|
|
|
| `AlertService` | Cross-server broadcast to all players. |
|
|
|
|
|
| `LobbyPicker` | Finds a referrable lobby (least-loaded), with role-prefix fallback. |
|
|
|
|
|
| `ShutdownHandoff` | On graceful stop, walks local players and refers each to a lobby before Redis closes. |
|
|
|
|
|
| Commands | `/alert`, `/glist`, `/hub`, `/send` |
|
|
|
|
|
|
|
|
|
|
## Commands
|
|
|
|
|
|
|
|
|
|
| Command | Permission | What it does |
|
|
|
|
|
|---|---|---|
|
|
|
|
|
| `/alert <message>` | `networkcore.alert` | Single-token message broadcast to every player on every server. (Multi-word reasons need the UI - see Staff's pattern.) |
|
|
|
|
|
| `/glist` | `networkcore.glist` | Lists every server + players currently on each, from the local `PlayerPresence` cache. |
|
|
|
|
|
| `/hub` | `networkcore.hub` | Refers the caller to a lobby. Picks least-loaded. Errors if no lobby is available. |
|
|
|
|
|
| `/send <player> <serverIdOrRole>` | `networkcore.send` | Refers the named player (online anywhere) to a server. `server` can be a literal server id or a role (picks least-loaded with that role). |
|
|
|
|
|
|
|
|
|
|
## How the transfer actually works
|
|
|
|
|
|
|
|
|
|
Hytale ships `PlayerRef.referToServer(host, port)` which sends a `ClientReferral` packet to the player's client. The client disconnects from the current server and connects to the new host:port. Hytale's own `/refer` command does the same thing for one player at a time on the same server.
|
|
|
|
|
|
|
|
|
|
For a player who's on **this** server, we call `referToServer` directly.
|
|
|
|
|
|
|
|
|
|
For a player on a **different** server, we publish a `RefRequest` on the bus. Every server's `RefRequestService` listens; the one that has the player locally does the call. The other servers ignore the request.
|
|
|
|
|
|
|
|
|
|
This whole flow takes a few hundred ms end to end:
|
|
|
|
|
1. `/send Bob lobby` on server A (Bob is on arena-3) → A publishes RefRequest
|
|
|
|
|
2. arena-3 receives → calls `bob.referToServer("lobby-1.kweebec.com", 5520)`
|
|
|
|
|
3. Bob's client gets the ClientReferral, drops the connection to arena-3, opens a new one to lobby-1
|
|
|
|
|
4. lobby-1's connect flow runs as normal
|
|
|
|
|
|
|
|
|
|
## Public address setup
|
|
|
|
|
|
|
|
|
|
Every server needs to know its own public-facing host/port (what other servers should hand to clients in the referral packet). Configure via `server.public_address` + `server.public_port` in [config.json](03_Configuration.md#server).
|
|
|
|
|
|
|
|
|
|
If null, we auto-detect via Java's `InetAddress.getLocalHost().getHostName()`. On dev boxes that's usually fine. On cloud / hosted setups (Apex etc.) it almost certainly is NOT - you'll get an internal name that clients can't reach. **Set it explicitly.**
|
|
|
|
|
|
|
|
|
|
You can confirm it parsed correctly: boot log line `Public network address resolved to <host>:<port>`. Also `/networkcore servers` shows host/port for each server in the network now.
|
|
|
|
|
|
|
|
|
|
A server with no `host` in its `ServerInfo` (i.e. its heartbeat didn't carry one) is treated as "not referrable" - `/hub` and `/send` will skip it.
|
|
|
|
|
|
|
|
|
|
## Graceful shutdown handoff
|
|
|
|
|
|
|
|
|
|
In `NetworkCore.shutdown()`, before closing Redis, we walk `Universe.getPlayers()` and `referToServer(...)` each one to a lobby (picked via `LobbyPicker`).
|
|
|
|
|
|
|
|
|
|
This means a properly-shut-down server doesn't drop its players - they get teleported away. It happens BEFORE the goodbye announce + Redis close, so the LobbyPicker can still see other servers in the registry.
|
|
|
|
|
|
|
|
|
|
**Ungraceful kills** (kill -9, OOM, network partition, hardware failure) bypass this entirely. Players see Hytale's normal disconnect screen and reconnect manually. This is unavoidable without a proxy in front - there's no way for the dying server to refer players after it's dead.
|
|
|
|
|
|
|
|
|
|
To make the most of this:
|
|
|
|
|
- Use systemd / supervisord / similar to send SIGTERM and wait before SIGKILL. Hytale's shutdown hook handles SIGTERM.
|
|
|
|
|
- Don't `docker kill` containers - `docker stop` gives a grace period.
|
|
|
|
|
- For deploy rollouts, drain (run `/send-all-to lobby` or similar) before shutdown to be doubly sure.
|
|
|
|
|
|
|
|
|
|
## Presence cache
|
|
|
|
|
|
|
|
|
|
`PlayerPresenceService` keeps an in-memory `UUID → PlayerLocation` map. Maintained by:
|
|
|
|
|
|
|
|
|
|
- Local `PlayerConnectEvent` / `PlayerDisconnectEvent` → publish on `network:presence`
|
|
|
|
|
- Remote presence events on the same channel → update local map
|
|
|
|
|
- `ServerLeftNetworkEvent` (server dropped from registry) → purge all players known to be on that server
|
|
|
|
|
|
|
|
|
|
On server boot, we re-publish our local roster so other servers refresh their view of us after our restart. Type: `KIND_SYNC`.
|
|
|
|
|
|
|
|
|
|
The cache is unbounded but bounded in practice by total online player count. No TTL - entries live until a leave event drops them. A lost leave event leaves a stale entry until that player connects somewhere else (which fires a new join) or until the holding server drops from the registry.
|
|
|
|
|
|
|
|
|
|
## What this layer deliberately doesn't do
|
|
|
|
|
|
|
|
|
|
- **Per-world routing.** /hub picks a server, not a world within one. If you want world-aware routing, layer it on top.
|
|
|
|
|
- **Cross-region picking.** No notion of latency / geography. If you need region-aware lobby selection, sort `LobbyPicker` candidates by a region tag and a `getRegion()` method on `ServerInfo`.
|
|
|
|
|
- **Queue handling.** A `/send` to a full server just succeeds in queuing the referral; the destination might reject the connection. No retry, no queue, no "you're #3 in line".
|
|
|
|
|
- **Mid-flight player tracking.** Once we hand off a referral, we don't follow the player until they connect to the destination. There's a window of "between servers" where `presence.getLocation` returns the old server until the new one's connect event fires.
|
|
|
|
|
- **`/glist` filtering by group/role.** Easy follow-up; not in v1.
|
|
|
|
|
- **In-game UI for any of this.** Commands only. Build a HyUI hub later if you want.
|
|
|
|
|
|
|
|
|
|
## Migration note for existing plugins
|
|
|
|
|
|
|
|
|
|
`ServerInfo` got two new fields (`host`, `port`) and the constructor signature changed. Anything depending on the NetworkCore jar (Staff, Ranks) needs to be **rebuilt** against the new jar. Same `NoSuchMethodError` pattern we hit before with `Universe.getPlayers()`.
|