sounds and screens with improved matchmaker

This commit is contained in:
2026-04-05 00:08:52 +05:30
parent ef89a7320a
commit 352693c860
38 changed files with 6044 additions and 2270 deletions
+162 -8
View File
@@ -1,14 +1,61 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Networking;
using UnityEngine.Serialization;
using TMPro;
using Mirror;
public class GameManager : NetworkBehaviour
{
public static MatchmadePlayer RedPlayer { get; set; }
public static MatchmadePlayer BluePlayer { get; set; }
/// <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>
public static string DedicatedMatchSecret { get; private set; } = "";
/// <summary>Base URL for internal match API (no trailing slash), default <c>http://127.0.0.1:2612</c>.</summary>
public static string DedicatedInternalApiBase { get; private set; } = LoginManager.AuthBaseUrl;
public static void ConfigureDedicatedMatchReporting(int matchId, string secret=null, string internalApiBaseUrl = null)
{
DedicatedMatchId = matchId;
DedicatedMatchSecret = secret ?? "38516e39d3406225b7c3015a66e5d25c54e9b2024c0755212122bdcea3e3a328";
if (!string.IsNullOrWhiteSpace(internalApiBaseUrl))
DedicatedInternalApiBase = internalApiBaseUrl.TrimEnd('/');
Logger.Log($"Configured dedicated match reporting for match id {matchId} with secret {DedicatedMatchSecret} and internal api base {DedicatedInternalApiBase}");
}
/// <summary>Sets match <c>status</c> to <c>-1</c> (room closed). Safe to call from shutdown paths; runs synchronously so it completes before <c>Application.Quit</c>.</summary>
public static void ReportDedicatedMatchRoomClosed()
{
if (_dedicatedRoomClosedReported || DedicatedMatchId <= 0 || string.IsNullOrEmpty(DedicatedMatchSecret))
return;
_dedicatedRoomClosedReported = true;
if (DedicatedMatchId <= 0 || string.IsNullOrEmpty(DedicatedMatchSecret))
return;
using (var req = DedicatedMatschInternalApi.BuildRequest(DedicatedInternalApiBase, DedicatedMatchId, DedicatedMatchSecret, "{\"status\":-1}"))
{
var op = req.SendWebRequest();
while (!op.isDone)
System.Threading.Thread.Sleep(16);
DedicatedMatschInternalApi.LogIfFailed(req);
}
}
static bool _dedicatedRoomClosedReported;
static string Iso8601UtcNow() => DateTime.UtcNow.ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'", System.Globalization.CultureInfo.InvariantCulture);
bool _dedicatedReportedRedJoin;
bool _dedicatedReportedBlueJoin;
bool _dedicatedReportedGameOver;
public static Action<Team> OnTeamChanged;
public static bool isMoving{
get{
@@ -32,7 +79,7 @@ public class GameManager : NetworkBehaviour
[SyncVar(hook = nameof(OnSelectedTeamChanged))]
public Team SelectedTeam = Team.Red;
void OnSelectedTeamChanged(Team oldTeam, Team newTeam){
Debug.Log($"Selected team changed from {oldTeam} to {newTeam}");
Logger.Log($"Selected team changed from {oldTeam} to {newTeam}");
OnTeamChanged?.Invoke(newTeam);
if (!isClient || !gameStarted || oldTeam == newTeam)
@@ -109,6 +156,71 @@ public class GameManager : NetworkBehaviour
instance = this;
}
/// <summary>Server-only: called from <see cref="NetPlayer.CmdSetTeam"/> after <c>myTeam</c> is set.</summary>
public void OnMatchPlayerTeamAssigned(Team team)
{
if (!isServer || DedicatedMatchId <= 0 || string.IsNullOrEmpty(DedicatedMatchSecret))
return;
int n = FindObjectsByType<NetPlayer>(FindObjectsSortMode.None).Length;
string ts = Iso8601UtcNow();
if (team == Team.Red && !_dedicatedReportedRedJoin)
{
_dedicatedReportedRedJoin = true;
int status = n >= 2 ? 2 : 1;
string json = "{\"red_joined_at\":\"" + ts + "\",\"status\":" + status + "}";
StartCoroutine(DedicatedMatchPatch(DedicatedInternalApiBase, DedicatedMatchId, DedicatedMatchSecret, json));
// StartCoroutine(DedicatedMatchInternalApi.Patch(DedicatedInternalApiBase, DedicatedMatchId, DedicatedMatchSecret, json));
}
else if (team == Team.Blue && !_dedicatedReportedBlueJoin)
{
_dedicatedReportedBlueJoin = true;
int status = n >= 2 ? 2 : 1;
string json = "{\"blue_joined_at\":\"" + ts + "\",\"status\":" + status + "}";
StartCoroutine(DedicatedMatchPatch(DedicatedInternalApiBase, DedicatedMatchId, DedicatedMatchSecret, json));
}
}
IEnumerator DedicatedMatchPatch(string baseUrl, int matchId, string secret, string json){
if (matchId <= 0 || string.IsNullOrEmpty(secret))
yield break;
using (var req = DedicatedMatschInternalApi.BuildRequest(baseUrl, matchId, secret, json))
{
yield return req.SendWebRequest();
DedicatedMatschInternalApi.LogIfFailed(req);
}
}
public IEnumerator DedicatedMatchPatchWinner(string baseUrl, int matchId, string secret, string winnerLower)
{
if (matchId <= 0 || string.IsNullOrEmpty(secret))
yield break;
string jsonBody = "{\"winner\":\"" + winnerLower + "\"}";
using (var req = DedicatedMatschInternalApi.BuildWinnerRequest(baseUrl, matchId, secret, jsonBody))
{
yield return req.SendWebRequest();
DedicatedMatschInternalApi.LogIfFailed(req);
}
}
void ReportDedicatedMatchGameOver(Team winningTeam)
{
if (!isServer || DedicatedMatchId <= 0 || string.IsNullOrEmpty(DedicatedMatchSecret) || _dedicatedReportedGameOver)
return;
_dedicatedReportedGameOver = true;
StartCoroutine(CoReportDedicatedMatchGameOver(winningTeam));
}
IEnumerator CoReportDedicatedMatchGameOver(Team winningTeam)
{
yield return DedicatedMatchPatch(DedicatedInternalApiBase, DedicatedMatchId, DedicatedMatchSecret, "{\"status\":3}");
string winner = winningTeam == Team.Red ? "red" : "blue";
yield return DedicatedMatchPatchWinner(DedicatedInternalApiBase, DedicatedMatchId, DedicatedMatchSecret, winner);
}
void Start()
{
GameEvents.OnSelectedPuckChanged?.Invoke(null);
@@ -135,7 +247,7 @@ public class GameManager : NetworkBehaviour
[SerializeField]Puck selectedPuck;
void OnSelectedPuckChanged(Puck oldPuck, Puck newPuck){
// Debug.Log($"Selected puck changed from {oldPuck} to {newPuck}");
// Logger.Log($"Selected puck changed from {oldPuck} to {newPuck}");
GameEvents.OnSelectedPuckChanged?.Invoke(newPuck);
}
public static Puck SelectedPuck{
@@ -192,8 +304,8 @@ public class GameManager : NetworkBehaviour
coroutinePostLaunch = StartCoroutine(CoroutinePostLaunch());
curPuckPullForce = direction.magnitude;
float force = puckForce * puckForceCurve.Evaluate(curPuckPullForce);
Debug.Log($"force = {puckForce} * {puckForceCurve.Evaluate(curPuckPullForce)} = {force}");
Debug.Log($"launching puck at {direction} with {force * -direction} force");
Logger.Log($"force = {puckForce} * {puckForceCurve.Evaluate(curPuckPullForce)} = {force}");
Logger.Log($"launching puck at {direction} with {force * -direction} force");
selectedPuck.GetComponent<Rigidbody2D>().AddForce(-direction * force, ForceMode2D.Impulse);
hitCounter++;
@@ -270,14 +382,14 @@ public class GameManager : NetworkBehaviour
if(Input.GetKeyDown(KeyCode.Space)){
gameStarted = true;
GameCanvas.instance.HideWaitingForOpponentPanel();
Debug.Log("Game started On Server, EDITOR ONLY, DEBUG ONLY");
Logger.Log("Game started On Server, EDITOR ONLY, DEBUG ONLY");
}
#endif
if (players.Length == 2)
{
gameStarted = true;
GameCanvas.instance.HideWaitingForOpponentPanel();
Debug.Log("Game started On Server");
Logger.Log("Game started On Server");
}
}
}
@@ -405,7 +517,7 @@ public class GameManager : NetworkBehaviour
if(hitCounter == 1){
//kickoff goal
Debug.Log("Kickoff goal");
Logger.Log("Kickoff goal");
StartCoroutine(CoroutineOnGoal(team,true));//true = Kickoff goal, reset only the ball
hitCounter = 0;
@@ -423,9 +535,11 @@ public class GameManager : NetworkBehaviour
if(blueScore >= 3){
ReportDedicatedMatchGameOver(Team.Blue);
RpcGameOver(Team.Blue);
gameOver(Team.Blue);
}else if(redScore >= 3){
ReportDedicatedMatchGameOver(Team.Red);
RpcGameOver(Team.Red);
gameOver(Team.Red);
}else{
@@ -478,7 +592,7 @@ public class GameManager : NetworkBehaviour
}
void OnScoreChanged(int oldScore, int newScore){
Debug.Log($"Score changed from {oldScore} to {newScore}");
Logger.Log($"Score changed from {oldScore} to {newScore}");
redScoreText.text = redScore.ToString();
blueScoreText.text = blueScore.ToString();
}
@@ -505,3 +619,43 @@ public class GameManager : NetworkBehaviour
}
}
/// <summary>Headless dedicated server → matchmaker internal PATCH. Runs only where started (server-side coroutines).</summary>
static class DedicatedMatschInternalApi
{
public static UnityWebRequest BuildRequest(string baseUrl, int matchId, string secret, string jsonBody)
{
string url = baseUrl.TrimEnd('/') + "/internal/match/" + matchId;
return BuildPatchJson(url, secret, jsonBody);
}
public static UnityWebRequest BuildWinnerRequest(string baseUrl, int matchId, string secret, string jsonBody)
{
string url = baseUrl.TrimEnd('/') + "/internal/match/" + matchId + "/winner";
return BuildPatchJson(url, secret, jsonBody);
}
static UnityWebRequest BuildPatchJson(string url, string secret, string jsonBody)
{
byte[] bodyRaw = Encoding.UTF8.GetBytes(jsonBody);
var req = new UnityWebRequest(url, "PATCH")
{
uploadHandler = new UploadHandlerRaw(bodyRaw),
downloadHandler = new DownloadHandlerBuffer()
};
req.SetRequestHeader("Content-Type", "application/json");
req.SetRequestHeader("X-Dedicated-Server-Secret", secret);
return req;
}
public static void LogIfFailed(UnityWebRequest req)
{
if (req.result == UnityWebRequest.Result.Success){
Logger.Log("Dedicated match PATCH success: " + req.responseCode + " " + (req.downloadHandler != null ? req.downloadHandler.text : ""));
}else{
Logger.Log("Dedicated match PATCH failed: " + req.responseCode + " " + req.error + " " + (req.downloadHandler != null ? req.downloadHandler.text : ""));
Logger.Log("More details : " + req.url + " " + req.method + " " + req.uploadHandler.data + " " + req.downloadHandler.text);
}
}
}