Files
AIC-Project/Client/Assets/Script/xmain/Client/CSharpClientApp.cs
T
2026-07-10 10:24:29 +08:00

1015 lines
35 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using UnityEngine;
using XGame.MiniGame;
using XWorld.Framework.Protocol;
using UObject = UnityEngine.Object;
namespace XGame
{
public sealed class CSharpClientApp : MonoBehaviour
{
private const string LoginTokenKey = "LoginToken";
private const string LoginTokenTimeKey = "LoginTokenTime";
private const string LoginAccountKey = "LoginAccount";
private const string LoginRememberKey = "LoginRememberAccount";
private const string LoginPasswordKey = "LoginPassword";
private const string LoginRememberPasswordKey = "LoginRememberPassword";
private const string LoginAutoKey = "LoginAutoLogin";
private const string LoginAgreementKey = "LoginAgreement";
private const string LoginUserIdKey = "LoginUserId";
private const string LoginGuestIdKey = "LoginGuestId";
private const string LoginPlayerIdKey = "LoginPlayerId";
private const string LocalClientIdKey = "LocalClientId";
private const double TokenValidSeconds = 7 * 24 * 60 * 60;
public static CSharpClientApp Instance { get; private set; }
private AuthRegisterLoginController loginController;
private GameObject loginPanel;
private GameLobbyController lobbyController;
private GameObject lobbyPanel;
private MatchWaitingController waitingController;
private GameObject waitingPanel;
private XWebSocket socket;
private MiniGameNetChannel lobbyNet;
private MiniGameHost gameHost;
private LobbyWorldController lobbyWorld;
private readonly List<GameInfoMsg> games = new List<GameInfoMsg>();
private bool lobbyActive;
private bool openingLogin;
private bool openingLobby;
private bool openingWaiting;
private string matchMessage = string.Empty;
private string pendingGameId = string.Empty;
private int pendingGameVersion;
private float nextLobbyMoveFrameLogTime;
private int m_pid;//当前已鉴权的玩家 id(来自登录/注册返回的 token)
public static IEnumerator Init()
{
CSharpClientApp app = EnsureInstance();
yield return app.StartFlow();
}
public static void RestartCurrent()
{
if (Instance == null)
{
return;
}
Instance.StopFlow();
XWCoroutine.StartCo(Instance.StartFlow());
}
private static CSharpClientApp EnsureInstance()
{
GameObject main = GameObject.Find("Main");
if (main == null)
{
main = new GameObject("Main");
}
CSharpClientApp app = main.GetComponent<CSharpClientApp>();
if (app == null)
{
app = main.AddComponent<CSharpClientApp>();
}
return app;
}
private void Awake()
{
Instance = this;
}
private IEnumerator StartFlow()
{
Debug.Log("[CSharpClientApp] start");
if (HasValidToken())
{
EnterLobby();
yield break;
}
ClearToken();
OpenLoginPanel(string.Empty);
yield break;
}
private void Update()
{
if (lobbyActive)
{
lobbyNet?.Pump();
}
}
private void OnDestroy()
{
StopFlow();
if (Instance == this)
{
Instance = null;
}
}
private void StopFlow()
{
lobbyActive = false;
lobbyWorld?.SendLeave();
lobbyNet = null;
socket?.close();
socket = null;
CloseLoginPanel();
CloseLobby();
CloseWaiting();
}
private void OpenLoginPanel(string reason)
{
if (loginPanel != null || openingLogin)
{
ShowLoginError(reason);
return;
}
openingLogin = true;
XWCoroutine.StartCo(CoOpenLoginPanel(reason));
}
private IEnumerator CoOpenLoginPanel(string reason)
{
UObject loaded = null;
yield return XResLoader.coLoadRes("Assets/Game/Art/UI/Prefab/AuthRegisterLoginView.prefab", typeof(GameObject), obj => loaded = obj);
openingLogin = false;
GameObject asset = loaded as GameObject;
if (asset == null)
{
Debug.LogError("[CSharpClientApp] load login panel failed");
yield break;
}
loginPanel = Instantiate(asset);
AttachToMain(loginPanel);
loginController = loginPanel.GetComponent<AuthRegisterLoginController>();
if (loginController == null)
{
loginController = loginPanel.AddComponent<AuthRegisterLoginController>();
}
loginController.OnLogin = OnLoginClicked;
loginController.OnRegister = OnRegisterClicked;
loginController.OnGuestLogin = OnGuestLoginClicked;
bool rememberPassword = XData.GetInt(LoginRememberPasswordKey, 0) == 1;
string account = (XData.GetInt(LoginRememberKey, 0) == 1 || rememberPassword) ? XData.GetString(LoginAccountKey, "") : "";
string password = rememberPassword ? XData.GetString(LoginPasswordKey, "") : "";
loginController.SetInitialState(account, password, rememberPassword, XData.GetInt(LoginAutoKey, 0) == 1, XData.GetInt(LoginAgreementKey, 0) == 1);
loginController.SetServerInfo("Recommended Server", GlobalData.bLocal ? "Local" : "Online");
loginController.SetVersion("Version 0.1.0");
ShowLoginError(reason);
// 真机上旧 lua(client/login) 仍会再实例化一份 AuthRegisterLoginView 并盖在上层抢点击
// (那份没挂控制器、按钮无监听 → "点登录没反应")。lua 资源(bs.unity3d)热更未必到位,
// 这里在稳定热更的 C# 侧兜底:开面板后一段时间内持续销毁除本面板外的重复登录面板。
StartCoroutine(EnsureSingleLoginPanel(loginPanel));
}
//保证场景中只有 C# 这份(已接好委托)的登录面板存活,销毁其它来源的重复实例。
//用时间窗口轮询是为兼容"重复面板比本面板更晚创建"(lua 与 C# 启动时序不定)的情况。
private IEnumerator EnsureSingleLoginPanel(GameObject keep)
{
float t = 0f;
while (t < 3f && keep != null)
{
var grs = UnityEngine.Object.FindObjectsOfType<UnityEngine.UI.GraphicRaycaster>();
foreach (var g in grs)
{
if (g == null || g.gameObject == keep)
{
continue;
}
if (!g.gameObject.name.Contains("AuthRegisterLoginView"))
{
continue;
}
Debug.Log($"[CSharpClientApp] destroy duplicate login panel {g.gameObject.name} #{g.gameObject.GetInstanceID()}");
Destroy(g.gameObject);
}
t += Time.unscaledDeltaTime;
yield return null;
}
}
private void OnLoginClicked(string account, string password, bool rememberPassword, bool autoLogin)
{
account = account ?? string.Empty;
password = password ?? string.Empty;
if (account.Length == 0)
{
ShowLoginError("Please enter account.");
return;
}
if (password.Length == 0)
{
ShowLoginError("Please enter password.");
return;
}
XData.SetInt(LoginRememberKey, rememberPassword ? 1 : 0);
XData.SetInt(LoginRememberPasswordKey, rememberPassword ? 1 : 0);
XData.SetInt(LoginAutoKey, autoLogin ? 1 : 0);
XData.SetInt(LoginAgreementKey, 1);
if (rememberPassword)
{
XData.SetString(LoginAccountKey, account);
XData.SetString(LoginPasswordKey, password);
}
else
{
XData.DeleteKey(LoginAccountKey);
XData.DeleteKey(LoginPasswordKey);
}
if (GlobalData.bLocal)
{
// 本地/内网 dev:网关未启用鉴权,沿用 ?pid= 路径
SaveSession(account, account);
EnterLobby();
return;
}
ShowLoginError("");
XWCoroutine.StartCo(AuthClient.Login(account, password, (ok, token, pid, err) =>
{
if (!ok) { ShowLoginError(err); return; }
SaveSession(account, account, token, pid);
EnterLobby();
}));
}
private void OnGuestLoginClicked(string account, string password, bool remember, bool autoLogin)
{
string guestId = GetGuestId();
XData.SetInt(LoginAgreementKey, 1);
XData.SetInt(LoginRememberKey, 1);
XData.SetInt(LoginRememberPasswordKey, 0);
XData.SetString(LoginAccountKey, "guest:" + guestId);
XData.DeleteKey(LoginPasswordKey);
SaveSession("guest:" + guestId, guestId);
EnterLobby();
}
private void OnRegisterClicked(string account, string password, string confirmPassword)
{
account = account ?? string.Empty;
password = password ?? string.Empty;
confirmPassword = confirmPassword ?? string.Empty;
if (account.Length == 0)
{
ShowLoginError("Please enter account.");
return;
}
if (password.Length == 0)
{
ShowLoginError("Please enter password.");
return;
}
if (password != confirmPassword)
{
ShowLoginError("Passwords do not match.");
return;
}
XData.SetInt(LoginRememberKey, 1);
XData.SetInt(LoginRememberPasswordKey, 0);
XData.SetInt(LoginAutoKey, 0);
XData.SetInt(LoginAgreementKey, 1);
XData.SetString(LoginAccountKey, account);
XData.DeleteKey(LoginPasswordKey);
if (GlobalData.bLocal)
{
SaveSession(account, account);
EnterLobby();
return;
}
ShowLoginError("");
XWCoroutine.StartCo(AuthClient.Register(account, password, (ok, token, pid, err) =>
{
if (!ok) { ShowLoginError(err); return; }
SaveSession(account, account, token, pid);
EnterLobby();
}));
}
private void EnterLobby()
{
CloseLoginPanel();
OpenLobby();
ConnectLobby();
}
private void OpenLobby()
{
if (lobbyPanel != null || openingLobby)
{
RefreshLobby();
return;
}
openingLobby = true;
XWCoroutine.StartCo(CoOpenLobby());
}
private IEnumerator CoOpenLobby()
{
UObject loaded = null;
yield return XResLoader.coLoadRes("Assets/Game/Art/UI/Prefab/UI_GameLobby.prefab", typeof(GameObject), obj => loaded = obj);
openingLobby = false;
GameObject asset = loaded as GameObject;
if (asset == null)
{
Debug.LogError("[CSharpClientApp] load lobby panel failed");
yield break;
}
lobbyPanel = Instantiate(asset);
AttachToMain(lobbyPanel);
lobbyController = lobbyPanel.GetComponent<GameLobbyController>();
if (lobbyController == null)
{
lobbyController = lobbyPanel.AddComponent<GameLobbyController>();
}
lobbyController.OnMatch = OnMatchClicked;
RefreshLobby();
}
private void ConnectLobby()
{
if (socket != null && socket.IsConected())
{
JoinLobbyWorld();
RequestGameList();
return;
}
string rawNode = GlobalData.CurNode ?? string.Empty;
#if XW_DEVTEST
if (MiniGameTestOverride.IsActive && !string.IsNullOrEmpty(MiniGameTestOverride.GatewayUrl))
{
rawNode = AddDevPid(MiniGameTestOverride.GatewayUrl);
}
#endif
if (rawNode.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) ||
rawNode.StartsWith("wss://", StringComparison.OrdinalIgnoreCase))
{
int localPid = GetLobbyPlayerId();
if (GlobalData.bLocal)
{
// 本地/内网 dev 网关未启用鉴权,只认 ?pid=;每台设备必须使用不同 pid。
rawNode = WithPlayerId(rawNode);
Debug.Log("[CSharpClientApp] local lobby pid=" + localPid + " url=" + rawNode);
}
else
{
string token = !string.IsNullOrEmpty(GlobalData.Token) ? GlobalData.Token : XData.GetString(LoginTokenKey, "");
if (!string.IsNullOrEmpty(token))
{
// 生产:带签名 token 连接(网关验签取权威 pid)
string sep = rawNode.Contains("?") ? "&" : "?";
rawNode = rawNode + sep + "token=" + System.Uri.EscapeDataString(token);
}
else
{
rawNode = WithPlayerId(rawNode);
}
}
SetLobbyStatus("Connecting " + rawNode);
lsocket.connect(rawNode, 0, OnLobbyConnected);
return;
}
string node = NormalizeNode(rawNode, 7777);
SplitHostPort(node, 7777, out string host, out int port);
SetLobbyStatus("Connecting " + host + ":" + port);
lsocket.connect(host, port, OnLobbyConnected);
}
private void OnLobbyConnected(XWebSocket sock, string err)
{
if (!string.IsNullOrEmpty(err) || sock == null)
{
SetLobbyStatus("Network unavailable.");
Debug.LogError("[CSharpClientApp] lobby connect failed: " + err);
return;
}
socket = sock;
lobbyNet = new MiniGameNetChannel(sock);
lobbyNet.OnFrameworkFrame = OnLobbyFrameworkFrame;
lobbyActive = true;
SetLobbyStatus("Lobby connected.");
JoinLobbyWorld();
RequestGameList();
}
private void RequestGameList()
{
if (lobbyNet == null)
{
RefreshLobby();
return;
}
lobbyNet.SendFramework(FrameworkOpcode.GameListRequest, Array.Empty<byte>());
}
private void RefreshLobby()
{
if (lobbyController == null)
{
return;
}
lobbyController.BeginGames();
if (games.Count == 0)
{
lobbyController.AddGame(string.Empty, "Match Game", "Server will pick an available game");
}
else
{
foreach (GameInfoMsg game in games)
{
string name = string.IsNullOrEmpty(game.DisplayName) ? game.GameId : game.DisplayName;
lobbyController.AddGame(game.GameId, name, "players " + game.MinPlayers + "-" + game.MaxPlayers);
}
}
lobbyController.EndGames();
if (!string.IsNullOrEmpty(matchMessage))
{
lobbyController.SetStatus(matchMessage);
}
}
private void OnMatchClicked(string gameId)
{
matchMessage = "Matching...";
pendingGameId = string.Empty;
pendingGameVersion = 0;
CloseLobby();
OpenWaiting(gameId ?? string.Empty, matchMessage, false);
if (lobbyNet == null)
{
SetWaitingStatus("Network unavailable.", true);
return;
}
if (string.IsNullOrEmpty(gameId))
{
lobbyNet.SendFramework(FrameworkOpcode.RandomMatchRequest, Array.Empty<byte>());
}
else
{
int version = 0;
for (int i = 0; i < games.Count; i++)
{
if (games[i].GameId == gameId)
{
version = games[i].Version;
break;
}
}
pendingGameId = gameId;
pendingGameVersion = version;
lobbyNet.SendFramework(FrameworkOpcode.MatchRequest, new MatchRequestMsg { GameId = gameId, Version = version }.Encode());
}
}
private void OpenWaiting(string gameId, string message, bool failed)
{
if (waitingPanel != null)
{
waitingController?.SetWaiting(gameId, message);
waitingController?.SetStatus(message, failed);
return;
}
if (openingWaiting)
{
return;
}
openingWaiting = true;
XWCoroutine.StartCo(CoOpenWaiting(gameId, message, failed));
}
private IEnumerator CoOpenWaiting(string gameId, string message, bool failed)
{
UObject loaded = null;
yield return XResLoader.coLoadRes("Assets/Game/Art/UI/Prefab/UI_MatchWaiting.prefab", typeof(GameObject), obj => loaded = obj);
openingWaiting = false;
GameObject asset = loaded as GameObject;
if (asset == null)
{
Debug.LogError("[CSharpClientApp] load match waiting panel failed");
yield break;
}
waitingPanel = Instantiate(asset);
AttachToMain(waitingPanel);
waitingController = waitingPanel.GetComponent<MatchWaitingController>();
if (waitingController == null)
{
waitingController = waitingPanel.AddComponent<MatchWaitingController>();
}
waitingController.OnCloseRequested = OnCloseWaiting;
waitingController.SetWaiting(gameId, message);
waitingController.SetStatus(message, failed);
}
private void OnCloseWaiting()
{
matchMessage = string.Empty;
pendingGameId = string.Empty;
pendingGameVersion = 0;
CloseWaiting();
OpenLobby();
}
private void OnLobbyFrameworkFrame(Frame frame)
{
switch ((FrameworkOpcode)frame.Opcode)
{
case FrameworkOpcode.GameListResponse:
games.Clear();
games.AddRange(GameListResponseMsg.Decode(frame.Payload).Games);
matchMessage = "Games discovered: " + games.Count;
RefreshLobby();
break;
case FrameworkOpcode.MatchAssigned:
MatchAssignedMsg assigned = MatchAssignedMsg.Decode(frame.Payload);
pendingGameId = assigned.GameId;
pendingGameVersion = assigned.Version;
Debug.Log("[CSharpClientApp] match assigned " + pendingGameId + "@" + pendingGameVersion);
SetWaitingStatus("Waiting for players...", false);
break;
case FrameworkOpcode.MatchFound:
MatchFoundMsg found = MatchFoundMsg.Decode(frame.Payload);
Debug.Log("[CSharpClientApp] match found room=" + found.RoomId + " self=" + found.SelfPlayerId);
if (string.IsNullOrEmpty(pendingGameId))
{
SetWaitingStatus("Room ready, but no game was assigned.", true);
break;
}
SetWaitingStatus("Room ready.", false);
EnterAssignedGame(pendingGameId, pendingGameVersion, found);
break;
case FrameworkOpcode.Error:
ErrorMsg err = ErrorMsg.Decode(frame.Payload);
pendingGameId = string.Empty;
pendingGameVersion = 0;
SetWaitingStatus("Server error " + err.Code + ": " + err.Message, true);
break;
case FrameworkOpcode.LobbyState:
Debug.Log("[CSharpClientApp] recv LobbyState bytes=" + (frame.Payload?.Length ?? 0));
lobbyWorld?.HandleFrame(frame);
break;
case FrameworkOpcode.LobbyMove:
if (Time.unscaledTime >= nextLobbyMoveFrameLogTime)
{
nextLobbyMoveFrameLogTime = Time.unscaledTime + 1f;
Debug.Log("[CSharpClientApp] recv LobbyMove bytes=" + (frame.Payload?.Length ?? 0));
}
lobbyWorld?.HandleFrame(frame);
break;
}
}
private void EnterAssignedGame(string gameId, int version, MatchFoundMsg found)
{
pendingGameId = string.Empty;
pendingGameVersion = 0;
lobbyActive = false;
lobbyWorld?.SendLeave();
lobbyNet = null;
CloseWaiting();
CloseLobby();
gameHost = GetComponent<MiniGameHost>();
if (gameHost == null)
{
gameHost = gameObject.AddComponent<MiniGameHost>();
}
gameHost.OnGameExited = () =>
{
matchMessage = "Returned to lobby.";
lobbyNet = new MiniGameNetChannel(socket);
lobbyNet.OnFrameworkFrame = OnLobbyFrameworkFrame;
lobbyActive = true;
OpenLobby();
JoinLobbyWorld();
RequestGameList();
};
gameHost.EnterGame(ResolvedCdnBase(), gameId, version, socket, false);
// 大厅链路 MatchFound 由大厅通道消费,宿主收不到,须显式注入房间信息(结算弹框判胜负用)
gameHost.SetMatchInfo(found.RoomId, found.SelfPlayerId);
}
private string ResolvedCdnBase()
{
string cdn = GlobalData.CurCDN;
#if XW_DEVTEST
if (MiniGameTestOverride.IsActive && !string.IsNullOrEmpty(MiniGameTestOverride.CdnBase))
{
cdn = MiniGameTestOverride.CdnBase;
}
#endif
if (string.IsNullOrEmpty(cdn))
{
cdn = "http://127.0.0.1:15081/";
}
cdn = NormalizeCdnBase(cdn);
if (!cdn.EndsWith("/"))
{
cdn += "/";
}
if (cdn.EndsWith("/minigame/", StringComparison.OrdinalIgnoreCase))
{
return cdn;
}
return cdn + "minigame/";
}
#if XW_DEVTEST
private static string AddDevPid(string gatewayUrl)
{
if (string.IsNullOrWhiteSpace(gatewayUrl))
{
return gatewayUrl;
}
return WithPlayerId(gatewayUrl);
}
#endif
private void JoinLobbyWorld()
{
if (lobbyNet == null)
{
return;
}
if (lobbyWorld == null)
{
lobbyWorld = gameObject.GetComponent<LobbyWorldController>();
if (lobbyWorld == null)
{
lobbyWorld = gameObject.AddComponent<LobbyWorldController>();
}
}
lobbyWorld.Initialize(
(op, payload) => lobbyNet?.SendFramework(op, payload ?? Array.Empty<byte>()),
GetLobbyPlayerId(),
GetLobbyPlayerName(),
0);
}
private static string GetLobbyPlayerName()
{
string account = XData.GetString(LoginAccountKey, string.Empty);
if (!string.IsNullOrEmpty(account))
{
return account;
}
string userId = XData.GetString(LoginUserIdKey, string.Empty);
return string.IsNullOrEmpty(userId) ? "Player" : userId;
}
private static int GetStablePlayerId()
{
string userId = GlobalData.bLocal ? GetLocalClientId() : XData.GetString(LoginUserIdKey, string.Empty);
if (string.IsNullOrEmpty(userId))
{
userId = XData.GetString(LoginAccountKey, string.Empty);
}
if (string.IsNullOrEmpty(userId))
{
userId = SystemInfo.deviceUniqueIdentifier;
}
int parsed;
if (int.TryParse(userId, out parsed) && parsed > 0)
{
return parsed;
}
unchecked
{
uint hash = 2166136261u;
for (int i = 0; i < userId.Length; i++)
{
hash ^= userId[i];
hash *= 16777619u;
}
return (int)(hash % 2000000000u) + 1;
}
}
private int GetLobbyPlayerId()
{
if (GlobalData.bLocal)
{
return GetStablePlayerId();
}
if (m_pid > 0)
{
return m_pid;
}
int savedPid = XData.GetInt(LoginPlayerIdKey, 0);
if (savedPid > 0)
{
m_pid = savedPid;
return savedPid;
}
return GetStablePlayerId();
}
private static string GetLocalClientId()
{
string localId = XData.GetString(LocalClientIdKey, string.Empty);
if (!string.IsNullOrEmpty(localId))
{
return localId;
}
localId = SystemInfo.deviceUniqueIdentifier;
if (string.IsNullOrEmpty(localId) || localId == "unsupported")
{
localId = Guid.NewGuid().ToString("N");
}
XData.SetString(LocalClientIdKey, localId);
XData.Save();
return localId;
}
private static string WithPlayerId(string gatewayUrl)
{
if (string.IsNullOrWhiteSpace(gatewayUrl))
{
return gatewayUrl;
}
int pid = GetStablePlayerId();
int queryStart = gatewayUrl.IndexOf('?');
if (queryStart < 0)
{
return gatewayUrl + "?pid=" + pid;
}
string head = gatewayUrl.Substring(0, queryStart);
string query = gatewayUrl.Substring(queryStart + 1);
string[] parts = query.Split('&');
bool replaced = false;
for (int i = 0; i < parts.Length; i++)
{
if (parts[i].StartsWith("pid=", StringComparison.OrdinalIgnoreCase))
{
parts[i] = "pid=" + pid;
replaced = true;
break;
}
}
return head + "?" + (replaced ? string.Join("&", parts) : query + "&pid=" + pid);
}
private static string NormalizeCdnBase(string cdnBase)
{
string url = string.IsNullOrWhiteSpace(cdnBase) ? "http://127.0.0.1:15081/" : cdnBase.Trim();
if (url.StartsWith("http://file://", StringComparison.OrdinalIgnoreCase))
{
url = url.Substring("http://".Length);
}
else if (url.StartsWith("https://file://", StringComparison.OrdinalIgnoreCase))
{
url = url.Substring("https://".Length);
}
if (url.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
{
url = NormalizeFileUrl(url);
}
else if (!url.StartsWith("http://", StringComparison.OrdinalIgnoreCase) &&
!url.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
{
url = "http://" + url;
}
return url.EndsWith("/") ? url : url + "/";
}
private static string NormalizeFileUrl(string url)
{
string rawPath = url.Substring("file://".Length).Replace('\\', '/');
while (rawPath.StartsWith("/") && rawPath.Length > 2 && rawPath[2] == ':')
{
rawPath = rawPath.Substring(1);
}
string localPath = Uri.UnescapeDataString(rawPath).Replace('/', Path.DirectorySeparatorChar);
try
{
return new Uri(localPath).AbsoluteUri;
}
catch
{
return "file:///" + rawPath.Replace("#", "%23");
}
}
private void SaveSession(string account, string userId, string token = null, int pid = 0)
{
string sessionToken = string.IsNullOrEmpty(token) ? Guid.NewGuid().ToString("N") : token;
XData.SetString(LoginTokenKey, sessionToken);
XData.SetString(LoginTokenTimeKey, Now().ToString(CultureInfo.InvariantCulture));
XData.SetString(LoginUserIdKey, string.IsNullOrEmpty(userId) ? account : userId);
XData.SetString(LoginAccountKey, account);
if (pid > 0)
{
m_pid = pid;
XData.SetInt(LoginPlayerIdKey, pid);
}
else if (!GlobalData.bLocal)
{
m_pid = 0;
XData.DeleteKey(LoginPlayerIdKey);
}
XData.Save();
GlobalData.Token = sessionToken;
}
private bool HasValidToken()
{
string token = XData.GetString(LoginTokenKey, "");
double savedAt = ParseDouble(XData.GetString(LoginTokenTimeKey, "0"));
if (string.IsNullOrEmpty(token) || savedAt <= 0)
{
return false;
}
if (Now() - savedAt > TokenValidSeconds)
{
return false;
}
GlobalData.Token = token;
m_pid = XData.GetInt(LoginPlayerIdKey, 0);
return true;
}
private static void ClearToken()
{
XData.DeleteKey(LoginTokenKey);
XData.DeleteKey(LoginTokenTimeKey);
XData.DeleteKey(LoginPlayerIdKey);
XData.Save();
}
private static string GetGuestId()
{
string guestId = XData.GetString(LoginGuestIdKey, "");
if (!string.IsNullOrEmpty(guestId))
{
return guestId;
}
string savedAccount = XData.GetString(LoginAccountKey, "");
if (savedAccount.StartsWith("guest:", StringComparison.Ordinal))
{
guestId = savedAccount.Substring("guest:".Length);
}
if (string.IsNullOrEmpty(guestId))
{
guestId = SystemInfo.deviceUniqueIdentifier;
}
if (string.IsNullOrEmpty(guestId) || guestId == "unsupported")
{
guestId = ((long)(Now() * 1000)).ToString(CultureInfo.InvariantCulture);
}
XData.SetString(LoginGuestIdKey, guestId);
XData.Save();
return guestId;
}
private static double Now()
{
return Utility.GetTimeTick() / 1000.0;
}
private static double ParseDouble(string value)
{
double result;
return double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out result) ? result : 0;
}
private void ShowLoginError(string message)
{
if (loginController != null && !string.IsNullOrEmpty(message))
{
loginController.ShowError(message);
}
}
private void SetLobbyStatus(string message)
{
matchMessage = message ?? string.Empty;
if (lobbyController != null)
{
lobbyController.SetStatus(matchMessage);
}
}
private void SetWaitingStatus(string message, bool failed)
{
matchMessage = message ?? string.Empty;
if (waitingController != null)
{
waitingController.SetStatus(matchMessage, failed);
}
if (lobbyController != null)
{
lobbyController.SetStatus(matchMessage);
}
}
private void CloseLoginPanel()
{
if (loginPanel != null)
{
Destroy(loginPanel);
}
loginPanel = null;
loginController = null;
}
private void CloseLobby()
{
if (lobbyPanel != null)
{
Destroy(lobbyPanel);
}
lobbyPanel = null;
lobbyController = null;
}
private void CloseWaiting()
{
if (waitingPanel != null)
{
Destroy(waitingPanel);
}
waitingPanel = null;
waitingController = null;
}
// 登录/大厅/匹配等待都是普通功能界面 → 挂到 UIRoot/Normal 层(见 UI 挂载规则)
private static void AttachToMain(GameObject obj)
{
UIManager.AttachTo(UILayer.Normal, obj);
}
private static string NormalizeNode(string node, int defaultPort)
{
string address = string.IsNullOrEmpty(node) ? "127.0.0.1:" + defaultPort : node.Trim();
if (address.StartsWith("http://", StringComparison.OrdinalIgnoreCase))
{
address = address.Substring("http://".Length);
}
else if (address.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
{
address = address.Substring("https://".Length);
}
int slash = address.IndexOf('/');
if (slash >= 0)
{
address = address.Substring(0, slash);
}
if (address.IndexOf(':') < 0)
{
address += ":" + defaultPort;
}
return address;
}
private static void SplitHostPort(string node, int defaultPort, out string host, out int port)
{
host = node;
port = defaultPort;
int colon = node.LastIndexOf(':');
if (colon >= 0)
{
host = node.Substring(0, colon);
int.TryParse(node.Substring(colon + 1), out port);
}
if (string.IsNullOrEmpty(host))
{
host = "127.0.0.1";
}
if (port <= 0)
{
port = defaultPort;
}
}
}
}