reconnect, 30% fixes from test feedback

This commit is contained in:
2026-05-01 18:56:37 +05:30
parent 1da7c052f8
commit e3aa51d3e7
383 changed files with 41074 additions and 98 deletions
+7 -2
View File
@@ -81,10 +81,12 @@ public class GameCanvas : MonoBehaviour
}
}
try{
redName.text = GameManager.RedPlayer.Name;
blueName.text = GameManager.BluePlayer.Name;
}catch{
Logger.Log("Failed to fetch red and blue names, isServer?");
}
}
void OnEmoteTextPressed(string txtName){
@@ -164,7 +166,10 @@ public class GameCanvas : MonoBehaviour
LoginManager.ClearMatchYourTeam();
if (NetworkManager.singleton != null && NetworkClient.active)
{
NetManager.MarkIntentionalClientDisconnect();
NetworkManager.singleton.StopClient();
}
LevelLoadManager.LoadLevel("MainMenu");
}
+56
View File
@@ -106,6 +106,12 @@ public class GameManager : NetworkBehaviour
public float puckDragClampMax = 5f;
public float puckDragMinClamp = 1f;
[Header("Field bounds")]
[Tooltip("Pitch width (X). Length (Y) is fieldSize × 2. Origin-centered; used for boundary-based camera zoom while dragging.")]
public float fieldSize = 10f;
[Tooltip("Exponent on distance-to-boundary (01). >1 = subtle at center, ramps up mostly near the boundary; 1 = linear.")]
[Min(0.01f)] public float cameraStretchBoundaryExponent = 2.5f;
[SyncVar(hook = nameof(OnScoreChanged))]
public int redScore=0;
[SyncVar(hook = nameof(OnScoreChanged))]
@@ -161,6 +167,50 @@ public class GameManager : NetworkBehaviour
instance = this;
}
/// <summary>
/// Blends from <paramref name="centerMultiplier"/> at the pitch center to <paramref name="boundaryMultiplier"/>
/// at the boundary (and beyond), using the max normalized axis distance inside the field rectangle.
/// </summary>
public float GetCameraStretchDistanceMultiplier(Vector3 puckWorldPosition, float centerMultiplier = 0.3f, float boundaryMultiplier = 1f)
{
if (fieldSize <= Mathf.Epsilon)
return boundaryMultiplier;
float halfWidth = fieldSize * 0.5f;
float halfHeight = fieldSize;
float nx = Mathf.Abs(puckWorldPosition.x) / halfWidth;
float ny = Mathf.Abs(puckWorldPosition.y) / halfHeight;
float t = Mathf.Clamp01(Mathf.Max(nx, ny));
float shaped = Mathf.Pow(t, cameraStretchBoundaryExponent);
return Mathf.Lerp(centerMultiplier, boundaryMultiplier, shaped);
}
void OnDrawGizmos()
{
if (fieldSize <= Mathf.Epsilon)
return;
float halfWidth = fieldSize * 0.5f;
float halfHeight = fieldSize;
Vector3 c = Vector3.zero;
Vector3 right = new Vector3(halfWidth, 0f, 0f);
Vector3 up = new Vector3(0f, halfHeight, 0f);
Vector3 a = c - right - up;
Vector3 b = c + right - up;
Vector3 d = c + right + up;
Vector3 e = c - right + up;
Color prev = Gizmos.color;
Gizmos.color = new Color(1f, 0.92f, 0.016f, 0.85f);
Gizmos.DrawLine(a, b);
Gizmos.DrawLine(b, d);
Gizmos.DrawLine(d, e);
Gizmos.DrawLine(e, a);
Gizmos.color = prev;
}
/// <summary>Server-only: called from <see cref="NetPlayer.CmdSetTeam"/> after <c>myTeam</c> is set.</summary>
public void OnMatchPlayerTeamAssigned(Team team)
{
@@ -226,6 +276,9 @@ public class GameManager : NetworkBehaviour
yield return DedicatedMatchPatchWinner(DedicatedInternalApiBase, DedicatedMatchId, DedicatedMatchSecret, winner);
}
void Start()
{
GameEvents.OnSelectedPuckChanged?.Invoke(null);
@@ -547,10 +600,12 @@ public class GameManager : NetworkBehaviour
blueScore++;
blueScoreText.text = blueScore.ToString();
PlayGoalEffects(Team.Blue);
Logger.Log($"Blue goal scored, blue score is now {blueScore}");
}else{
redScore++;
redScoreText.text = redScore.ToString();
PlayGoalEffects(Team.Red);
Logger.Log($"Red goal scored, red score is now {redScore}");
}
SetKickoffTeam(concedingTeam);
@@ -632,6 +687,7 @@ public class GameManager : NetworkBehaviour
}
public void Leave(){
NetManager.MarkIntentionalClientDisconnect();
StopClient();
LevelLoadManager.LoadLevel("MainMenu");
}
+20
View File
@@ -36,6 +36,7 @@ public class MainMenuManager : MonoBehaviour
public Button btnPaste;
public TMP_InputField inputCryptoAddress;
public TMP_Text txtWithdrawStatus;
private bool isCoinBuyInProgress;
void Awake()
{
@@ -146,14 +147,33 @@ public class MainMenuManager : MonoBehaviour
}
void OnBuy(string packId){
if (isCoinBuyInProgress)
return;
isCoinBuyInProgress = true;
SetCoinBuyButtonsInteractable(false);
LoginManager.instance.PurchaseRcPack(packId, (success, error) => {
if(success){
OnUserUpdated(LoginManager.CurrentUser);
AudioManager.instance.PlayCoinsSfx();
}
isCoinBuyInProgress = false;
SetCoinBuyButtonsInteractable(true);
});
}
void SetCoinBuyButtonsInteractable(bool interactable)
{
if (btnBuyUsd5 != null)
btnBuyUsd5.interactable = interactable;
if (btnBuyUsd20 != null)
btnBuyUsd20.interactable = interactable;
if (btnBuyUsd50 != null)
btnBuyUsd50.interactable = interactable;
}
void OnDestroy()
{
LoginManager.OnUserDataUpdated -= OnUserUpdated;
+146 -21
View File
@@ -1,11 +1,32 @@
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()
{
@@ -13,37 +34,103 @@ public class NetManager : NetworkManager
base.OnStopServer();
}
public override void OnStopClient()
{
base.OnStopClient();
TryStartReconnectLoop();
}
public override void OnClientConnect()
{
base.OnClientConnect();
Logger.Log("Client connected");
reopenRequestedThisDisconnect = false;
reopenRequestInFlight = false;
StopReconnectLoop();
}
void TryStartReconnectLoop()
public override void OnClientDisconnect()
{
if (!ShouldReconnect() || reconnectCoroutine != null)
return;
base.OnClientDisconnect();
Logger.Log("Client disconnected");
if (ShouldReconnect())
{
if (reconnectCoroutine == null)
reconnectCoroutine = StartCoroutine(ReconnectLoopCoroutine());
else
SetReconnectCanvasVisible(true);
}
else
SetReconnectCanvasVisible(false);
}
reconnectCoroutine = StartCoroutine(CoroutineReconnectLoop());
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)
@@ -54,20 +141,58 @@ public class NetManager : NetworkManager
return true;
}
IEnumerator CoroutineReconnectLoop()
IEnumerator CoRequestReopenForCurrentPort()
{
while (ShouldReconnect())
{
if (!NetworkClient.active)
{
Debug.Log("Client disconnected. Reconnecting...");
StartClient();
yield return new WaitForSeconds(reconnectIntervalSeconds);
}
reopenRequestInFlight = true;
yield return new WaitForSeconds(1);
int port = Cupid.RoomPort;
if (port <= 0)
{
Logger.Log("Reconnect: skipping /reopen because room port is invalid");
reopenRequestInFlight = false;
yield break;
}
reconnectCoroutine = null;
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;
}
}
+5
View File
@@ -45,6 +45,8 @@ public class NetPlayer : NetworkBehaviour
[Command]
void CmdSetTeam(Team team){
setMyTeam(team);
Logger.Log($"Client {netId} set team to {team}");
}
@@ -67,6 +69,7 @@ public class NetPlayer : NetworkBehaviour
void CmdSendTextEmote(string txtName){
RpcSendTextEmote(txtName);
ShowTextEmote(txtName);
Logger.Log($"Client {netId} sent text emote {txtName}");
}
[ClientRpc]
@@ -92,6 +95,8 @@ public class NetPlayer : NetworkBehaviour
void CmdSendEmojiEmote(int id){
RpcSendEmojiEmote(id);
ShowEmojiEmote(id);
Logger.Log($"Client {netId} sent emoji emote {id}");
}
[ClientRpc]
+3 -1
View File
@@ -69,7 +69,9 @@ public class Puck : NetworkBehaviour
&& team == NetPlayer.localPlayer.myTeam
&& GameManager.instance.SelectedTeam == team)
{
CameraEffects.instance.SetStretchFactor(lineEnd.magnitude / clamp);
float baseStretch = lineEnd.magnitude / clamp;
float distanceMultiplier = GameManager.instance.GetCameraStretchDistanceMultiplier(transform.position);
CameraEffects.instance.SetStretchFactor(baseStretch * distanceMultiplier);
}
// Only two points for the line
+65
View File
@@ -0,0 +1,65 @@
using System;
using UnityEngine;
public class ServerAutoClose : MonoBehaviour
{
#if UNITY_SERVER
void Start()
{
Logger.Log("Starting auto close watchdog");
}
int lastObservedPlayerCount = -1;
bool hasPlayerEverJoined = false;
float noPlayersTimer = 0f;
int lastCountdownLoggedSecond = -1;
const float NoPlayersTimeoutSeconds = 65f;
void Update()
{
int activePlayers = 0;
try
{
activePlayers = CupidConnector.GetActivePlayerConnectionCount();
}
catch (Exception e)
{
Logger.Log("Error getting active player connection count: " + e.Message);
return;
}
if (activePlayers != lastObservedPlayerCount)
{
Logger.Log("Active player connections: " + activePlayers);
lastObservedPlayerCount = activePlayers;
}
if (activePlayers > 0)
{
hasPlayerEverJoined = true;
noPlayersTimer = 0f;
lastCountdownLoggedSecond = -1;
}
noPlayersTimer += Time.deltaTime;
int secondsRemaining = Mathf.CeilToInt(NoPlayersTimeoutSeconds - noPlayersTimer);
if (secondsRemaining > 0 && secondsRemaining % 10 == 0 && secondsRemaining != lastCountdownLoggedSecond)
{
lastCountdownLoggedSecond = secondsRemaining;
Logger.Log("Server will be closed due to no players in, " + secondsRemaining);
}
if (noPlayersTimer >= NoPlayersTimeoutSeconds)
{
if (hasPlayerEverJoined)
Logger.Log("All players left, exiting");
else
Logger.Log("No players joined, exiting");
GameManager.ReportDedicatedMatchRoomClosed();
Application.Quit();
}
}
#endif
}
+2
View File
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c10a35ed198fa2941b2b28a03c558897
+29
View File
@@ -0,0 +1,29 @@
using TMPro;
using UnityEngine;
[RequireComponent(typeof(TMP_Text))]
public class LoadingTextTMP : MonoBehaviour
{
public string baseText = "Loading";
public char dotChar = '.';
public int dotCount =3;
public float interval = 1f;
TMP_Text txt;
void Start()
{
txt = GetComponent<TMP_Text>();
}
float timer = 0;
void Update(){
timer += Time.deltaTime;
if(timer > interval * dotCount){
timer =0;
}
int dots = (int)(timer / interval);
txt.text = baseText + new string(dotChar, dots);
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 093b12f4aa5fb0446a9c1aa544025970