Standalone server: stream map commands only to relevant players#868
Standalone server: stream map commands only to relevant players#868
Conversation
Introduce a conservative standalone-only map streaming mode. Map-scoped commands are filtered to players currently on that map only when running under the standalone server and when the map exists in the server snapshot data. When a player reports a map switch, the standalone server sends a map resync payload for the destination map so the client reloads the latest snapshot for that map. Non-standalone hosting keeps the previous broadcast behavior unchanged. Maps without snapshot data also fall back to the old broadcast path to avoid breaking maps created after the last joinpoint.
There was a problem hiding this comment.
Pull request overview
Adds standalone-dedicated-server-only support for “map streaming” by sending map-scoped Server_Command packets only to players currently on that map, and resyncing a player’s destination map snapshot on PlayerCount map switches.
Changes:
- Introduce a standalone-only gate (
MultiplayerServer.IsStandaloneServer) and helper APIs to enable map streaming + per-player map resync (SendMapResponse). - Filter map-scoped command broadcasting in
CommandHandler.Sendto only players on the relevant map (with a conservative fallback to broadcast). - Add tests + a test connection implementation to verify standalone filtering and map-switch resync behavior.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| Source/Tests/StandaloneMapStreamingTest.cs | New tests covering standalone filtering, non-standalone broadcast behavior, and map-switch resync. |
| Source/Tests/Helper/RecordingConnection.cs | New test connection that records sent packet IDs. |
| Source/Server/Server.cs | Sets IsStandaloneServer = true for the standalone dedicated server entrypoint. |
| Source/Common/ServerPlayer.cs | Adds hasReportedCurrentMap tracking for per-player map filtering behavior. |
| Source/Common/Networking/State/ServerPlayingState.cs | Updates player map tracking on PlayerCount and triggers map resync when applicable. |
| Source/Common/MultiplayerServer.cs | Adds IsStandaloneServer, CanUseStandaloneMapStreaming, and SendMapResponse. |
| Source/Common/CommandHandler.cs | Implements map-scoped filtering for Server_Command in standalone mode. |
| Source/Client/Networking/State/ClientPlayingState.cs | Reloads the destination map from the updated snapshot when Server_MapResponse arrives. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| OnMainThread.Enqueue(() => | ||
| { | ||
| var mapsToLoad = Find.Maps.Select(m => m.uniqueID).Append(mapId).Distinct().ToList(); | ||
| Loader.ReloadGame(mapsToLoad, false, Multiplayer.game?.gameComp.asyncTime ?? false); | ||
| }); | ||
| } |
There was a problem hiding this comment.
Server_MapResponse is sent via SendFragmented, but the client handler is declared with [PacketHandler(Packets.Server_MapResponse)] (no allowFragmented: true). If the payload exceeds MaxFragmentPacketSize (likely for map snapshots), the receiver will throw Packet ... can't be fragmented. Mark the handler as allowing fragmented packets (or add an explicit fragmented handler) so standalone resync works for real map sizes.
When a standalone-server client is not inside a concrete map yet (world map or no active map), map-scoped live commands should keep using the previous broadcast-like behavior for that player. This avoids starving world-view users of updates until they enter a specific map. Players who are inside a concrete map still keep the per-map filtering.
Scope
This change is intentionally limited to the standalone dedicated server path. Local hosting and non-standalone modes keep the previous broadcast behavior.
What changed
Validation