This commit is contained in:
2026-08-20 19:45:38 +05:30
parent 74d9fae6e6
commit 78592e8d8e
87 changed files with 13154 additions and 223 deletions
+166 -16
View File
@@ -16,6 +16,15 @@ public class GameManager : NetworkBehaviour
public static MatchmadePlayer BluePlayer { get; set; }
/// <summary>Coin units from matchmaker <c>rc_prize</c>; used for in-game prize UI.</summary>
public static int MatchRcPrizeCoins { get; set; }
/// <summary>Participation CC amount shown on game over. Actual credit is decided by the winner PATCH.</summary>
public const int ParticipationCcAmount = 100;
/// <summary>Server-reported: red was still connected when the match ended (eligible for participation CC).</summary>
public static bool RedFinishedMatch { get; private set; } = true;
/// <summary>Server-reported: blue was still connected when the match ended (eligible for participation CC).</summary>
public static bool BlueFinishedMatch { get; private set; } = true;
/// <summary>Local player stayed connected through match end and should see +CC on game over.</summary>
public static bool LocalPlayerEarnedParticipationCc =>
MyTeam == Team.Blue ? BlueFinishedMatch : RedFinishedMatch;
/// <summary>Set by dedicated server startup (e.g. CupidConnector parsing <c>-matchId</c>). PATCH is skipped if id ≤ 0 or secret is empty.</summary>
public static int DedicatedMatchId { get; private set; }
/// <summary>Sent as <c>X-Dedicated-Server-Secret</c>. From <c>-dedicatedSecret</c> arg or <c>DEDICATED_SERVER_SECRET</c> env.</summary>
@@ -504,10 +513,57 @@ public class GameManager : NetworkBehaviour
}
}
bool _abortLeaveStarted;
void AbortMatchCollectFailed()
{
RpcAbortMatch("Match cancelled", "The match could not start. Please try again.");
StartCoroutine(CoDisconnectAfterAbortRpc());
}
[ClientRpc]
void RpcAbortMatch(string title, string message)
{
ShowMatchAbortMessage(title, message);
// Host keeps the server up so the Rpc can reach the other client first.
if (!NetworkServer.active)
LeaveToMenuAfterAbort();
}
IEnumerator CoDisconnectAfterAbortRpc()
{
yield return new WaitForSecondsRealtime(0.5f);
if (NetworkServer.active)
NetworkServer.DisconnectAll();
if (isClient)
LeaveToMenuAfterAbort();
}
void NotifyAndLeaveMatch(string title, string message)
{
ShowMatchAbortMessage(title, message);
LeaveToMenuAfterAbort();
}
void ShowMatchAbortMessage(string title, string message)
{
NetManager.MarkIntentionalClientDisconnect();
if (CupidLobby.instance != null)
CupidLobby.instance.Cancel();
LoginManager.ClearMatchYourTeam();
MessageBoxDialog.Show(title, message);
}
void LeaveToMenuAfterAbort()
{
if (_abortLeaveStarted)
return;
_abortLeaveStarted = true;
if (LevelLoadManager.instance != null)
LevelLoadManager.instance.Leave();
else
LevelLoadManager.LoadLevel("MainMenu");
}
IEnumerator DedicatedMatchPatch(string baseUrl, int matchId, string secret, string json)
@@ -522,12 +578,12 @@ public class GameManager : NetworkBehaviour
}
}
public IEnumerator DedicatedMatchPatchWinner(string baseUrl, int matchId, string secret, string winnerLower)
public IEnumerator DedicatedMatchPatchWinner(string baseUrl, int matchId, string secret, string winnerLower, bool redConnected, bool blueConnected)
{
if (matchId <= 0 || string.IsNullOrEmpty(secret))
yield break;
string jsonBody = "{\"winner\":\"" + winnerLower + "\"}";
string jsonBody = DedicatedMatschInternalApi.BuildWinnerPatchJson(winnerLower, redConnected, blueConnected);
using (var req = DedicatedMatschInternalApi.BuildWinnerRequest(baseUrl, matchId, secret, jsonBody))
{
yield return req.SendWebRequest();
@@ -535,7 +591,7 @@ public class GameManager : NetworkBehaviour
}
}
void ReportDedicatedMatchGameOver(Team winningTeam)
void ReportDedicatedMatchGameOver(Team winningTeam, bool redConnected, bool blueConnected)
{
if (!isServer || DedicatedMatchId <= 0 || string.IsNullOrEmpty(DedicatedMatchSecret) || _winnerSettled)
return;
@@ -545,14 +601,14 @@ public class GameManager : NetworkBehaviour
return;
}
_winnerSettled = true;
StartCoroutine(CoReportDedicatedMatchGameOver(winningTeam));
StartCoroutine(CoReportDedicatedMatchGameOver(winningTeam, redConnected, blueConnected));
}
IEnumerator CoReportDedicatedMatchGameOver(Team winningTeam)
IEnumerator CoReportDedicatedMatchGameOver(Team winningTeam, bool redConnected, bool blueConnected)
{
yield return DedicatedMatchPatch(DedicatedInternalApiBase, DedicatedMatchId, DedicatedMatchSecret, "{\"status\":3}");
string winner = winningTeam == Team.Red ? "red" : "blue";
yield return DedicatedMatchPatchWinner(DedicatedInternalApiBase, DedicatedMatchId, DedicatedMatchSecret, winner);
yield return DedicatedMatchPatchWinner(DedicatedInternalApiBase, DedicatedMatchId, DedicatedMatchSecret, winner, redConnected, blueConnected);
}
/// <summary>Score or forfeit end: latch game over, report winner if escrowed, notify clients.</summary>
@@ -563,11 +619,58 @@ public class GameManager : NetworkBehaviour
isGameEnded = true;
ReplayRecorder.StopAndFlush();
ReportDedicatedMatchGameOver(winningTeam);
RpcGameOver(winningTeam);
bool redConnected = IsSideConnectedForParticipation(Team.Red);
bool blueConnected = IsSideConnectedForParticipation(Team.Blue);
Logger.Log($"Dedicated match: end winner={winningTeam} red_connected={redConnected} blue_connected={blueConnected}");
ApplyParticipationCcFlags(redConnected, blueConnected);
ReportDedicatedMatchGameOver(winningTeam, redConnected, blueConnected);
RpcGameOver(winningTeam, redConnected, blueConnected);
gameOver(winningTeam);
}
/// <summary>
/// Server-side: participation CC is only for players still connected at settle.
/// Voluntary leave clears the connected flag before <see cref="EndMatch"/>.
/// </summary>
bool IsSideConnectedForParticipation(Team team)
{
if (team == Team.Red)
{
if (!_redConnected)
return false;
}
else if (team == Team.Blue)
{
if (!_blueConnected)
return false;
}
else
{
return false;
}
foreach (var player in FindObjectsByType<NetPlayer>(FindObjectsSortMode.None))
{
if (player == null || player.myTeam != team)
continue;
if (player.connectionToClient != null)
return player.connectionToClient.isReady;
// Listen-server host has no connectionToClient.
if (player.isLocalPlayer)
return true;
}
return false;
}
static void ApplyParticipationCcFlags(bool redConnected, bool blueConnected)
{
RedFinishedMatch = redConnected;
BlueFinishedMatch = blueConnected;
}
void HandleDisconnectForfeitTimers()
{
if (!_entriesCollected || _winnerSettled || !gameStarted || isGameEnded)
@@ -620,9 +723,13 @@ public class GameManager : NetworkBehaviour
return;
Team winningTeam = ResolveForfeitWinnerForShutdown();
bool redConnected = IsSideConnectedForParticipation(Team.Red);
bool blueConnected = IsSideConnectedForParticipation(Team.Blue);
_winnerSettled = true;
isGameEnded = true;
Logger.Log("Dedicated match: blocking winner settle before shutdown → " + winningTeam);
ApplyParticipationCcFlags(redConnected, blueConnected);
Logger.Log("Dedicated match: blocking winner settle before shutdown → " + winningTeam
+ " red_connected=" + redConnected + " blue_connected=" + blueConnected);
using (var statusReq = DedicatedMatschInternalApi.BuildRequest(
DedicatedInternalApiBase, DedicatedMatchId, DedicatedMatchSecret, "{\"status\":3}"))
@@ -634,7 +741,7 @@ public class GameManager : NetworkBehaviour
}
string winner = winningTeam == Team.Red ? "red" : "blue";
string jsonBody = "{\"winner\":\"" + winner + "\"}";
string jsonBody = DedicatedMatschInternalApi.BuildWinnerPatchJson(winner, redConnected, blueConnected);
using (var winReq = DedicatedMatschInternalApi.BuildWinnerRequest(
DedicatedInternalApiBase, DedicatedMatchId, DedicatedMatchSecret, jsonBody))
{
@@ -1157,6 +1264,7 @@ public class GameManager : NetworkBehaviour
RpcPlayGoalScoredSfx();
freezeInput=true;
CancelInGoalPuckResets();
Team concedingTeam = team;
if(hitCounter == 1){
@@ -1236,7 +1344,8 @@ public class GameManager : NetworkBehaviour
}
[ClientRpc]
void RpcGameOver(Team team){
void RpcGameOver(Team team, bool redConnected, bool blueConnected){
ApplyParticipationCcFlags(redConnected, blueConnected);
gameOver(team);
}
@@ -1245,7 +1354,11 @@ public class GameManager : NetworkBehaviour
}
IEnumerator CoroutineGameOver(Team team){
yield return new WaitForSeconds(2f);
if (GameCanvas.instance != null)
yield return GameCanvas.instance.ShowRandomPostGamePanelThenHide();
else
yield return new WaitForSeconds(2f);
yield return CoroutineFetchBothPlayersL10();
// StopClient();
@@ -1368,6 +1481,11 @@ public class GameManager : NetworkBehaviour
if (leavingTeam != Team.Red && leavingTeam != Team.Blue)
return;
if (leavingTeam == Team.Red)
_redConnected = false;
else if (leavingTeam == Team.Blue)
_blueConnected = false;
Team winner = GetOpposingTeam(leavingTeam);
Logger.Log($"Match: {leavingTeam} forfeited by leave — {winner} wins");
EndMatch(winner);
@@ -1383,10 +1501,7 @@ public class GameManager : NetworkBehaviour
{
_thirdPlayerLeaveTriggered = true;
Logger.Log("Third player in a 2-player match (matchmaker bug); leaving to main menu.");
if (LevelLoadManager.instance != null)
LevelLoadManager.instance.Leave();
else
LevelLoadManager.LoadLevel("MainMenu");
NotifyAndLeaveMatch("Unable to join", "This match is already full.");
yield break;
}
}
@@ -1456,6 +1571,15 @@ public class GameManager : NetworkBehaviour
bool freezeInput = false;
public bool CanProcessGoal => isServer && !freezeInput && !isGameEnded;
void CancelInGoalPuckResets()
{
foreach (Puck puck in pucks)
{
if (puck != null)
puck.CancelInGoalReset();
}
}
IEnumerator CoroutineOnGoal(Team team, bool kickoff = false){
if(coroutinePostLaunch!=null){
StopCoroutine(coroutinePostLaunch);
@@ -1521,6 +1645,16 @@ static class DedicatedMatschInternalApi
return BuildPatchJson(url, secret, jsonBody);
}
public static string BuildWinnerPatchJson(string winnerLower, bool redConnected, bool blueConnected)
{
return JsonUtility.ToJson(new DedicatedWinnerPatchRequest
{
winner = winnerLower,
red_connected = redConnected,
blue_connected = blueConnected
});
}
public static UnityWebRequest BuildRematchRequest(string baseUrl, string secret, string jsonBody)
{
string url = baseUrl.TrimEnd('/') + "/internal/rematch";
@@ -1570,7 +1704,13 @@ static class DedicatedMatschInternalApi
{
var parsed = JsonUtility.FromJson<DedicatedWinnerPatchResponse>(body);
if (parsed != null && parsed.ok && parsed.economy != null)
{
GameManager.MatchRcPrizeCoins = parsed.economy.rc_prize;
Logger.Log("Dedicated winner PATCH economy: rc_prize=" + parsed.economy.rc_prize
+ " participant_cc=" + parsed.economy.participant_cc
+ " cc_awarded_red=" + parsed.economy.cc_awarded_red
+ " cc_awarded_blue=" + parsed.economy.cc_awarded_blue);
}
}
catch (Exception e)
{
@@ -1602,12 +1742,22 @@ class DedicatedJoinPatchResponse
public int escrow_user_id;
}
[Serializable]
class DedicatedWinnerPatchRequest
{
public string winner;
public bool red_connected;
public bool blue_connected;
}
[Serializable]
class DedicatedWinnerPatchEconomy
{
public int entry_fee_rc;
public int rc_prize;
public int participant_cc;
public bool cc_awarded_red;
public bool cc_awarded_blue;
}
[Serializable]