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
+24 -4
View File
@@ -1,3 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Mirror;
@@ -10,19 +11,37 @@ public class CupidConnector : MonoBehaviour
{
#if UNITY_SERVER
//Server code
string[] args = System.Environment.GetCommandLineArgs();
string[] args = Environment.GetCommandLineArgs();
int matchId = 0;
string dedicatedSecret = "38516e39d3406225b7c3015a66e5d25c54e9b2024c0755212122bdcea3e3a328";
string internalApiBase = null;
for (int i = 0; i < args.Length; i++)
{
if (args[i].Contains("-port"))
if (args[i].Contains("-port") && i + 1 < args.Length)
{
Cupid.RoomPort = int.Parse(args[i+1]);
Cupid.RoomPort = int.Parse(args[i + 1]);
Logger.SetFileName(Cupid.RoomPort.ToString());
}
}
if (i + 1 < args.Length)
{
if (string.Equals(args[i], "-matchId", StringComparison.OrdinalIgnoreCase)
|| string.Equals(args[i], "-matchid", StringComparison.OrdinalIgnoreCase))
int.TryParse(args[i + 1], out matchId);
else if (string.Equals(args[i], "-dedicatedSecret", StringComparison.OrdinalIgnoreCase))
dedicatedSecret = args[i + 1];
else if (string.Equals(args[i], "-internalApiBase", StringComparison.OrdinalIgnoreCase))
internalApiBase = args[i + 1];
}
}
GameManager.ConfigureDedicatedMatchReporting(matchId);
if(Cupid.RoomPort < 0){
Logger.Log("Invalid port, Did you pass the -port arguement?");
return;
}
if(matchId <=0){
Logger.Log("Invalid match id, Did you pass the -matchId arguement?");
return;
}
transport.Port = (ushort)Cupid.RoomPort;
Logger.Log($"Starting server at port {Cupid.RoomPort}");
NetworkManager.singleton.StartServer();
@@ -47,6 +66,7 @@ public class CupidConnector : MonoBehaviour
}else{
if(NetworkServer.connections.Count <= 0){
Logger.Log("Closing port " + Cupid.RoomPort + " due to no players");
GameManager.ReportDedicatedMatchRoomClosed();
Application.Quit();
}
}
+72 -24
View File
@@ -12,36 +12,66 @@ public static class Cupid
private static int port;
public static int Port => port;
private static string password;
private static string bearerToken;
public static string BearerToken => bearerToken;
private static CupidSettings settings = null;
public static CupidSettings Settings => settings;
public static bool isInitialized => settings!=null;
public static bool isInitialized => settings != null;
public static int RoomPort =-1;
public static int RoomPort = -1;
public static async Task Init(string _serverAddress, int _port, string _password){
serverAddress= _serverAddress;
public static async Task Init(string _serverAddress, int _port, string _bearerToken)
{
serverAddress = _serverAddress;
port = _port;
password = _password;
bearerToken = _bearerToken ?? "";
using (UnityWebRequest www = UnityWebRequest.Get(CupidURI + "/settings?password="+password))
if (string.IsNullOrEmpty(bearerToken))
{
Logger.Log("Cupid: no bearer token; configure auth before matchmaking.");
settings = null;
return;
}
using (UnityWebRequest www = UnityWebRequest.Get(CupidURI + "/settings"))
{
www.SetRequestHeader("Authorization", "Bearer " + bearerToken);
var operation = www.SendWebRequest();
while (!operation.isDone)
{
await Task.Yield();
}
try{
settings = JsonUtility.FromJson<CupidSettings>(www.downloadHandler.text);
if(settings==null){throw new NullReferenceException();}
try
{
if (www.result != UnityWebRequest.Result.Success)
{
Logger.Log("Cupid settings request failed: " + www.error);
Logger.Log(www.downloadHandler != null ? www.downloadHandler.text : "");
settings = null;
return;
}
string body = www.downloadHandler != null ? www.downloadHandler.text : "";
if (string.IsNullOrWhiteSpace(body))
{
settings = null;
return;
}
settings = JsonUtility.FromJson<CupidSettings>(body);
if (settings == null) { throw new NullReferenceException(); }
Logger.Log("Cupid init success");
}catch(Exception e){
}
catch (Exception e)
{
Logger.Log("Error retreiving settings from server " + e.Message);
Logger.Log(www.downloadHandler.text);
Logger.Log(www.downloadHandler != null ? www.downloadHandler.text : "");
settings = null;
}
}
}
@@ -59,13 +89,25 @@ public static class Cupid
}
}
public static CupidRoom? ParseRoom(string data){
CupidRoom? room = null;
try{
room = JsonUtility.FromJson<CupidRoom>(data);
}catch{}
return room;
/// <summary>Matchmaker GET / body: <c>"0"</c> while waiting; otherwise a JSON room object.</summary>
public static CupidRoom? ParseRoom(string data)
{
if (string.IsNullOrWhiteSpace(data))
return null;
string t = data.Trim();
if (t == "0")
return null;
try
{
CupidRoom room = JsonUtility.FromJson<CupidRoom>(t);
if (room.Port <= 0)
return null;
return room;
}
catch
{
return null;
}
}
}
@@ -78,14 +120,20 @@ public class CupidSettings
}
[System.Serializable]
public struct CupidRoom{
public struct CupidRoom
{
public CupidQueueEntry[] Players;
public string GameName;
public int Port;
public uint InitTime;
public long InitTime;
public int match_id;
public string your_team;
}
[System.Serializable]
public struct CupidQueueEntry{
string Name;
uint LastSeen;
public struct CupidQueueEntry
{
public string Name;
public long LastSeen;
public int UserId;
}
+98 -52
View File
@@ -1,22 +1,24 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.SceneManagement;
public class CupidLobby : MonoBehaviour
{
public const string GAME_NAME = "soccar";
public const bool saveUsername = false;
private static string m_username = "";
[SerializeField]private GameObject MatchmakingUI;
[SerializeField]private string GameScene;
[Header("Server info")]
[Header("Server info (matchmaker port should match server settings.json)")]
[SerializeField]private string serverAddress = "xx.xx.xxx.xx";
[SerializeField]private int cupidPort = 1601;
[SerializeField]private string password = "xyz@123";
[SerializeField]private int cupidPort = 2612;
[Tooltip("Used when LoginManager is absent or has no token (e.g. Cupid sample scene).")]
[SerializeField]private string bearerTokenFallback = "";
public static string Username
{
@@ -47,11 +49,17 @@ public class CupidLobby : MonoBehaviour
void Start()
{
Cupid.Init(serverAddress, cupidPort, password);
string token = !string.IsNullOrEmpty(LoginManager.AuthToken)
? LoginManager.AuthToken
: bearerTokenFallback;
if (LoginManager.instance != null && LoginManager.CurrentUser != null && !string.IsNullOrEmpty(LoginManager.CurrentUser.username))
m_username = LoginManager.CurrentUser.username;
Cupid.Init(serverAddress, cupidPort, token);
}
bool matchmaking = false;
public void Matchmake()
{
LoginManager.ClearMatchYourTeam();
Logger.Log("Starting matchmake as " + Username);
StartCoroutine(matchmake());
}
@@ -59,63 +67,101 @@ public class CupidLobby : MonoBehaviour
IEnumerator matchmake()
{
matchmaking = true;
if (string.IsNullOrEmpty(Cupid.BearerToken))
{
Logger.Log("Matchmake aborted: no bearer token");
matchmaking = false;
RefreshMatchmakingPanel();
yield break;
}
while (matchmaking)
{
RefreshMatchmakingPanel();
WWW req = new WWW(Cupid.CupidURI + "/?password=" + password + "&&username=" + Username + $"&&game_name={GAME_NAME}");
yield return req;
// Debug.Log(req.text);
CupidRoom? room = Cupid.ParseRoom(req.text);
if(room == null){
Logger.Log("No room : " + req.text);
}else{
CupidRoom _room = (CupidRoom)room;
Logger.Log("Got into a room");
Logger.Log(req.text);
Logger.Log("Setting cupid to load game scene");
Cupid.RoomPort = _room.Port;
matchmaking=false;
Logger.Log("Loading game scene");
SceneManager.LoadScene(GameScene);
using (UnityWebRequest req = UnityWebRequest.Get(Cupid.CupidURI + "/"))
{
req.downloadHandler = new DownloadHandlerBuffer();
req.SetRequestHeader("Authorization", "Bearer " + Cupid.BearerToken);
yield return req.SendWebRequest();
string text = req.downloadHandler != null ? req.downloadHandler.text : "";
if (req.result != UnityWebRequest.Result.Success)
{
Logger.Log("Matchmaker poll failed: " + req.error + " body: " + text);
yield return new WaitForSeconds(1);
continue;
}
CupidRoom? room = Cupid.ParseRoom(text);
if (room == null)
{
if (text != null && text.Trim() != "0")
Logger.Log("Still waiting: " + text);
}
else
{
CupidRoom _room = (CupidRoom)room;
Logger.Log("Got into a room");
Logger.Log(text);
MatchmadeResponse matchmadeResponse = JsonUtility.FromJson<MatchmadeResponse>(text);
if(matchmadeResponse.Players.Length < 2){
Debug.Log("Waiting for the other player to show up");
yield return new WaitForSeconds(1);
continue;
}
Team myTeam = matchmadeResponse.your_team == "red" ? Team.Red : Team.Blue;
MatchmadePlayer myPlayer = matchmadeResponse.Players.First(p => p.UserId == LoginManager.CurrentUser.id);
MatchmadePlayer opponentPlayer = matchmadeResponse.Players.First(p => p.UserId != LoginManager.CurrentUser.id);
if(myTeam==Team.Red){
GameManager.RedPlayer = myPlayer;
GameManager.BluePlayer = opponentPlayer;
}else{
GameManager.BluePlayer = myPlayer;
GameManager.RedPlayer = opponentPlayer;
}
LevelLoadManager.instance.SetupMatchMade(myTeam, myPlayer.Name, opponentPlayer.Name, $"L10 {myPlayer.l10_wins} -{myPlayer.l10_losses}", $"L10 {opponentPlayer.l10_wins} -{opponentPlayer.l10_losses}");
GameManager.ConfigureDedicatedMatchReporting(matchmadeResponse.match_id);
Logger.Log("Setting cupid to load game scene");
Cupid.RoomPort = _room.Port;
LoginManager.SetMatchYourTeam(_room.your_team);
matchmaking = false;
Logger.Log("Loading game scene");
LevelLoadManager.LoadLevel(GameScene);
yield break;
}
}
// string[] data = req.text.Split(',');
// if(data.Length ==2){
// Logger.Log(req.text);
// if(data[0] == "1"){
// //Game started
// Logger.Log("Setting cupid room to " + data[1]);
// Cupid.RoomPort = int.Parse(data[1]);
// Logger.Log("Loading scene " + GameScene);
// SceneManager.LoadScene(GameScene);
// matchmaking=false;
// break;
// }else{
// //Game not started gotta continue
// }
// int gamePort = -1;
// try{
// gamePort = int.Parse(data[1]);
// }catch(Exception e){
// Logger.Log("Couldn't parse game port: " + req.text);
// }
// }
yield return new WaitForSeconds(1);
}
}
IEnumerator CancelMatchmake(){
WWW www = new WWW(Cupid.CupidURI + "/cancel?password=" + password + "&&username=" + Username);
yield return www;
if(www.text == "1"){
Logger.Log("Cancelled matchmaking success");
}else{
Logger.Log("Matchmaking cancellation said " + www.text);
IEnumerator CancelMatchmake()
{
if (string.IsNullOrEmpty(Cupid.BearerToken))
{
Logger.Log("Cancel skipped: no bearer token");
yield break;
}
using (UnityWebRequest www = UnityWebRequest.Get(Cupid.CupidURI + "/cancel"))
{
www.downloadHandler = new DownloadHandlerBuffer();
www.SetRequestHeader("Authorization", "Bearer " + Cupid.BearerToken);
yield return www.SendWebRequest();
string text = www.downloadHandler != null ? www.downloadHandler.text : "";
if (www.result != UnityWebRequest.Result.Success)
{
Logger.Log("Cancel request failed: " + www.error + " body: " + text);
yield break;
}
if (text.Trim() == "1")
Logger.Log("Cancelled matchmaking success");
else
Logger.Log("Matchmaking cancellation said " + text);
}
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
View File
@@ -6360,6 +6360,8 @@ MonoBehaviour:
blueColor: {r: 0.26666668, g: 0.54901963, b: 0.91372555, a: 1}
blueTurnTimer: {fileID: 103355666}
redTurnTimer: {fileID: 1585467379}
redName: {fileID: 89162709}
blueName: {fileID: 250988505}
txtWhosTurn: {fileID: 1508373957}
blueTurnTimerRound: {fileID: 1167081639}
redTurnTimerRound: {fileID: 673352631}
+3397 -1
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1528,7 +1528,7 @@ MonoBehaviour:
GameScene: Game
serverAddress: vps.playpoolstudios.com
cupidPort: 2612
password: HelloWorld
bearerTokenFallback:
--- !u!4 &613204348
Transform:
m_ObjectHideFlags: 0
+18 -2
View File
@@ -1,7 +1,8 @@
using System.Collections;
using Mirror;
using UnityEngine;
public class Ball : MonoBehaviour
public class Ball : NetworkBehaviour
{
[HideInInspector]public Rigidbody2D rb;
Vector3 startPosition;
@@ -35,8 +36,10 @@ public class Ball : MonoBehaviour
public bool touchingB,touchingL,touchingR,touchingT = false;
void Update()
{
WallDetection();
CheckNearGoal();
if(!isServer){return;}
WallDetection();
}
@@ -50,6 +53,7 @@ public class Ball : MonoBehaviour
}
}
distToGoal = closestDist;
AudioManager.instance.SetIsBallNearGoal(distToGoal < nearGoalThreshold);
}
@@ -92,13 +96,25 @@ public class Ball : MonoBehaviour
if(collision.collider.CompareTag("wall")){
AudioManager.instance.PlayWallHit(vol);
RpcWallHitAudio(vol);
}
else if(collision.collider.CompareTag("Puck")){
AudioManager.instance.PlayBallHit(vol);
RpcBallHitAudio(vol);
}
}
[ClientRpc]
void RpcWallHitAudio(float vol){
AudioManager.instance.PlayWallHit(vol);
}
[ClientRpc]
void RpcBallHitAudio(float vol){
AudioManager.instance.PlayBallHit(vol);
}
void WallDetection(){
// Linecast for each direction
Vector3 pos = transform.position;
+11 -1
View File
@@ -33,7 +33,17 @@ public class CameraEffects : MonoBehaviour
isBeingStretched = true;
}
public void SetUpsideDown(bool isUpsideDown){
if(isUpsideDown){
transform.rotation = Quaternion.Euler(0, 0, 180);
transform.position = new Vector3(transform.position.x, Mathf.Abs(transform.position.y), transform.position.z);
}else{
transform.rotation = Quaternion.Euler(0, 0, 0);
transform.position = new Vector3(transform.position.x, -Mathf.Abs(transform.position.y), transform.position.z);
}
}
public void ReleaseStretchFactor(){
stretchFactor = 0;
+6
View File
@@ -23,6 +23,7 @@ public class GameCanvas : MonoBehaviour
public Color blueColor = Color.blue;
public TMP_Text blueTurnTimer;
[FormerlySerializedAs("txtTurnTimer")] public TMP_Text redTurnTimer;
public TMP_Text redName,blueName;
public TMP_Text txtWhosTurn;
public Image blueTurnTimerRound;
[FormerlySerializedAs("turnTimerRound")] public Image redTurnTimerRound;
@@ -30,6 +31,11 @@ public class GameCanvas : MonoBehaviour
public GameObject waitingForOpponentPanel;
void Start(){
redName.text = GameManager.RedPlayer.Name;
blueName.text = GameManager.BluePlayer.Name;
}
public void ShowWaitingForOpponentPanel(){
waitingForOpponentPanel.SetActive(true);
}
+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);
}
}
}
+66 -60
View File
@@ -1,6 +1,10 @@
using System;
using System.Collections;
using System.IO;
using System.Text;
#if UNITY_EDITOR
using UnityEditor;
#endif
using TMPro;
using UnityEngine;
using UnityEngine.Networking;
@@ -9,7 +13,7 @@ using UnityEngine.UI;
public class LoginManager : MonoBehaviour
{
const string AuthBaseUrl = "http://vps.playpoolstudios.com:2612";
public const string AuthBaseUrl = "http://vps.playpoolstudios.com:2612";
const string PlayerPrefsAuthKey = "WalkInvest.AuthToken";
const int MIN_USERNAME_LENGTH = 3;
@@ -33,6 +37,33 @@ public class LoginManager : MonoBehaviour
public static string AuthToken { get; private set; }
/// <summary>Assigned side from the matchmaker <c>your_team</c> field. <c>null</c> until a room is found or after clear.</summary>
public static Team? MatchYourTeam { get; private set; }
/// <summary>Parses matchmaker strings such as <c>"red"</c> / <c>"blue"</c> (case-insensitive).</summary>
public static void SetMatchYourTeam(string matchmakerYourTeam)
{
MatchYourTeam = ParseMatchmakerTeam(matchmakerYourTeam);
}
static Team? ParseMatchmakerTeam(string s)
{
if (string.IsNullOrWhiteSpace(s))
return null;
switch (s.Trim().ToLowerInvariant())
{
case "red": return Team.Red;
case "blue": return Team.Blue;
default:
return null;
}
}
public static void ClearMatchYourTeam()
{
MatchYourTeam = null;
}
/// <summary>Latest user payload from the server. Reading this schedules a refresh (one in flight at a time).</summary>
public static UserData CurrentUser
{
@@ -49,29 +80,6 @@ public class LoginManager : MonoBehaviour
static UserData _currentUser;
bool _userDataReadRefreshPending;
[Serializable]
class AuthApiResponse
{
public bool ok;
public string error;
public string token;
}
[Serializable]
class AuthUserApiResponse
{
public bool ok;
public string error;
public UserData user;
}
[Serializable]
class PurchaseRcApiResponse
{
public bool ok;
public string error;
}
public static LoginManager instance;
void Awake()
@@ -92,13 +100,47 @@ public class LoginManager : MonoBehaviour
btnLogin.onClick.AddListener(OnLogin);
btnRegister.onClick.AddListener(OnRegister);
#if UNITY_EDITOR
// Dev: log in with clone-aware credentials instead of resuming a saved session.
AuthToken = "";
PlayerPrefs.DeleteKey(PlayerPrefsAuthKey);
PlayerPrefs.Save();
bool cloneWorkspace = EditorWorkspaceLooksLikeClone();
string devUser = cloneWorkspace ? "warlock2" : "warlock";
const string devPass = "12345678";
if (usernameInputLogin != null) usernameInputLogin.text = devUser;
if (passwordInputLogin != null) passwordInputLogin.text = devPass;
string loginJson = "{\"username\":\"" + EscapeJsonString(devUser) + "\",\"password\":\"" + EscapeJsonString(devPass) + "\"}";
StartCoroutine(AuthPostCoroutine(AuthBaseUrl + "/auth/login", loginJson, "Signed in."));
#else
if (!string.IsNullOrEmpty(AuthToken))
{
SetBusy(true);
StartCoroutine(ResumeSessionCoroutine());
}
#endif
}
#if UNITY_EDITOR
static bool EditorWorkspaceLooksLikeClone()
{
// Project folder is parent of Assets (same as editor project path).
string projectRoot = Path.GetDirectoryName(Application.dataPath);
if (!string.IsNullOrEmpty(projectRoot) &&
projectRoot.IndexOf("clone", StringComparison.OrdinalIgnoreCase) >= 0)
return true;
string product = PlayerSettings.productName;
if (!string.IsNullOrEmpty(product) &&
product.IndexOf("clone", StringComparison.OrdinalIgnoreCase) >= 0)
return true;
return false;
}
#endif
IEnumerator ResumeSessionCoroutine()
{
yield return StartCoroutine(FetchUserDataCoroutine(null, true));
@@ -433,39 +475,3 @@ public class LoginManager : MonoBehaviour
}
}
}
[Serializable]
public class UserData
{
public int id;
public string username;
public int cc;
public int rc;
public string created_at;
public string last_logged_at;
}
/// <summary>Typical <c>pack</c> ids for <see cref="LoginManager.PurchaseRcPack"/>; prefer <see cref="LoginManager.GetRcPacks"/> for the live list.</summary>
public static class RcPack
{
public const string Usd5 = "usd_5";
public const string Usd20 = "usd_20";
public const string Usd50 = "usd_50";
public static bool IsValid(string packId)
{
return packId == Usd5 || packId == Usd20 || packId == Usd50;
}
}
public static class CoinHelper{
public static float Format(int coins){
int whole = coins / 4;
int fraction = coins % 4;
return float.Parse(whole + "." + fraction);
}
public static string FormatString(int coins){
return Format(coins).ToString("N2");
}
}
+6 -1
View File
@@ -1,6 +1,11 @@
using UnityEngine;
using Mirror;
public class NetManager : NetworkManager
{
public override void OnStopServer()
{
GameManager.ReportDedicatedMatchRoomClosed();
base.OnStopServer();
}
}
+8
View File
@@ -22,11 +22,19 @@ public class NetPlayer : NetworkBehaviour
myTeam = Team.Blue;
CmdSetTeam(Team.Blue);
}
if(myTeam == Team.Blue){
CameraEffects.instance.SetUpsideDown(true);
}else{
CameraEffects.instance.SetUpsideDown(false);
}
}
[Command]
void CmdSetTeam(Team team){
myTeam = team;
if (GameManager.instance != null)
GameManager.instance.OnMatchPlayerTeamAssigned(team);
}
+10 -1
View File
@@ -130,8 +130,17 @@ public class Puck : NetworkBehaviour
Debug.Log("Puck collision speed: " + otherSpeed);
float maxVolSpeed =5f;
float vol = Mathf.Clamp(otherSpeed / maxVolSpeed,0.1f,1);
if (AudioManager.instance != null)
if (AudioManager.instance != null){
AudioManager.instance.PlayPuckHit(vol);
if(isServer){
RpcPuckHitAudio(vol);
}
}
}
[ClientRpc]
void RpcPuckHitAudio(float vol){
AudioManager.instance.PlayPuckHit(vol);
}
public void Reset(Vector3? position = null, float duration = 0.5f)
+14
View File
@@ -0,0 +1,14 @@
public static class CoinHelper
{
public static float Format(int coins)
{
int whole = coins / 4;
int fraction = coins % 4;
return float.Parse(whole + "." + fraction);
}
public static string FormatString(int coins)
{
return Format(coins).ToString("N2");
}
}
+2
View File
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5bf3c59f7c1a7e640a05edc5f9828abd
@@ -3,6 +3,7 @@ using UnityEngine.UI;
using DG.Tweening;
using UnityEngine.SceneManagement;
using System.Collections;
using TMPro;
public class LevelLoadManager : MonoBehaviour
{
public static LevelLoadManager instance;
@@ -13,6 +14,14 @@ public class LevelLoadManager : MonoBehaviour
public bool isShowing => canvasGroup.alpha == 1;
[Header("Matchmade UI")]
public GameObject matchmadeUI;
public GameObject p1_red,p2_red,p1_blue,p2_blue;
public Image p1_icon, p2_icon;
public TMP_Text p1_name, p2_name;
public TMP_Text p1_l10, p2_l10;
public Sprite redIcon, blueIcon;
void Awake()
{
if(instance != null){
@@ -24,6 +33,8 @@ public class LevelLoadManager : MonoBehaviour
DontDestroyOnLoad(gameObject);
Hide();
matchmadeUI.SetActive(false);
}
@@ -38,7 +49,11 @@ public class LevelLoadManager : MonoBehaviour
IEnumerator CoroutineLoadLevel(string levelName){
instance.Show();
yield return new WaitForSeconds(transitionDuration);
if(matchmadeUI.activeSelf){
yield return new WaitForSeconds(3f);
}
yield return SceneManager.LoadSceneAsync(levelName);
instance.Hide();
}
@@ -58,4 +73,32 @@ public class LevelLoadManager : MonoBehaviour
canvasGroup.DOFade(0, transitionDuration).SetEase(Ease.InSine);
contentImage.DOFillAmount(0, transitionDuration).SetEase(Ease.InSine);
}
public void SetupMatchMade(Team myTeam, string opponentName, string myName, string myL10, string opponentL10){
matchmadeUI.SetActive(true);
if(myTeam == Team.Red){
p1_red.SetActive(true);
p1_blue.SetActive(false);
p2_red.SetActive(false);
p2_blue.SetActive(true);
p1_icon.sprite = redIcon;
p2_icon.sprite = blueIcon;
}else{
p1_red.SetActive(false);
p1_blue.SetActive(true);
p2_red.SetActive(true);
p2_blue.SetActive(false);
p1_icon.sprite = blueIcon;
p2_icon.sprite = redIcon;
}
p1_name.text = myName;
p2_name.text = opponentName;
p1_l10.text = myL10;
p2_l10.text = opponentL10;
}
}
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: fd2602c01ff00f047aab3e93a95bb230
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+12
View File
@@ -0,0 +1,12 @@
/// <summary>Typical <c>pack</c> ids for <see cref="LoginManager.PurchaseRcPack"/>; prefer <see cref="LoginManager.GetRcPacks"/> for the live list.</summary>
public static class RcPack
{
public const string Usd5 = "usd_5";
public const string Usd20 = "usd_20";
public const string Usd50 = "usd_50";
public static bool IsValid(string packId)
{
return packId == Usd5 || packId == Usd20 || packId == Usd50;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 241f412d7db597748af9c1138e640e0e
+13
View File
@@ -0,0 +1,13 @@
using System;
using UnityEngine;
[Serializable]
public class UserData
{
public int id;
public string username;
public int cc;
public int rc;
public string created_at;
public string last_logged_at;
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7fa72c32d6a0d1e4abc0b724b1988d8c
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 031adef9a13d67c48b0969916c4e2160
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,10 @@
using System;
using UnityEngine;
[Serializable]
public class AuthApiResponse
{
public bool ok;
public string error;
public string token;
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 91e26300ebc95df47949bbd3744bf975
@@ -0,0 +1,10 @@
using System;
using UnityEngine;
[Serializable]
public class AuthUserApiResponse
{
public bool ok;
public string error;
public UserData user;
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d82de2425d5fb6448bc038942fb1aa88
@@ -0,0 +1,23 @@
using System;
using UnityEngine;
[Serializable]
public class MatchmadePlayer
{
public string Name;
public long LastSeen;
public int l10_wins;
public int l10_losses;
public int UserId;
}
[Serializable]
public class MatchmadeResponse
{
public MatchmadePlayer[] Players;
public string GameName;
public int Port;
public long InitTime;
public int match_id;
public string your_team;
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9f3314f8ed9de9c4babeefda34b3026c
@@ -0,0 +1,9 @@
using System;
using UnityEngine;
[Serializable]
public class PurchaseRcApiResponse
{
public bool ok;
public string error;
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 4aef938f509352a43920aa7bc5eb593d
@@ -10,27 +10,25 @@ MonoBehaviour:
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 15003, guid: 0000000000000000e000000000000000, type: 0}
m_Name: New Linux Server Profile
m_Name: New Linux Server Profile 1
m_EditorClassIdentifier:
m_AssetVersion: 1
m_BuildTarget: 24
m_Subtarget: 1
m_PlatformId: 91d938b35f6f4798811e41f2acf9377f
m_PlatformBuildProfile:
rid: 1400252593157439489
rid: 4982994492420521984
m_OverrideGlobalSceneList: 1
m_Scenes:
- m_enabled: 1
m_path: Assets/Scenes/Proto.unity
- m_enabled: 1
m_path: Assets/Scenes/Intro.unity
m_path: Assets/Scenes/Game.unity
m_ScriptingDefines: []
m_PlayerSettingsYaml:
m_Settings: []
references:
version: 2
RefIds:
- rid: 1400252593157439489
- rid: 4982994492420521984
type: {class: LinuxPlatformSettings, ns: UnityEditor.LinuxStandalone, asm: UnityEditor.LinuxStandalone.Extensions}
data:
m_Development: 0
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: fd612947a32a2c74481adba1225943a1
guid: 43ec01a6372fea54ab82ad24fd240078
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
+29 -2
View File
@@ -1,3 +1,30 @@
Logger initiated at 4/3/2026 7:03:10 PM
Logger initiated at 4/4/2026 11:59:58 PM
[4/3/2026 7:03:10 PM] Cupid init success
[4/4/2026 11:59:58 PM] Error retreiving settings from server JSON parse error: The document root must not follow by other values.
[4/4/2026 11:59:58 PM] 403 Unauthorized
[4/5/2026 12:00:29 AM] Starting matchmake as warlock
[4/5/2026 12:00:39 AM] Got into a room
[4/5/2026 12:00:39 AM] {"Players":[{"Name":"warlock","LastSeen":1775327439036,"UserId":4,"l10_wins":0,"l10_losses":1}],"GameName":"soccar","Port":26987,"InitTime":1775327439036,"match_id":16,"your_team":"red"}
[4/5/2026 12:00:40 AM] Got into a room
[4/5/2026 12:00:40 AM] {"Players":[{"Name":"warlock","LastSeen":1775327439036,"UserId":4,"l10_wins":0,"l10_losses":1}],"GameName":"soccar","Port":26987,"InitTime":1775327439036,"match_id":16,"your_team":"red"}
[4/5/2026 12:00:41 AM] Got into a room
[4/5/2026 12:00:41 AM] {"Players":[{"Name":"warlock","LastSeen":1775327439036,"UserId":4,"l10_wins":0,"l10_losses":1}],"GameName":"soccar","Port":26987,"InitTime":1775327439036,"match_id":16,"your_team":"red"}
[4/5/2026 12:00:46 AM] Got into a room
[4/5/2026 12:00:46 AM] {"Players":[{"Name":"warlock","LastSeen":1775327439036,"UserId":4,"l10_wins":0,"l10_losses":1},{"Name":"warlock2","LastSeen":1775327443636,"UserId":5,"l10_wins":1,"l10_losses":0}],"GameName":"soccar","Port":26987,"InitTime":1775327439036,"match_id":16,"your_team":"red"}
[4/5/2026 12:00:46 AM] Configured dedicated match reporting for match id 16 with secret 38516e39d3406225b7c3015a66e5d25c54e9b2024c0755212122bdcea3e3a328 and internal api base http://vps.playpoolstudios.com:2612
[4/5/2026 12:00:46 AM] Setting cupid to load game scene
[4/5/2026 12:00:46 AM] Loading game scene
[4/5/2026 12:00:50 AM] Starting client at $vps.playpoolstudios.com:26987
[4/5/2026 12:01:09 AM] Selected team changed from Red to Blue
[4/5/2026 12:01:15 AM] Selected team changed from Blue to Red
[4/5/2026 12:01:20 AM] Selected team changed from Red to Blue
[4/5/2026 12:01:26 AM] Selected team changed from Blue to Red
[4/5/2026 12:01:31 AM] Selected team changed from Red to Blue
[4/5/2026 12:01:38 AM] Selected team changed from Blue to Red
[4/5/2026 12:01:44 AM] Selected team changed from Red to Blue
[4/5/2026 12:01:50 AM] Selected team changed from Blue to Red
[4/5/2026 12:02:01 AM] Selected team changed from Red to Blue
[4/5/2026 12:02:08 AM] Selected team changed from Blue to Red
[4/5/2026 12:02:12 AM] Selected team changed from Red to Blue
[4/5/2026 12:02:15 AM] Selected team changed from Blue to Red
[4/5/2026 12:02:18 AM] Score changed from 0 to 1
+2 -1
View File
@@ -142,7 +142,8 @@ PlayerSettings:
visionOSBundleVersion: 1.0
tvOSBundleVersion: 1.0
bundleVersion: 0.4
preloadedAssets: []
preloadedAssets:
- {fileID: -944628639613478452, guid: 2bcd2660ca9b64942af0de543d8d7100, type: 3}
metroInputSource: 0
wsaTransparentSwapchain: 0
m_HolographicPauseOnTrackingLoss: 1