From 792d3818be49d162c2ae4f9e8316009654ab9c26 Mon Sep 17 00:00:00 2001 From: MhaWay Date: Thu, 4 Jun 2026 18:42:01 +0200 Subject: [PATCH 1/2] Fix standalone join-point creation on join --- .../Networking/State/ServerJoiningState.cs | 15 +++++++++++---- Source/Tests/StandaloneMapStreamingTest.cs | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/Source/Common/Networking/State/ServerJoiningState.cs b/Source/Common/Networking/State/ServerJoiningState.cs index c97410d1..48620157 100644 --- a/Source/Common/Networking/State/ServerJoiningState.cs +++ b/Source/Common/Networking/State/ServerJoiningState.cs @@ -29,10 +29,7 @@ protected override async Task RunState() if (Server.settings.pauseOnJoin) Server.commands.PauseAll(); - // On standalone, only request a fresh join point when another player is already active. - // For the normal first join, serve the persisted state immediately instead of blocking on WaitJoinPoint. - if ((Server.IsStandaloneServer && Server.PlayingPlayers.Any()) || - (!Server.IsStandaloneServer && Server.settings.autoJoinPoint.HasFlag(AutoJoinPointFlags.Join))) + if (ShouldCreateJoinPointOnJoin(Server)) Server.worldData.TryStartJoinPointCreation(sourcePlayer: Player); Server.playerManager.OnJoin(Player); @@ -43,6 +40,16 @@ protected override async Task RunState() connection.ChangeState(ConnectionStateEnum.ServerLoading); } + public static bool ShouldCreateJoinPointOnJoin(MultiplayerServer server) + { + // Standalone joins always consume the persisted world state. Fresh join points are created + // by players already inside the game during save/refresh flows, not by the entering client. + if (server.IsStandaloneServer) + return false; + + return server.settings.autoJoinPoint.HasFlag(AutoJoinPointFlags.Join); + } + private void HandleProtocol(ClientProtocolPacket packet) { if (packet.protocolVersion != MpVersion.Protocol) diff --git a/Source/Tests/StandaloneMapStreamingTest.cs b/Source/Tests/StandaloneMapStreamingTest.cs index 6676875b..1d1f27ab 100644 --- a/Source/Tests/StandaloneMapStreamingTest.cs +++ b/Source/Tests/StandaloneMapStreamingTest.cs @@ -141,6 +141,24 @@ public void MapToMapTransition_DoesNotSendMapResponseWhenStreamingDisabled() Assert.That(conn.SentPackets, Does.Not.Contain(Packets.Server_MapResponse)); } + [Test] + public void StandaloneJoin_DoesNotCreateJoinPoint_WhenAnotherPlayerIsAlreadyActive() + { + AddPlayer("existing", 1); + + Assert.That(ServerJoiningState.ShouldCreateJoinPointOnJoin(server), Is.False); + } + + [Test] + public void StandaloneJoin_DoesNotCreateJoinPoint_WhenStreamingIsEnabled() + { + server.settings.multifaction = true; + server.settings.asyncTime = true; + AddPlayer("existing", 1); + + Assert.That(ServerJoiningState.ShouldCreateJoinPointOnJoin(server), Is.False); + } + [Test] public void HandleDebug_IgnoredWhenDevModeDisabled() { From 80c77ce37d61839edb93b5721ea2b9400593ed6e Mon Sep 17 00:00:00 2001 From: MhaWay Date: Fri, 5 Jun 2026 13:02:11 +0200 Subject: [PATCH 2/2] Address review feedback on standalone join fix --- .../Networking/State/ServerJoiningState.cs | 12 +------- Source/Tests/ServerTest.cs | 29 +++++++++++++++++++ Source/Tests/StandaloneMapStreamingTest.cs | 18 ------------ 3 files changed, 30 insertions(+), 29 deletions(-) diff --git a/Source/Common/Networking/State/ServerJoiningState.cs b/Source/Common/Networking/State/ServerJoiningState.cs index 48620157..b2d2a392 100644 --- a/Source/Common/Networking/State/ServerJoiningState.cs +++ b/Source/Common/Networking/State/ServerJoiningState.cs @@ -29,7 +29,7 @@ protected override async Task RunState() if (Server.settings.pauseOnJoin) Server.commands.PauseAll(); - if (ShouldCreateJoinPointOnJoin(Server)) + if (!Server.IsStandaloneServer && Server.settings.autoJoinPoint.HasFlag(AutoJoinPointFlags.Join)) Server.worldData.TryStartJoinPointCreation(sourcePlayer: Player); Server.playerManager.OnJoin(Player); @@ -40,16 +40,6 @@ protected override async Task RunState() connection.ChangeState(ConnectionStateEnum.ServerLoading); } - public static bool ShouldCreateJoinPointOnJoin(MultiplayerServer server) - { - // Standalone joins always consume the persisted world state. Fresh join points are created - // by players already inside the game during save/refresh flows, not by the entering client. - if (server.IsStandaloneServer) - return false; - - return server.settings.autoJoinPoint.HasFlag(AutoJoinPointFlags.Join); - } - private void HandleProtocol(ClientProtocolPacket packet) { if (packet.protocolVersion != MpVersion.Protocol) diff --git a/Source/Tests/ServerTest.cs b/Source/Tests/ServerTest.cs index 5727e2c1..4cfff8bd 100644 --- a/Source/Tests/ServerTest.cs +++ b/Source/Tests/ServerTest.cs @@ -96,6 +96,35 @@ public void LoadingStateHandlesKeepAliveWhileWaitingForJoinPoint() } } + [Test] + public void StandaloneJoinWithExistingPlayer_DoesNotStartJoinPoint() + { + var server = MakeServer(out var port); + server.IsStandaloneServer = true; + + var existingConn = new RecordingConnection("existing"); + existingConn.ChangeState(ConnectionStateEnum.ServerPlaying); + var existingPlayer = new ServerPlayer(100, existingConn); + existingConn.serverPlayer = existingPlayer; + server.playerManager.Players.Add(existingPlayer); + + ConnectClient(port, typeof(TestJoiningState)); + + var timeoutWatch = Stopwatch.StartNew(); + while (true) + { + if (server.playerManager.Players.Count == 1) + break; + + if (timeoutWatch.ElapsedMilliseconds > 2000) + Assert.Fail("Timeout"); + + Thread.Sleep(50); + } + + Assert.That(server.worldData.CreatingJoinPoint, Is.False); + } + private void ConnectClient(int port, Type joiningStateType) { var clientListener = new TestNetListener(joiningStateType); diff --git a/Source/Tests/StandaloneMapStreamingTest.cs b/Source/Tests/StandaloneMapStreamingTest.cs index 1d1f27ab..6676875b 100644 --- a/Source/Tests/StandaloneMapStreamingTest.cs +++ b/Source/Tests/StandaloneMapStreamingTest.cs @@ -141,24 +141,6 @@ public void MapToMapTransition_DoesNotSendMapResponseWhenStreamingDisabled() Assert.That(conn.SentPackets, Does.Not.Contain(Packets.Server_MapResponse)); } - [Test] - public void StandaloneJoin_DoesNotCreateJoinPoint_WhenAnotherPlayerIsAlreadyActive() - { - AddPlayer("existing", 1); - - Assert.That(ServerJoiningState.ShouldCreateJoinPointOnJoin(server), Is.False); - } - - [Test] - public void StandaloneJoin_DoesNotCreateJoinPoint_WhenStreamingIsEnabled() - { - server.settings.multifaction = true; - server.settings.asyncTime = true; - AddPlayer("existing", 1); - - Assert.That(ServerJoiningState.ShouldCreateJoinPointOnJoin(server), Is.False); - } - [Test] public void HandleDebug_IgnoredWhenDevModeDisabled() {