Merge branch 'develop' into feature/clear-embedded-chip
# Conflicts: # Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs # Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs
This commit is contained in:
@@ -14,6 +14,7 @@ using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BrewMonster.Scripts.Task;
|
||||
using BrewMonster.UI;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
@@ -24,6 +25,8 @@ namespace BrewMonster.Network
|
||||
// 2. Login
|
||||
public class UnityGameSession : MonoSingleton<UnityGameSession>
|
||||
{
|
||||
private const string WorldSceneName = "a61";
|
||||
private const string LoginSceneName = "LoginScene";
|
||||
private GameSession _gameSession;
|
||||
|
||||
private bool _isInitialized = false;
|
||||
@@ -82,6 +85,28 @@ namespace BrewMonster.Network
|
||||
Instance._ip = ip;
|
||||
Instance._port = port;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Origin-like: In-game can only return to Select Role (LOGOUT(1)).
|
||||
/// This will load LoginScene and auto-login (using saved creds) to show the Select Role UI.
|
||||
/// </summary>
|
||||
public static void ReturnToSelectRole()
|
||||
{
|
||||
if (Instance == null) return;
|
||||
// Origin-like: in-game only returns to Select Role (auto-login using saved creds).
|
||||
// Keep world scene loaded (a61) but cleaned.
|
||||
_ = Instance.LogoutAndReturnAsync(outType: 1, entryTarget: LogoutFlowState.LoginEntryTarget.SelectRole, clearSavedCreds: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Origin-like: Account logout from Select Role (LOGOUT(0)).
|
||||
/// This will clear saved creds and load LoginScene showing username/password UI.
|
||||
/// </summary>
|
||||
public static void LogoutAccount()
|
||||
{
|
||||
if (Instance == null) return;
|
||||
_ = Instance.LogoutAndReturnAsync(outType: 0, entryTarget: LogoutFlowState.LoginEntryTarget.LoginUI, clearSavedCreds: true);
|
||||
}
|
||||
public static void c2s_CmdCastSkill(int idSkill, byte byPVPMask, int iNumTarget, int[] aTargets)
|
||||
{
|
||||
Instance._gameSession.CmdCache.SendCmdCastSkill(idSkill, byPVPMask, iNumTarget, aTargets);
|
||||
@@ -146,6 +171,166 @@ namespace BrewMonster.Network
|
||||
|
||||
DontDestroyOnLoad(gameObject);
|
||||
}
|
||||
|
||||
private async Task LogoutAndReturnAsync(int outType, LogoutFlowState.LoginEntryTarget entryTarget, bool clearSavedCreds)
|
||||
{
|
||||
// Tell LoginScene what to show next.
|
||||
LogoutFlowState.NextLoginEntry = entryTarget;
|
||||
|
||||
if (clearSavedCreds)
|
||||
{
|
||||
PlayerPrefs.DeleteKey("username");
|
||||
PlayerPrefs.DeleteKey("password");
|
||||
PlayerPrefs.Save();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (_gameSession != null && _gameSession.IsConnected)
|
||||
{
|
||||
// Send LOGOUT(outType) like the original client.
|
||||
_gameSession.SendPlayerLogout(outType);
|
||||
|
||||
// Wait briefly for server-driven disconnect.
|
||||
await WaitForDisconnectAsync(timeoutMs: 4000);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
BMLogger.LogError($"LogoutAndReturnAsync exception: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Fallback: if server didn't close, close locally.
|
||||
if (_gameSession != null && _gameSession.IsConnected)
|
||||
{
|
||||
_gameSession.Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
// Return to LoginScene.
|
||||
// IMPORTANT: for outType=1 we must keep the world scene (a61) loaded; only "clean" runtime objects.
|
||||
await Task.Yield();
|
||||
// Requirement: even on account logout, keep a61 loaded (do not delete the world scene),
|
||||
// just clean runtime objects and show LoginScene UI.
|
||||
CleanRuntimeObjectsKeepWorld();
|
||||
await EnsureSceneLoadedAdditiveAsync(WorldSceneName);
|
||||
await EnsureLoginSceneAdditiveAndActivateAsync(keepSceneName: WorldSceneName);
|
||||
|
||||
// When LoginScene is already loaded additively, Start() won't re-run.
|
||||
// Force the login UI to refresh to the correct entry state immediately.
|
||||
ApplyLoginEntryToUI(entryTarget);
|
||||
}
|
||||
|
||||
private static void ApplyLoginEntryToUI(LogoutFlowState.LoginEntryTarget entryTarget)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Find even if inactive (Resources.FindObjectsOfTypeAll returns inactive objects too).
|
||||
var all = Resources.FindObjectsOfTypeAll<LoginScreenUI>();
|
||||
for (int i = 0; i < all.Length; i++)
|
||||
{
|
||||
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);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
BMLogger.LogError($"ApplyLoginEntryToUI error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void CleanRuntimeObjectsKeepWorld()
|
||||
{
|
||||
// Spawned runtime objects (player/monsters/vfx) are parented under ObjectSpawner.
|
||||
// Destroying them "cleans" the world without unloading the scene (a61 remains loaded).
|
||||
try
|
||||
{
|
||||
var spawner = BrewMonster.ObjectSpawner.Instance;
|
||||
if (spawner == null) return;
|
||||
|
||||
var root = spawner.transform;
|
||||
for (int i = root.childCount - 1; i >= 0; i--)
|
||||
{
|
||||
var child = root.GetChild(i);
|
||||
if (child != null)
|
||||
UnityEngine.Object.Destroy(child.gameObject);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
BMLogger.LogError($"CleanRuntimeObjectsKeepWorld error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task EnsureSceneLoadedAdditiveAsync(string sceneName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(sceneName)) return;
|
||||
var s = SceneManager.GetSceneByName(sceneName);
|
||||
if (s.IsValid() && s.isLoaded) return;
|
||||
|
||||
var op = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);
|
||||
while (op != null && !op.isDone)
|
||||
await Task.Yield();
|
||||
}
|
||||
|
||||
private static async Task EnsureLoginSceneAdditiveAndActivateAsync(string keepSceneName)
|
||||
{
|
||||
// Load LoginScene additively if needed (do not unload keepSceneName, e.g., a61).
|
||||
var loginScene = SceneManager.GetSceneByName(LoginSceneName);
|
||||
if (!loginScene.IsValid() || !loginScene.isLoaded)
|
||||
{
|
||||
var op = SceneManager.LoadSceneAsync(LoginSceneName, LoadSceneMode.Additive);
|
||||
while (op != null && !op.isDone)
|
||||
await Task.Yield();
|
||||
loginScene = SceneManager.GetSceneByName(LoginSceneName);
|
||||
}
|
||||
|
||||
if (loginScene.IsValid() && loginScene.isLoaded)
|
||||
{
|
||||
SceneManager.SetActiveScene(loginScene);
|
||||
}
|
||||
|
||||
// Optionally unload other non-world scenes (keep a61 + LoginScene).
|
||||
// This prevents extra scenes like NPCRender staying around.
|
||||
for (int i = 0; i < SceneManager.sceneCount; i++)
|
||||
{
|
||||
var s = SceneManager.GetSceneAt(i);
|
||||
if (!s.IsValid() || !s.isLoaded) continue;
|
||||
if (s.name == LoginSceneName) continue;
|
||||
if (!string.IsNullOrEmpty(keepSceneName) && s.name == keepSceneName) continue;
|
||||
|
||||
// Only unload if it's not the active scene (we already switched active to LoginScene above).
|
||||
if (SceneManager.GetActiveScene() == s) continue;
|
||||
_ = SceneManager.UnloadSceneAsync(s);
|
||||
}
|
||||
}
|
||||
|
||||
private Task WaitForDisconnectAsync(int timeoutMs)
|
||||
{
|
||||
if (_gameSession == null || !_gameSession.IsConnected)
|
||||
return Task.CompletedTask;
|
||||
|
||||
var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
void OnDisc() => tcs.TrySetResult(true);
|
||||
_gameSession.Disconnected += OnDisc;
|
||||
|
||||
return Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.WhenAny(tcs.Task, Task.Delay(timeoutMs));
|
||||
}
|
||||
finally
|
||||
{
|
||||
_gameSession.Disconnected -= OnDisc;
|
||||
}
|
||||
});
|
||||
}
|
||||
public RoleInfo GetRoleInfo()
|
||||
{
|
||||
return _gameSession.GetRoleInfo();
|
||||
@@ -486,10 +671,6 @@ namespace BrewMonster.Network
|
||||
{
|
||||
Instance._gameSession.c2s_SendCmdGivePresent(roleid, mail_id, goods_id, goods_index, goods_slot);
|
||||
}
|
||||
public static void c2s_CmdNPCSevClearEmbeddedChip(int iEquipIdx, int tidEquip)
|
||||
{
|
||||
Instance._gameSession.c2s_CmdNPCSevClearEmbeddedChip(iEquipIdx, tidEquip);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
@@ -504,4 +685,33 @@ namespace BrewMonster.Network
|
||||
Instance._gameSession.c2s_SendCmdGetItemInfo(byPackage, bySlot);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Small cross-scene state to tell LoginScene what to show after a logout flow.
|
||||
/// This mirrors the original client: in-game can only return to Select Role; Account Logout happens from Select Role.
|
||||
/// </summary>
|
||||
public static class LogoutFlowState
|
||||
{
|
||||
public enum LoginEntryTarget
|
||||
{
|
||||
LoginUI = 0,
|
||||
SelectRole = 1,
|
||||
}
|
||||
|
||||
private static LoginEntryTarget _nextEntry = LoginEntryTarget.LoginUI;
|
||||
|
||||
public static LoginEntryTarget NextLoginEntry
|
||||
{
|
||||
get => _nextEntry;
|
||||
set => _nextEntry = value;
|
||||
}
|
||||
|
||||
/// <summary>Consume and reset to default (LoginUI).</summary>
|
||||
public static LoginEntryTarget ConsumeNextLoginEntry()
|
||||
{
|
||||
var v = _nextEntry;
|
||||
_nextEntry = LoginEntryTarget.LoginUI;
|
||||
return v;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user