This commit is contained in:
Bandwidth
2026-04-26 20:49:32 +02:00
parent e0b808faed
commit d886f97e14
66 changed files with 8327 additions and 933 deletions

View File

@@ -132,7 +132,20 @@ public class GameClient : IDisposable
return false;
}
public void Disconnect(string reason = "User disconnected")
/// <summary>
/// Tears down the socket and crypto session. When `transient` is true
/// (network drop, decrypt-failure cascade, anything we expect to retry),
/// the lobby/role/task/state caches are preserved so the post-reconnect
/// flow can re-associate via Reconnect(LobbyId). Default false matches
/// pre-P9 behavior (full state wipe) for explicit user disconnects.
///
/// Critical for the P9 reconnect bug: previously every Disconnect path
/// nuked LobbyId, so by the time GameManager_Network's reconnect coroutine
/// fired, the client had no idea which lobby it had been in - the
/// post-handshake Reconnect call had nothing to send and the server
/// answered the next vote/action with NOT_IN_LOBBY.
/// </summary>
public void Disconnect(string reason = "User disconnected", bool transient = false)
{
_cts?.Cancel();
_tcpClient?.Close();
@@ -140,15 +153,22 @@ public class GameClient : IDisposable
_stream = null;
_encryption?.Dispose();
_encryption = null;
LobbyId = null;
JoinCode = null;
CurrentLobbyState = null;
MyRole = null;
MyTasks.Clear();
PlayerPositions.Clear();
Bodies.Clear();
if (!transient)
{
LobbyId = null;
JoinCode = null;
CurrentLobbyState = null;
MyRole = null;
MyTasks.Clear();
PlayerPositions.Clear();
Bodies.Clear();
}
// PlayerPositions are stale anyway after a drop, but we keep them so
// the UI doesn't blink avatars off-map mid-meeting; the next position
// broadcast overwrites them. LastEventId is intentionally preserved
// so the Reconnect message can ask the server for missed events.
Dispatcher.Post(() => OnDisconnected?.Invoke(reason));
}
@@ -236,7 +256,8 @@ public class GameClient : IDisposable
decryptFailures++;
if (decryptFailures >= 3)
{
Disconnect("Too many decryption failures");
// Transient: keep LobbyId for the reconnect coroutine.
Disconnect("Too many decryption failures", transient: true);
return;
}
continue;
@@ -253,7 +274,9 @@ public class GameClient : IDisposable
}
catch (Exception ex) when (!ct.IsCancellationRequested)
{
Disconnect($"Connection error: {ex.Message}");
// Transient: TCP RST / read failure is exactly what reconnect was
// designed for. Keep LobbyId so post-reconnect flow can re-attach.
Disconnect($"Connection error: {ex.Message}", transient: true);
}
}
@@ -508,7 +531,7 @@ public class GameClient : IDisposable
#region Game Actions
public void CreateLobby(Position? center = null, int impostorCount = 1, int taskCount = 5, string? password = null, double playAreaRadius = 500)
public void CreateLobby(Position? center = null, int impostorCount = 1, int taskCount = 5, string? password = null, double playAreaRadius = 500, GameSettingsOverrides? settings = null)
{
Send(new CreateLobby
{
@@ -516,7 +539,8 @@ public class GameClient : IDisposable
PlayAreaRadius = playAreaRadius,
ImpostorCount = impostorCount,
TaskCount = taskCount,
Password = password
Password = password,
Settings = settings
});
}