diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs b/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs index 3e88367263..72dd6cb0ec 100644 --- a/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs +++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs @@ -52,6 +52,9 @@ namespace CSNetwork private Action _createRoleCallback; private RoleInfo _selectedRole; public bool IsConnected => _networkManager?.IsConnected ?? false; + // When true, suppress *gameplay traffic* (mostly gamedatasend C2S commands) during logout/scene transitions. + // We still allow account/role flow protocols like rolelist/selectrole so "Return to Select Role" can work. + private volatile bool _suppressGameplayTraffic = false; public static SynchronizationContext Context; private CECC2SCmdCache m_CmdCache; // C2S command cache @@ -103,6 +106,17 @@ namespace CSNetwork #endif public CECC2SCmdCache CmdCache { get => m_CmdCache; } + public bool IsGameplayTrafficSuppressed => _suppressGameplayTraffic; + + public void SetGameplayTrafficSuppressed(bool suppressed, bool clearCachedCmds = true) + { + _suppressGameplayTraffic = suppressed; + if (suppressed && clearCachedCmds) + { + try { m_CmdCache?.RemoveAllCmds(); } catch { /* best-effort */ } + } + } + public GameSession() { @@ -551,6 +565,15 @@ namespace CSNetwork // --- Protocol Sending --- public void SendProtocol(Protocol protocol, Action complete = null) { + // During logout/role-select transitions, drop background gameplay commands (gamedatasend). + // Do NOT block rolelist/selectrole/login related protocols. + if (_suppressGameplayTraffic && protocol is gamedatasend) + { + _logger.Log(LogType.Warning, + $"Suppress outgoing: dropped {protocol?.GetType().Name ?? "null"} ({protocol?.GetPType().ToString() ?? "?"})"); + return; + } + if (IsConnected) { _logger.Log(LogType.Debug, @@ -1032,8 +1055,9 @@ namespace CSNetwork int cashAmount = cashData.cash_amount; PostToUnityContext(() => ShopUIManager.CacheCash(cashAmount)); } - catch (Exception ex) + catch (Exception) { + // Ignore parse failures for optional cash payload. } } diff --git a/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs b/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs index c495060997..9136eee74d 100644 --- a/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs +++ b/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs @@ -38,6 +38,8 @@ namespace BrewMonster.Network private string _username = ""; private string _password = ""; private bool _isIntentionalDisconnect = false; + // When true, prevent all outgoing protocols (background ticks, tasks, etc.) from sending. + private bool _suppressOutgoing = false; CECStubbornFactionInfoSender m_stubbornFactionInfoSender; public GameSession GameSession { get => _gameSession; } @@ -149,8 +151,23 @@ namespace BrewMonster.Network return; } + // New login session: allow outgoing again. + Instance.SetSuppressOutgoing(false); Instance._gameSession.LoginAsync(username, password, onLoginComplete); } + + private void SetSuppressOutgoing(bool suppressed) + { + _suppressOutgoing = suppressed; + try + { + _gameSession?.SetGameplayTrafficSuppressed(suppressed, clearCachedCmds: true); + } + catch (Exception ex) + { + BMLogger.LogError($"SetSuppressOutgoing failed: {ex.Message}"); + } + } public void c2s_SendCmdStopMove(in Vector3 vDest, float fSpeed, int iMoveMode, byte byDir, ushort wStamp, int iTime) { @@ -191,6 +208,9 @@ namespace BrewMonster.Network { // Tell LoginScene what to show next. LogoutFlowState.NextLoginEntry = entryTarget; + + // Immediately suppress outgoing protocols so background systems can't send after LOGOUT begins. + SetSuppressOutgoing(true); _gameSession.Disconnected -= OnUnexpectedDisconnect; _gameSession.FriendRequestReceived -= OnFriendRequestReceived; _gameSession.AddFriendResultReceived -= OnAddFriendResultReceived; @@ -259,9 +279,9 @@ namespace BrewMonster.Network var ui = all[i]; if (ui == null) continue; if (!ui.gameObject.scene.IsValid() || ui.gameObject.scene.name != LoginSceneName) continue; - // Avoid hard dependency on method existence (merges may edit LoginScreenUI). - // ui.SendMessage("ApplyLoginEntry", entryTarget, SendMessageOptions.DontRequireReceiver); - ui.ApplyLoginEntry( entryTarget ); + // Avoid hard dependency (and potential duplicate symbol ambiguity across merges): + // dispatch by name, do not statically bind here. + ui.SendMessage("ApplyLoginEntry", entryTarget, SendMessageOptions.DontRequireReceiver); return; } } @@ -415,6 +435,9 @@ namespace BrewMonster.Network public static void EnterWorldAsync(RoleInfo roleInfo, Action callback = null) { BMLogger.Log("EnterWorldAsync !!!!! nay "); + // We are about to re-enter world gameplay: re-enable outgoing gameplay traffic + CmdCache tick. + // Without this, ReturnToSelectRole flow leaves suppression on and the player can't play after re-enter. + Instance.SetSuppressOutgoing(false); Instance._gameSession.EnterWorldAsync(roleInfo, callback); } public static void SendChatData(byte cChannel, in string szMsg, int iPack, int iSlot) @@ -733,7 +756,7 @@ namespace BrewMonster.Network actDone?.Invoke(true); } - void OnDestroy() + new void OnDestroy() { // Mark as intentional to prevent showing disconnect message _isIntentionalDisconnect = true; @@ -820,7 +843,9 @@ namespace BrewMonster.Network public void Update() { - _gameSession?.CmdCache?.Tick(Time.deltaTime); + // Don't tick/schedule outgoing C2S while we're in logout/role-select flow. + if (!_suppressOutgoing) + _gameSession?.CmdCache?.Tick(Time.deltaTime); #if UNITY_EDITOR // Debug: Press D to disconnect from server (Editor only)