1015 lines
35 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
}
|