extra services

This commit is contained in:
2026-05-26 23:14:19 -04:00
parent 1f5aa7b79c
commit 2c644f02f0
25 changed files with 1113 additions and 16 deletions
+9 -1
View File
@@ -10,7 +10,11 @@
{
"server": {
"id": null,
"role": "lobby"
"role": "lobby",
"public_address": null,
"public_port": null,
"lobby_role": "lobby",
"lobby_role_prefix": "lobby_"
},
"redis": {
"url": null,
@@ -59,6 +63,10 @@
|---|---|---|
| `id` | `null` | Optional fixed server id. If null, auto-generated as `<role>-<8 hex chars>` (e.g. `lobby-3f2a91bd`) on every boot. Set this when you want a stable id across restarts. |
| `role` | `"lobby"` | Logical role of this server. Used to filter via `ServerRegistry.getServersByRole(...)`. Examples: `lobby`, `bridge_duel`, `arena`. Use snake_case. |
| `public_address` | `null` | Hostname/IP other servers will hand to players when transferring them HERE via `/send` / `/hub`. Null = auto-detect system hostname. **Set this explicitly on hosted setups** where auto-detect gives the wrong answer (typically `127.0.0.1` or a private LAN name). |
| `public_port` | `null` | Port for the same. Null = Hytale's `DEFAULT_PORT` (currently 5520). |
| `lobby_role` | `"lobby"` | Role string `/hub` and shutdown handoff look for first. |
| `lobby_role_prefix` | `"lobby_"` | If no server matches `lobby_role`, accept any role starting with this prefix (e.g. `lobby_eu`, `lobby_na`). |
### redis
+6
View File
@@ -13,6 +13,12 @@ Every subcommand is gated by a permission node, checked via Hytale's `Permission
| `/networkcore status` | `networkcore.status` |
| `/networkcore servers` | `networkcore.servers` |
| `/networkcore publish` | `networkcore.publish` |
| `/alert <message>` | `networkcore.alert` |
| `/glist` | `networkcore.glist` |
| `/hub` | `networkcore.hub` |
| `/send <player> <id\|role>` | `networkcore.send` |
The four "BungeeCord-style" commands at the bottom are documented in detail in [11_Network_Features.md](11_Network_Features.md).
You can grant the whole namespace with `networkcore.*` (Hytale's permission system handles wildcards). A player without the relevant node sees Hytale's standard `You do not have permission for this command.` message - the command system itself blocks execution before our code runs.
+85
View File
@@ -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()`.
+1
View File
@@ -16,6 +16,7 @@ If you're building a plugin that needs to talk to other servers, record stats, o
8. [08_Commands.md](08_Commands.md) - the /networkcore admin command
9. [09_Operations_And_Security.md](09_Operations_And_Security.md) - auth token, what's not secured, ops checks
10. [10_Database.md](10_Database.md) - shared MySQL pool (HikariCP)
11. [11_Network_Features.md](11_Network_Features.md) - /alert, /glist, /hub, /send + cross-server player movement
## Quick reference