using System.Collections; using UnityEngine; using Mirror; using UnityEngine.Networking; public class NetManager : NetworkManager { const float InitialReconnectDelaySeconds = 2f; const float ReopenTriggerDelaySeconds = 15f; [SerializeField] float reconnectIntervalSeconds = 15f; Coroutine reconnectCoroutine; bool reopenRequestedThisDisconnect; bool reopenRequestInFlight; [SerializeField] private CanvasGroup reconnectCanvasGroup; static bool s_IntentionalClientDisconnect; public static void MarkIntentionalClientDisconnect() { s_IntentionalClientDisconnect = true; } public override void OnApplicationQuit() { MarkIntentionalClientDisconnect(); StopReconnectLoop(); base.OnApplicationQuit(); } public override void OnStopServer() { GameManager.ReportDedicatedMatchRoomClosed(); base.OnStopServer(); } public override void OnClientConnect() { base.OnClientConnect(); Logger.Log("Client connected"); reopenRequestedThisDisconnect = false; reopenRequestInFlight = false; StopReconnectLoop(); } public override void OnClientDisconnect() { base.OnClientDisconnect(); Logger.Log("Client disconnected"); if (ShouldReconnect()) { if (reconnectCoroutine == null) reconnectCoroutine = StartCoroutine(ReconnectLoopCoroutine()); else SetReconnectCanvasVisible(true); } else SetReconnectCanvasVisible(false); } public override void OnStopClient() { s_IntentionalClientDisconnect = false; base.OnStopClient(); } void StopReconnectLoop() { if (reconnectCoroutine == null) { SetReconnectCanvasVisible(false); return; } StopCoroutine(reconnectCoroutine); reconnectCoroutine = null; SetReconnectCanvasVisible(false); } void SetReconnectCanvasVisible(bool visible) { if (reconnectCanvasGroup == null) return; reconnectCanvasGroup.alpha = visible ? 1f : 0f; reconnectCanvasGroup.interactable = visible; reconnectCanvasGroup.blocksRaycasts = visible; } IEnumerator ReconnectLoopCoroutine() { SetReconnectCanvasVisible(true); reopenRequestedThisDisconnect = false; reopenRequestInFlight = false; float reconnectStartedAt = Time.realtimeSinceStartup; float initialWaitEnd = Time.realtimeSinceStartup + InitialReconnectDelaySeconds; while (ShouldReconnect() && Time.realtimeSinceStartup < initialWaitEnd) yield return null; while (ShouldReconnect()) { if (!reopenRequestedThisDisconnect && !reopenRequestInFlight && Time.realtimeSinceStartup - reconnectStartedAt >= ReopenTriggerDelaySeconds) { reopenRequestedThisDisconnect = true; StartCoroutine(CoRequestReopenForCurrentPort()); } if (NetworkClient.active && !NetworkClient.isConnected) { StopClient(); yield return null; continue; } if (!NetworkClient.active) { Logger.Log("Reconnect: attempting StartClient"); StartClient(); } float waitEnd = Time.realtimeSinceStartup + reconnectIntervalSeconds; while (ShouldReconnect() && Time.realtimeSinceStartup < waitEnd) yield return null; } reconnectCoroutine = null; SetReconnectCanvasVisible(false); } bool ShouldReconnect() { if (s_IntentionalClientDisconnect) return false; if (NetworkServer.active) return false; if (NetworkClient.isConnected) return false; if (GameManager.instance != null && GameManager.instance.isGameEnded) return false; return true; } IEnumerator CoRequestReopenForCurrentPort() { reopenRequestInFlight = true; int port = Cupid.RoomPort; if (port <= 0) { Logger.Log("Reconnect: skipping /reopen because room port is invalid"); reopenRequestInFlight = false; yield break; } string baseUri = Cupid.CupidURI; string password = Cupid.SettingsPassword; if (string.IsNullOrWhiteSpace(baseUri) || string.IsNullOrEmpty(password)) { Logger.Log("Reconnect: skipping /reopen because Cupid URI/password is not available"); reopenRequestInFlight = false; yield break; } string query = "?password=" + UnityWebRequest.EscapeURL(password) + "&port=" + port; string[] endpointCandidates = { baseUri.TrimEnd('/') + "/internal/admin/reopen" + query, baseUri.TrimEnd('/') + "/reopen" + query }; bool success = false; for (int i = 0; i < endpointCandidates.Length; i++) { string url = endpointCandidates[i]; using (UnityWebRequest req = UnityWebRequest.Get(url)) { req.downloadHandler = new DownloadHandlerBuffer(); yield return req.SendWebRequest(); string body = req.downloadHandler != null ? req.downloadHandler.text : ""; if (req.result == UnityWebRequest.Result.Success && req.responseCode >= 200 && req.responseCode < 300) { Logger.Log("Reconnect: /reopen success on port " + port + ". Response: " + body); success = true; break; } Logger.Log("Reconnect: /reopen failed at " + url + " status " + req.responseCode + " error: " + req.error + " body: " + body); } } if (!success) Logger.Log("Reconnect: all /reopen endpoint attempts failed."); reopenRequestInFlight = false; } }