using System; using System.Collections; using AOT.Boot; using UnityEngine; namespace HotUpdate.Net { /// /// 登录 → WS 连接编排(含指数退避重连与维护公告)。 /// public sealed class GameNet : MonoBehaviour { public string HttpBase = AuthApi.DefaultHttpBase; public string WsUrlTemplate = "wss://game.xworld.ren/ws?pid={pid}&token={token}"; public SessionData Session { get; private set; } public WsClient Socket { get; private set; } public string Status { get; private set; } = "idle"; public event Action OnAuthed; public event Action OnWsReady; public event Action OnStatus; public event Action OnWsMessage; public event Action OnWsDisconnected; public event Action OnReconnecting; public event Action OnMaintenance; BootContext _boot; bool _started; bool _maintenance; bool _everConnected; public void Bind(BootContext boot) { _boot = boot; if (_boot == null) return; if (!string.IsNullOrEmpty(_boot.HttpBase)) HttpBase = _boot.HttpBase; if (!string.IsNullOrEmpty(_boot.WsUrlTemplate)) { var tpl = _boot.WsUrlTemplate; if (!tpl.Contains("{token}")) { tpl = tpl.Contains("?") ? tpl + (tpl.EndsWith("?") || tpl.EndsWith("&") ? "token={token}" : "&token={token}") : tpl + "?pid={pid}&token={token}"; } WsUrlTemplate = tpl; } } public void StartGuestSession() { if (_started) return; _started = true; StartCoroutine(RunGuestFlow()); } /// UI「立即重连」:重置退避并马上再连。 public void RequestReconnect() { if (_maintenance || Session == null || !Session.IsValid) return; SetStatus("手动重连…"); if (Socket != null) { Socket.AutoReconnect = true; Socket.ForceReconnect(); } else StartCoroutine(ConnectWs()); } public void NotifyMaintenance(string message) { if (_maintenance) return; _maintenance = true; if (Socket != null) { Socket.AutoReconnect = false; Socket.Close(); } SetStatus("maintenance"); OnMaintenance?.Invoke(message ?? "服务器维护中"); } IEnumerator RunGuestFlow() { SetStatus("auth…"); var deviceId = DeviceId.GetOrCreate(); var username = $"guest_{deviceId}"; var password = $"pw_{deviceId}"; AuthResponse auth = null; string err = null; yield return AuthApi.EnsureAccount(HttpBase, username, password, (r, e) => { auth = r; err = e; }); if (auth == null || string.IsNullOrEmpty(auth.token)) { SetStatus($"auth fail: {err}"); Debug.LogError($"[GameNet] auth failed: {err}"); yield break; } Session = new SessionData { Username = username, Token = auth.token, Pid = auth.pid, DeviceId = deviceId, }; Debug.Log($"[GameNet] authed pid={Session.Pid} user={username}"); OnAuthed?.Invoke(Session); SetStatus($"authed pid={Session.Pid}"); yield return ConnectWs(); } IEnumerator ConnectWs() { if (_maintenance) yield break; if (Session == null || !Session.IsValid) { SetStatus("no session"); yield break; } SetStatus("ws connecting…"); Socket?.Dispose(); Socket = new WsClient(); Socket.AutoReconnect = true; Socket.OnConnected += HandleConnected; Socket.OnDisconnected += HandleDisconnected; Socket.OnReconnecting += attempt => { SetStatus($"ws reconnecting #{attempt}"); OnReconnecting?.Invoke(attempt); }; Socket.OnMessage += msg => { Debug.Log($"[GameNet] << {msg}"); OnWsMessage?.Invoke(msg); }; var url = Session.BuildWsUrl(WsUrlTemplate); Socket.Connect(url); float t = 0f; while (t < 15f && Socket.State != WsState.Connected && Socket.State != WsState.Disconnected) { t += Time.unscaledDeltaTime; yield return null; } if (Socket.State == WsState.Connected) SetStatus("ws connected"); else if (Socket.State == WsState.Disconnected && !string.IsNullOrEmpty(Socket.LastError)) SetStatus($"ws fail: {Socket.LastError}"); else if (Socket.State != WsState.Connected) SetStatus($"ws state={Socket.State}"); } void HandleConnected() { if (_maintenance) return; SetStatus(_everConnected ? "ws reconnected" : "ws connected"); _everConnected = true; SendHello(); OnWsReady?.Invoke(); } void HandleDisconnected(string reason) { SetStatus($"ws down: {reason}"); OnWsDisconnected?.Invoke(reason ?? "closed"); } void SendHello() { if (Socket == null || Session == null) return; Socket.SendText( $"{{\"op\":\"hello\",\"proto\":1,\"app\":\"{_boot?.AppVersion}\",\"res\":\"{_boot?.ResVersion}\",\"pid\":{Session.Pid}}}"); } void Update() { Socket?.Tick(); } void OnDestroy() { Socket?.Dispose(); Socket = null; } void SetStatus(string s) { Status = s; Debug.Log($"[GameNet] {s}"); OnStatus?.Invoke(s); } } }