new API and rematch WIP

This commit is contained in:
2026-05-04 17:48:09 +05:30
parent fb0d386ba1
commit 82287d74fb
17 changed files with 5534 additions and 1258 deletions
+44 -1
View File
@@ -142,13 +142,30 @@ public static class Cupid
}
/// <summary>Matchmaker GET / body: <c>"0"</c> while waiting; otherwise a JSON room object.</summary>
public static CupidRoom? ParseRoom(string data)
public static CupidRoom? ParseRoom(string data) => ParseRoom(data, 0, null);
/// <inheritdoc cref="ParseRoom(string)"/>
/// <param name="currentUserId">Used to pick <c>for_red</c> vs <c>for_blue</c> when both list the same players.</param>
/// <param name="teamDisambiguation">Optional hint (e.g. <see cref="GameManager.MyTeam"/>) when the roster is duplicated on both sides.</param>
public static CupidRoom? ParseRoom(string data, int currentUserId, Team? teamDisambiguation)
{
if (string.IsNullOrWhiteSpace(data))
return null;
string t = data.Trim();
if (t == "0")
return null;
if (t.IndexOf("\"for_red\"", StringComparison.Ordinal) >= 0)
{
MatchmadeResponse env = JsonUtility.FromJson<MatchmadeResponse>(t);
if (env == null || !env.ok || !MatchmadeResponse.IsMatchReady(env))
return null;
MatchmadeTeamPayload branch = MatchmadeResponse.SelectTeamPayload(env, currentUserId, teamDisambiguation);
if (branch == null || branch.Port <= 0)
return null;
return ToCupidRoom(branch);
}
try
{
CupidRoom room = JsonUtility.FromJson<CupidRoom>(t);
@@ -161,6 +178,32 @@ public static class Cupid
return null;
}
}
static CupidRoom ToCupidRoom(MatchmadeTeamPayload p)
{
var room = new CupidRoom
{
GameName = p.GameName ?? "",
Port = p.Port,
InitTime = p.InitTime,
match_id = p.match_id,
your_team = p.your_team ?? ""
};
if (p.Players != null)
{
room.Players = new CupidQueueEntry[p.Players.Length];
for (int i = 0; i < p.Players.Length; i++)
{
room.Players[i] = new CupidQueueEntry
{
Name = p.Players[i].Name,
LastSeen = p.Players[i].LastSeen,
UserId = p.Players[i].UserId
};
}
}
return room;
}
}
[System.Serializable]
+23 -13
View File
@@ -1,7 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.SceneManagement;
@@ -102,7 +101,12 @@ public class CupidLobby : MonoBehaviour
continue;
}
CupidRoom? room = Cupid.ParseRoom(text);
Team? teamHint = LoginManager.MatchYourTeam;
if (!teamHint.HasValue && GameManager.instance != null)
teamHint = GameManager.MyTeam;
int userId = LoginManager.CurrentUser != null ? LoginManager.CurrentUser.id : 0;
CupidRoom? room = Cupid.ParseRoom(text, userId, teamHint);
if (room == null)
{
if (text != null && text.Trim() != "0")
@@ -113,29 +117,35 @@ public class CupidLobby : MonoBehaviour
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");
MatchmadeResponse env = JsonUtility.FromJson<MatchmadeResponse>(text);
MatchmadeTeamPayload branch = MatchmadeResponse.SelectTeamPayload(env, userId, teamHint);
if (branch == null || !MatchmadeResponse.TryResolveMyAndOpponent(env, branch, userId, out MatchmadePlayer myPlayer, out MatchmadePlayer opponentPlayer))
{
Debug.Log("Waiting for the other player to show up (could not resolve roster)");
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);
string yt = (branch.your_team ?? "").Trim().ToLowerInvariant();
Team myTeam = yt == "blue" ? Team.Blue : Team.Red;
if(myTeam==Team.Red){
if (myTeam == Team.Red)
{
GameManager.RedPlayer = myPlayer;
GameManager.BluePlayer = opponentPlayer;
}else{
}
else
{
GameManager.BluePlayer = myPlayer;
GameManager.RedPlayer = opponentPlayer;
}
GameManager.MyTeam = myTeam;
// LevelLoadManager.instance.SetupMatchMade(myTeam, myPlayer.Name, opponentPlayer.Name, $"L10 {myPlayer.l10_wins}-{myPlayer.l10_losses}", $"L10 {opponentPlayer.l10_wins}-{opponentPlayer.l10_losses}");
LevelLoadManager.instance.SetupMatchMade(myTeam, myPlayer, opponentPlayer);
GameManager.ConfigureDedicatedMatchReporting(matchmadeResponse.match_id);
int rcPrizeCoins = env.rc_prize > 0 ? env.rc_prize : branch.rc_prize;
GameManager.MatchRcPrizeCoins = rcPrizeCoins;
LevelLoadManager.instance.SetupMatchMade(myTeam, myPlayer, opponentPlayer, CoinHelper.FormatString(rcPrizeCoins) + " RC");
GameManager.ConfigureDedicatedMatchReporting(branch.match_id);
Logger.Log("Setting cupid to load game scene");
Cupid.RoomPort = _room.Port;
LoginManager.SetMatchYourTeam(_room.your_team);
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
File diff suppressed because one or more lines are too long
+61 -56
View File
@@ -1,57 +1,62 @@
Logger initiated at 5/2/2026 10:17:33 PM
Logger initiated at 5/4/2026 5:31:47 PM
[5/2/2026 10:17:33 PM] Cupid init success
[5/2/2026 10:17:33 PM] Starting matchmake as warlock2
[5/2/2026 10:17:34 PM] Starting matchmake as warlock
[5/2/2026 10:17:38 PM] Got into a room
[5/2/2026 10:17:38 PM] {"Players":[{"Name":"warlock","LastSeen":1777740457910,"UserId":4,"l10_wins":1,"l10_losses":0}],"GameName":"soccar","Port":26494,"InitTime":1777740457910,"match_id":236,"your_team":"red"}
[5/2/2026 10:17:38 PM] Got into a room
[5/2/2026 10:17:38 PM] {"Players":[{"Name":"warlock","LastSeen":1777740457910,"UserId":4,"l10_wins":1,"l10_losses":0},{"Name":"warlock2","LastSeen":1777740458378,"UserId":5,"l10_wins":0,"l10_losses":1}],"GameName":"soccar","Port":26494,"InitTime":1777740457910,"match_id":236,"instance_started":true,"your_team":"blue"}
[5/2/2026 10:17:38 PM] Configured dedicated match reporting for match id 236 with secret 38516e39d3406225b7c3015a66e5d25c54e9b2024c0755212122bdcea3e3a328 and internal api base https://kickkingsapi.playpoolstudios.com
[5/2/2026 10:17:38 PM] Setting cupid to load game scene
[5/2/2026 10:17:38 PM] Loading game scene
[5/2/2026 10:17:39 PM] Got into a room
[5/2/2026 10:17:39 PM] {"Players":[{"Name":"warlock","LastSeen":1777740459663,"UserId":4,"l10_wins":1,"l10_losses":0},{"Name":"warlock2","LastSeen":1777740458378,"UserId":5,"l10_wins":0,"l10_losses":1}],"GameName":"soccar","Port":26494,"InitTime":1777740457910,"match_id":236,"instance_started":true,"your_team":"red"}
[5/2/2026 10:17:39 PM] Configured dedicated match reporting for match id 236 with secret 38516e39d3406225b7c3015a66e5d25c54e9b2024c0755212122bdcea3e3a328 and internal api base https://kickkingsapi.playpoolstudios.com
[5/2/2026 10:17:39 PM] Setting cupid to load game scene
[5/2/2026 10:17:39 PM] Loading game scene
[5/2/2026 10:17:45 PM] Starting client at $kickkingsapi.playpoolstudios.com:26494
[5/2/2026 10:17:45 PM] Client connected
[5/2/2026 10:17:50 PM] Starting client at $kickkingsapi.playpoolstudios.com:26494
[5/2/2026 10:17:50 PM] Client connected
[5/2/2026 10:17:59 PM] Selected team changed from Red to Blue
[5/2/2026 10:17:59 PM] Selected team changed from Red to Blue
[5/2/2026 10:18:09 PM] Selected team changed from Blue to Red
[5/2/2026 10:18:10 PM] Selected team changed from Blue to Red
[5/2/2026 10:18:15 PM] Selected team changed from Red to Blue
[5/2/2026 10:18:15 PM] Selected team changed from Red to Blue
[5/2/2026 10:18:21 PM] Selected team changed from Blue to Red
[5/2/2026 10:18:21 PM] Selected team changed from Blue to Red
[5/2/2026 10:18:25 PM] Selected team changed from Red to Blue
[5/2/2026 10:18:25 PM] Selected team changed from Red to Blue
[5/2/2026 10:18:33 PM] Selected team changed from Blue to Red
[5/2/2026 10:18:33 PM] Selected team changed from Blue to Red
[5/2/2026 10:18:37 PM] Score changed from 0 to 1
[5/2/2026 10:18:37 PM] Score changed from 0 to 1
[5/2/2026 10:18:40 PM] Selected team changed from Red to Blue
[5/2/2026 10:18:40 PM] Selected team changed from Red to Blue
[5/2/2026 10:18:48 PM] Selected team changed from Blue to Red
[5/2/2026 10:18:48 PM] Selected team changed from Blue to Red
[5/2/2026 10:18:54 PM] Score changed from 1 to 2
[5/2/2026 10:18:54 PM] Score changed from 1 to 2
[5/2/2026 10:18:57 PM] Selected team changed from Red to Blue
[5/2/2026 10:18:57 PM] Selected team changed from Red to Blue
[5/2/2026 10:19:05 PM] Selected team changed from Blue to Red
[5/2/2026 10:19:05 PM] Selected team changed from Blue to Red
[5/2/2026 10:19:11 PM] Selected team changed from Red to Blue
[5/2/2026 10:19:12 PM] Selected team changed from Red to Blue
[5/2/2026 10:19:19 PM] Selected team changed from Blue to Red
[5/2/2026 10:19:19 PM] Selected team changed from Blue to Red
[5/2/2026 10:19:27 PM] Selected team changed from Red to Blue
[5/2/2026 10:19:27 PM] Selected team changed from Red to Blue
[5/2/2026 10:19:34 PM] Selected team changed from Blue to Red
[5/2/2026 10:19:34 PM] Score changed from 2 to 3
[5/2/2026 10:19:34 PM] Selected team changed from Blue to Red
[5/2/2026 10:19:34 PM] Score changed from 2 to 3
[5/2/2026 10:19:37 PM] Client disconnected
[5/2/2026 10:19:38 PM] Client disconnected
[5/4/2026 5:31:47 PM] Cupid init success
[5/4/2026 5:31:48 PM] Starting matchmake as warlock2
[5/4/2026 5:31:49 PM] Starting matchmake as warlock
[5/4/2026 5:31:52 PM] Still waiting: {"ok":true,"entry_fee":21,"rc_prize":37,"for_red":{"Players":[{"Name":"warlock2","LastSeen":1777896112344,"UserId":5,"l10_wins":3,"l10_losses":4}],"GameName":"soccar","Port":26825,"InitTime":1777896112344,"entry_fee":21,"match_id":248,"rc_prize":37,"your_team":"red"},"for_blue":null}
[5/4/2026 5:31:53 PM] Got into a room
[5/4/2026 5:31:53 PM] {"ok":true,"entry_fee":21,"rc_prize":37,"for_red":{"Players":[{"Name":"warlock2","LastSeen":1777896112344,"UserId":5,"l10_wins":3,"l10_losses":4},{"Name":"warlock","LastSeen":1777896113172,"UserId":4,"l10_wins":4,"l10_losses":3}],"GameName":"soccar","Port":26825,"InitTime":1777896112344,"entry_fee":21,"match_id":248,"instance_started":true,"rc_prize":37,"your_team":"red"},"for_blue":{"Players":[{"Name":"warlock2","LastSeen":1777896112344,"UserId":5,"l10_wins":3,"l10_losses":4},{"Name":"warlock","LastSeen":1777896113172,"UserId":4,"l10_wins":4,"l10_losses":3}],"GameName":"soccar","Port":26825,"InitTime":1777896112344,"entry_fee":21,"match_id":248,"instance_started":true,"rc_prize":37,"your_team":"blue"}}
[5/4/2026 5:31:53 PM] Configured dedicated match reporting for match id 248 with secret 38516e39d3406225b7c3015a66e5d25c54e9b2024c0755212122bdcea3e3a328 and internal api base https://kickkingsapi.playpoolstudios.com
[5/4/2026 5:31:53 PM] Setting cupid to load game scene
[5/4/2026 5:31:53 PM] Loading game scene
[5/4/2026 5:31:53 PM] Got into a room
[5/4/2026 5:31:53 PM] {"ok":true,"entry_fee":21,"rc_prize":37,"for_red":{"Players":[{"Name":"warlock2","LastSeen":1777896113824,"UserId":5,"l10_wins":3,"l10_losses":4},{"Name":"warlock","LastSeen":1777896113172,"UserId":4,"l10_wins":4,"l10_losses":3}],"GameName":"soccar","Port":26825,"InitTime":1777896112344,"entry_fee":21,"match_id":248,"instance_started":true,"rc_prize":37,"your_team":"red"},"for_blue":{"Players":[{"Name":"warlock2","LastSeen":1777896113824,"UserId":5,"l10_wins":3,"l10_losses":4},{"Name":"warlock","LastSeen":1777896113172,"UserId":4,"l10_wins":4,"l10_losses":3}],"GameName":"soccar","Port":26825,"InitTime":1777896112344,"entry_fee":21,"match_id":248,"instance_started":true,"rc_prize":37,"your_team":"blue"}}
[5/4/2026 5:31:53 PM] Configured dedicated match reporting for match id 248 with secret 38516e39d3406225b7c3015a66e5d25c54e9b2024c0755212122bdcea3e3a328 and internal api base https://kickkingsapi.playpoolstudios.com
[5/4/2026 5:31:53 PM] Setting cupid to load game scene
[5/4/2026 5:31:53 PM] Loading game scene
[5/4/2026 5:32:00 PM] Starting client at $kickkingsapi.playpoolstudios.com:26825
[5/4/2026 5:32:00 PM] Client connected
[5/4/2026 5:32:00 PM] Starting client at $kickkingsapi.playpoolstudios.com:26825
[5/4/2026 5:32:00 PM] Client connected
[5/4/2026 5:32:08 PM] Selected team changed from Red to Blue
[5/4/2026 5:32:08 PM] Selected team changed from Red to Blue
[5/4/2026 5:32:14 PM] Selected team changed from Blue to Red
[5/4/2026 5:32:14 PM] Selected team changed from Blue to Red
[5/4/2026 5:32:20 PM] Selected team changed from Red to Blue
[5/4/2026 5:32:20 PM] Selected team changed from Red to Blue
[5/4/2026 5:32:25 PM] Selected team changed from Blue to Red
[5/4/2026 5:32:26 PM] Selected team changed from Blue to Red
[5/4/2026 5:32:28 PM] Score changed from 0 to 1
[5/4/2026 5:32:28 PM] Score changed from 0 to 1
[5/4/2026 5:32:31 PM] Selected team changed from Red to Blue
[5/4/2026 5:32:31 PM] Selected team changed from Red to Blue
[5/4/2026 5:32:39 PM] Selected team changed from Blue to Red
[5/4/2026 5:32:39 PM] Selected team changed from Blue to Red
[5/4/2026 5:32:45 PM] Selected team changed from Red to Blue
[5/4/2026 5:32:45 PM] Selected team changed from Red to Blue
[5/4/2026 5:32:52 PM] Selected team changed from Blue to Red
[5/4/2026 5:32:52 PM] Selected team changed from Blue to Red
[5/4/2026 5:32:57 PM] Selected team changed from Red to Blue
[5/4/2026 5:32:57 PM] Selected team changed from Red to Blue
[5/4/2026 5:33:08 PM] Selected team changed from Blue to Red
[5/4/2026 5:33:08 PM] Selected team changed from Blue to Red
[5/4/2026 5:33:13 PM] Selected team changed from Red to Blue
[5/4/2026 5:33:13 PM] Selected team changed from Red to Blue
[5/4/2026 5:33:17 PM] Score changed from 0 to 1
[5/4/2026 5:33:17 PM] Score changed from 0 to 1
[5/4/2026 5:33:20 PM] Selected team changed from Blue to Red
[5/4/2026 5:33:20 PM] Selected team changed from Blue to Red
[5/4/2026 5:33:29 PM] Selected team changed from Red to Blue
[5/4/2026 5:33:30 PM] Selected team changed from Red to Blue
[5/4/2026 5:33:35 PM] Selected team changed from Blue to Red
[5/4/2026 5:33:35 PM] Selected team changed from Blue to Red
[5/4/2026 5:33:39 PM] Score changed from 1 to 2
[5/4/2026 5:33:39 PM] Score changed from 1 to 2
[5/4/2026 5:33:42 PM] Selected team changed from Red to Blue
[5/4/2026 5:33:42 PM] Selected team changed from Red to Blue
[5/4/2026 5:33:52 PM] Selected team changed from Blue to Red
[5/4/2026 5:33:52 PM] Selected team changed from Blue to Red
[5/4/2026 5:33:57 PM] Score changed from 2 to 3
[5/4/2026 5:33:57 PM] Score changed from 2 to 3
[5/4/2026 5:34:39 PM] Client disconnected
[5/4/2026 5:34:42 PM] Client disconnected
+5 -4
View File
@@ -2590,7 +2590,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 0
m_IsActive: 1
--- !u!224 &784446025213843339
RectTransform:
m_ObjectHideFlags: 0
@@ -2906,9 +2906,9 @@ RectTransform:
m_Children: []
m_Father: {fileID: 741608775212071875}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 1}
m_AnchorMax: {x: 0.5, y: 1}
m_AnchoredPosition: {x: 0, y: -999.55}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 80.45001}
m_SizeDelta: {x: 419.1856, y: 93.29602}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &3617049431174458592
@@ -3628,6 +3628,7 @@ MonoBehaviour:
redIcon: {fileID: 7194123030578827194, guid: 809c8d780dc80f14e80fd92c6e42ed71, type: 3}
blueIcon: {fileID: 7609527126711406816, guid: 8d015c6e4a5ab8540814b06ae5f85980, type: 3}
txtMiddle: {fileID: 2458004521057696282}
txtRcPrize: {fileID: 8330661879995437874}
p1_winner: {fileID: 5375017959079975307}
p2_winner: {fileID: 2911971058065672229}
--- !u!225 &7794655613667813618
+2743 -5
View File
File diff suppressed because it is too large Load Diff
-20
View File
@@ -4859,22 +4859,6 @@ PrefabInstance:
serializedVersion: 3
m_TransformParent: {fileID: 0}
m_Modifications:
- target: {fileID: 3194354901675590681, guid: 169bd8967120a614da3d7b0e9c71557c, type: 3}
propertyPath: m_IsActive
value: 1
objectReference: {fileID: 0}
- target: {fileID: 4033947976462218865, guid: 169bd8967120a614da3d7b0e9c71557c, type: 3}
propertyPath: m_AnchorMax.y
value: 0.5
objectReference: {fileID: 0}
- target: {fileID: 4033947976462218865, guid: 169bd8967120a614da3d7b0e9c71557c, type: 3}
propertyPath: m_AnchorMin.y
value: 0.5
objectReference: {fileID: 0}
- target: {fileID: 4033947976462218865, guid: 169bd8967120a614da3d7b0e9c71557c, type: 3}
propertyPath: m_AnchoredPosition.y
value: 80.45001
objectReference: {fileID: 0}
- target: {fileID: 6417757714012981459, guid: 169bd8967120a614da3d7b0e9c71557c, type: 3}
propertyPath: m_Pivot.x
value: 0
@@ -4955,10 +4939,6 @@ PrefabInstance:
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7133780496080864158, guid: 169bd8967120a614da3d7b0e9c71557c, type: 3}
propertyPath: m_IsActive
value: 1
objectReference: {fileID: 0}
- target: {fileID: 8578419198713715619, guid: 169bd8967120a614da3d7b0e9c71557c, type: 3}
propertyPath: m_Name
value: LevelLoader
+4 -4
View File
@@ -3306,8 +3306,8 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 102.62}
m_AnchoredPosition: {x: 164.3, y: -73.801}
m_SizeDelta: {x: 95.636, y: 102.62}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &830868269
MonoBehaviour:
@@ -3481,8 +3481,8 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 102.6215}
m_AnchoredPosition: {x: 47.817, y: -73.8}
m_SizeDelta: {x: 95.6344, y: 102.6215}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &874144767
MonoBehaviour:
+4
View File
@@ -37,6 +37,7 @@ public class GameCanvas : MonoBehaviour
public TMP_Text blueTurnTimer;
[FormerlySerializedAs("txtTurnTimer")] public TMP_Text redTurnTimer;
public TMP_Text redName,blueName;
public TMP_Text txtRcPrize;
public TMP_Text txtWhosTurn;
public Image blueTurnTimerRound;
[FormerlySerializedAs("turnTimerRound")] public Image redTurnTimerRound;
@@ -87,6 +88,9 @@ public class GameCanvas : MonoBehaviour
}catch{
Logger.Log("Failed to fetch red and blue names, isServer?");
}
if (txtRcPrize != null)
txtRcPrize.text = CoinHelper.FormatString(GameManager.MatchRcPrizeCoins) + " RC";
}
void OnEmoteTextPressed(string txtName){
+57 -2
View File
@@ -14,6 +14,8 @@ public class GameManager : NetworkBehaviour
public static Team MyTeam { get; set; }
public static MatchmadePlayer RedPlayer { get; set; }
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>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>
@@ -257,7 +259,7 @@ public class GameManager : NetworkBehaviour
using (var req = DedicatedMatschInternalApi.BuildWinnerRequest(baseUrl, matchId, secret, jsonBody))
{
yield return req.SendWebRequest();
DedicatedMatschInternalApi.LogIfFailed(req);
DedicatedMatschInternalApi.LogWinnerPatchResult(req);
}
}
@@ -688,7 +690,7 @@ public class GameManager : NetworkBehaviour
yield return new WaitForSeconds(2f);
yield return CoroutineFetchBothPlayersL10();
StopClient();
// StopClient();
LevelLoadManager.instance.SetupGameOver(team,redScore,blueScore);
GameOverCanvas.instance.Show(team, MyTeam, redScore, blueScore);
@@ -810,6 +812,9 @@ public class GameManager : NetworkBehaviour
freezeInput=false;
}
}
/// <summary>Headless dedicated server → matchmaker internal PATCH. Runs only where started (server-side coroutines).</summary>
@@ -850,4 +855,54 @@ static class DedicatedMatschInternalApi
Logger.Log("More details : " + req.url + " " + req.method + " " + req.uploadHandler.data + " " + req.downloadHandler.text);
}
}
/// <summary>Handles <c>PATCH /internal/match/:id/winner</c>: 200 parses economy; 409 idempotent (already paid).</summary>
public static void LogWinnerPatchResult(UnityWebRequest req)
{
long code = req.responseCode;
string body = req.downloadHandler != null ? req.downloadHandler.text : "";
if (code == 200 && req.result == UnityWebRequest.Result.Success)
{
try
{
var parsed = JsonUtility.FromJson<DedicatedWinnerPatchResponse>(body);
if (parsed != null && parsed.ok && parsed.economy != null)
GameManager.MatchRcPrizeCoins = parsed.economy.rc_prize;
}
catch (Exception e)
{
Logger.Log("Dedicated winner PATCH: could not parse economy: " + e.Message + " body=" + body);
}
Logger.Log("Dedicated match winner PATCH success: " + code + " " + body);
return;
}
if (code == 409)
{
Logger.Log("Dedicated match winner PATCH: winner already recorded (409 idempotent), no second payout. " + body);
return;
}
Logger.Log("Dedicated match winner PATCH failed: " + code + " " + req.error + " " + body);
Logger.Log("More details : " + req.url + " " + req.method + " " + body);
}
}
[Serializable]
class DedicatedWinnerPatchEconomy
{
public int entry_fee_rc;
public int rc_prize;
public int participant_cc;
}
[Serializable]
class DedicatedWinnerPatchResponse
{
public bool ok;
public int id;
public int winner_id;
public DedicatedWinnerPatchEconomy economy;
}
+153 -6
View File
@@ -1,8 +1,10 @@
using UnityEngine;
using DG.Tweening;
using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine.Networking;
using UnityEngine.UI;
public class GameOverCanvas : MonoBehaviour
{
@@ -28,7 +30,7 @@ public class GameOverCanvas : MonoBehaviour
public Sprite redIcon, blueIcon;
public RectTransform p1_winner, p2_winner;
[Header("Rematch")]
[Header("Game Over Data")]
public Button btnRematch;
public Button btnLeave;
public GameObject rematchWarningLoser;
@@ -39,6 +41,17 @@ public class GameOverCanvas : MonoBehaviour
public float returnToMenuCountdown = 15f;
[Header("Rematch panel")]
public CanvasGroup rematchPanel;
public RectTransform rematchPopup;
public TMP_Text txtMyRCBalance, txtOpponentRCBalance;
public TMP_Text txtSelectedBet;
public Slider betSlider;
public Button btnConfirmBet;
public Button btnCancelBet;
public static GameOverCanvas instance;
void Awake()
@@ -64,11 +77,38 @@ public class GameOverCanvas : MonoBehaviour
btnRematch.onClick.AddListener(OnBtnRematchClicked);
btnLeave.onClick.AddListener(OnBtnLeaveGameClicked);
betSlider.onValueChanged.AddListener(OnBetSliderValueChanged);
}
void OnBetSliderValueChanged(float value){
txtSelectedBet.text = CoinHelper.FormatString((int)value);
}
void OnBtnRematchClicked(){
// LevelLoadManager.LoadLevel("Game");
NetPlayer.localPlayer.RequestRematch();
SetRematchPendingState(true);
}
public void OnRematchRequested(){
if(winningTeam == myTeam){
//Only i can initiate the rematch, so here we go
ShowRematchPanel();
}
}
void SetRematchPendingState(bool val){
btnRematch.interactable = false;
btnLeave.interactable = !val;
if(val){
StopCoroutine(countdownCoroutine);
}else{
countdownCoroutine = StartCoroutine(CoroutineCountdown());
}
}
void OnBtnLeaveGameClicked(){
LevelLoadManager.LoadLevel("MainMenu");
}
@@ -115,10 +155,17 @@ public class GameOverCanvas : MonoBehaviour
canvasGroup.alpha = 0;
canvasGroup.blocksRaycasts = false;
canvasGroup.interactable = false;
rematchPanel.alpha=0;
rematchPanel.blocksRaycasts = false;
rematchPanel.interactable = false;
}
Team winningTeam;
Team myTeam;
public void Setup(Team winningTeam, Team myTeam, int redScore, int blueScore){
this.winningTeam = winningTeam;
this.myTeam = myTeam;
p1_winner.gameObject.SetActive(false);
p2_winner.gameObject.SetActive(false);
if(myTeam == Team.Red){
@@ -184,10 +231,12 @@ public class GameOverCanvas : MonoBehaviour
rcPrize = 8.2f;
}
btnRematch.interactable = winningTeam == myTeam;
rematchWarningLoser.SetActive(winningTeam != myTeam);
btnRematch.interactable = winningTeam != myTeam;
rematchWarningLoser.SetActive(winningTeam == myTeam);
StartCoroutine(CoroutineShow());
StartCoroutine(CoroutineFetchAndUpdateBalances());
}
IEnumerator CoroutineShow(){
@@ -232,9 +281,9 @@ public class GameOverCanvas : MonoBehaviour
StartCoroutine(CoroutineCountdown());
countdownCoroutine = StartCoroutine(CoroutineCountdown());
}
Coroutine countdownCoroutine;
IEnumerator CoroutineSetTextNumber(float number, TMP_Text text, float speed = 100f, string formatting = "N0", string prefix = "", string suffix = ""){
float t=0;
@@ -260,4 +309,102 @@ public class GameOverCanvas : MonoBehaviour
LevelLoadManager.LoadLevel("MainMenu");
}
public void ShowRematchPanel(){
SetRematchPendingState(true);
StartCoroutine(CoroutineShowRematchPanel());
betSlider.maxValue = (LevelLoadManager.myPlayer.rc < LevelLoadManager.opponentPlayer.rc) ? LevelLoadManager.myPlayer.rc : LevelLoadManager.opponentPlayer.rc;
}
IEnumerator CoroutineShowRematchPanel(){
rematchPopup.localScale = new Vector3(0, 0, 0);
rematchPanel.blocksRaycasts = true;
rematchPanel.interactable = true;
rematchPanel.DOFade(1, 0.3f).SetEase(Ease.InOutBack);
yield return new WaitForSeconds(0.1f);
rematchPopup.DOScale(1, 0.2f).SetEase(Ease.OutBack);
yield return new WaitForSeconds(0.2f);
}
public void OnRematchCancelled(){
SetRematchPendingState(false);
StartCoroutine(CoroutineHideRematchPanel());
}
IEnumerator CoroutineHideRematchPanel(){
rematchPopup.DOScale(0, 0.2f).SetEase(Ease.InBack);
yield return new WaitForSeconds(0.2f);
rematchPanel.DOFade(0, 0.3f).SetEase(Ease.InOutBack);
yield return new WaitForSeconds(0.3f);
}
IEnumerator CoroutineFetchAndUpdateBalances()
{
var my = LevelLoadManager.myPlayer;
var opp = LevelLoadManager.opponentPlayer;
string baseUrl = (GameManager.DedicatedInternalApiBase ?? "").TrimEnd('/');
if (string.IsNullOrEmpty(baseUrl) || my == null || opp == null)
{
ApplyRcBalancesToUi(null);
yield break;
}
if (my.UserId > 0)
yield return FetchPlayerRcAndApply(baseUrl, my);
if (opp.UserId > 0 && opp.UserId != my.UserId)
yield return FetchPlayerRcAndApply(baseUrl, opp);
ApplyRcBalancesToUi(null);
}
void ApplyRcBalancesToUi(string loadingPlaceholder)
{
var my = LevelLoadManager.myPlayer;
var opp = LevelLoadManager.opponentPlayer;
string myText = loadingPlaceholder ?? (my != null ? CoinHelper.FormatString((int)my.rc) : "0.0 RC");
string oppText = loadingPlaceholder ?? (opp != null ? CoinHelper.FormatString((int)opp.rc) : "0.0 RC");
if (txtMyRCBalance != null)
txtMyRCBalance.text = myText;
if (txtOpponentRCBalance != null)
txtOpponentRCBalance.text = oppText;
}
IEnumerator FetchPlayerRcAndApply(string baseUrl, MatchmadePlayer player)
{
string url = $"{baseUrl}/players/{player.UserId}/rc";
using (var req = UnityWebRequest.Get(url))
{
yield return req.SendWebRequest();
if (req.responseCode != 200)
{
Logger.Log($"Player RC fetch HTTP {(int)req.responseCode}: {req.error} {url}");
yield break;
}
string text = req.downloadHandler != null ? req.downloadHandler.text : "";
if (string.IsNullOrEmpty(text))
yield break;
var resp = JsonUtility.FromJson<PlayerRcApiResponse>(text);
if (resp != null && resp.ok)
player.rc = resp.rc;
else if (resp != null && !string.IsNullOrEmpty(resp.error))
Logger.Log($"Player RC fetch: {resp.error} ({url})");
}
}
[Serializable]
class PlayerRcApiResponse
{
public bool ok;
public int player_id;
public float rc;
public string error;
}
}
+63
View File
@@ -216,4 +216,67 @@ public class NetPlayer : NetworkBehaviour
void RpcOnPointerUp(){
}
/// <summary>
/// Request a rematch with the opponent.
/// </summary>
public void RequestRematch(){
if(isServer){
GameOverCanvas.instance.OnRematchRequested();
RpcRequestRematch();
}else{
CmdRequestRematch();
}
}
[Command]
void CmdRequestRematch(){
Logger.Log("Rematch requested");
if(isServerOnly){
//server
}else{
GameOverCanvas.instance.OnRematchRequested();
}
RpcRequestRematch();
}
[ClientRpc]
void RpcRequestRematch(){
GameOverCanvas.instance.OnRematchRequested();
}
public void AcceptRematch(float betRc){
if(isServer){
OnRematchAccepted(betRc);
RpcAcceptRematch(betRc);
}else{
CmdAcceptRematch(betRc);
}
}
[Command]
void CmdAcceptRematch(float betRc){
OnRematchAccepted(betRc);
RpcAcceptRematch(betRc);
}
[ClientRpc]
void RpcAcceptRematch(float betRc){
OnRematchAccepted(betRc);
}
void OnRematchAccepted(float betRc){
//Implement here
if(betRc == 0){
//Cancel signal
GameOverCanvas.instance.OnRematchCancelled();
return;
}
}
}
@@ -22,6 +22,7 @@ public class LevelLoadManager : MonoBehaviour
public TMP_Text p1_l10, p2_l10;
public Sprite redIcon, blueIcon;
public TMP_Text txtMiddle;
public TMP_Text txtRcPrize;
[Header("Game Over UI")]
public GameObject p1_winner;
public GameObject p2_winner;
@@ -84,7 +85,7 @@ public class LevelLoadManager : MonoBehaviour
}
Team myTeam = Team.Red;
public void SetupMatchMade(Team myTeam_, string opponentName, string myName, string myL10, string opponentL10){
public void SetupMatchMade(Team myTeam_, string opponentName, string myName, string myL10, string opponentL10, string rcPrize){
myTeam = myTeam_;
matchmadeUI.SetActive(true);
p1_winner.SetActive(false);
@@ -113,23 +114,24 @@ public class LevelLoadManager : MonoBehaviour
p2_name.text = myName;
p1_l10.text = myL10;
p2_l10.text = opponentL10;
txtRcPrize.text = rcPrize;
}
public static MatchmadePlayer myPlayer;
public static MatchmadePlayer opponentPlayer;
public void SetupMatchMade(Team myTeam_, MatchmadePlayer myPlayer_, MatchmadePlayer opponentPlayer_){
public void SetupMatchMade(Team myTeam_, MatchmadePlayer myPlayer_, MatchmadePlayer opponentPlayer_, string rcPrize){
myTeam = myTeam_;
myPlayer = myPlayer_;
opponentPlayer = opponentPlayer_;
string opponentName = opponentPlayer_.Name;
string myName = myPlayer_.Name;
string myL10 = $"L10 {myPlayer.l10_wins} -{myPlayer.l10_losses}";
string opponentL10 = $"L10 {opponentPlayer.l10_wins} -{opponentPlayer.l10_losses}";
SetupMatchMade(myTeam, myName, opponentName, myL10, opponentL10);
SetupMatchMade(myTeam, myName, opponentName, myL10, opponentL10, rcPrize);
}
bool gameOver = false;
public void SetupGameOver(Team winningTeam, int redScore, int blueScore){
@@ -9,15 +9,246 @@ public class MatchmadePlayer
public int l10_wins;
public int l10_losses;
public int UserId;
/// <summary>Rocket Credits from server (e.g. GET /players/:id/rc). Cached client-side.</summary>
public float rc;
}
/// <summary>Per-team block from matchmaker GET / when a side is in queue or match is ready.</summary>
[Serializable]
public class MatchmadeResponse
public class MatchmadeTeamPayload
{
public MatchmadePlayer[] Players;
public string GameName;
public int Port;
public long InitTime;
public bool instance_started;
public int match_id;
public int entry_fee;
public int rc_prize;
public string your_team;
}
/// <summary>Envelope from matchmaker fill / rematch success (and in-progress queue state).</summary>
[Serializable]
public class MatchmadeResponse
{
public bool ok;
public int entry_fee;
public int rc_prize;
public MatchmadeTeamPayload for_red;
public MatchmadeTeamPayload for_blue;
/// <summary>Optional root field: which nested block is for this client when both <see cref="for_red"/> and <see cref="for_blue"/> list the same players.</summary>
public string your_team;
public static bool TeamPayloadHasPort(MatchmadeTeamPayload p) =>
p != null && p.Port > 0;
/// <summary>True when both sides are present on the same dedicated instance (2v2-style duplicate rosters).</summary>
public static bool IsMatchReadyDuplicateRosters(MatchmadeResponse e)
{
if (e == null || !e.ok)
return false;
if (!TeamPayloadHasPort(e.for_red) || !TeamPayloadHasPort(e.for_blue))
return false;
if (e.for_red.Players == null || e.for_blue.Players == null)
return false;
if (e.for_red.match_id != e.for_blue.match_id || e.for_red.Port != e.for_blue.Port)
return false;
return e.for_red.Players.Length >= 2 && e.for_blue.Players.Length >= 2;
}
/// <summary>True when each side lists at least one player and together they form a full 1v1 (disjoint user sets, same instance).</summary>
public static bool IsMatchReadySplitRosters(MatchmadeResponse e)
{
if (e == null || !e.ok)
return false;
if (!TeamPayloadHasPort(e.for_red) || !TeamPayloadHasPort(e.for_blue))
return false;
if (e.for_red.Players == null || e.for_blue.Players == null)
return false;
if (e.for_red.match_id != e.for_blue.match_id || e.for_red.Port != e.for_blue.Port)
return false;
if (e.for_red.Players.Length < 1 || e.for_blue.Players.Length < 1)
return false;
int a = e.for_red.Players[0].UserId;
int b = e.for_blue.Players[0].UserId;
if (a == b)
return false;
if (e.for_red.Players.Length == 1 && e.for_blue.Players.Length == 1)
return true;
// Wider payloads: require no user id appearing on both sides
foreach (var pr in e.for_red.Players)
{
foreach (var pb in e.for_blue.Players)
{
if (pr.UserId == pb.UserId)
return false;
}
}
return true;
}
public static bool IsMatchReady(MatchmadeResponse e) =>
IsMatchReadyDuplicateRosters(e) || IsMatchReadySplitRosters(e);
static bool PayloadContainsUserId(MatchmadeTeamPayload p, int userId)
{
if (p?.Players == null || userId == 0)
return false;
for (int i = 0; i < p.Players.Length; i++)
{
if (p.Players[i].UserId == userId)
return true;
}
return false;
}
static int IndexOfUserId(MatchmadePlayer[] players, int userId)
{
if (players == null || userId == 0)
return -1;
for (int i = 0; i < players.Length; i++)
{
if (players[i].UserId == userId)
return i;
}
return -1;
}
/// <summary>Duplicate roster payloads: same users in the same order on red and blue blocks.</summary>
static bool SamePlayerOrder(MatchmadePlayer[] a, MatchmadePlayer[] b)
{
if (a == null || b == null || a.Length != b.Length)
return false;
for (int i = 0; i < a.Length; i++)
{
if (a[i].UserId != b[i].UserId)
return false;
}
return true;
}
/// <summary>
/// When <see cref="for_red"/> and <see cref="for_blue"/> list the same roster in the same order (1v1),
/// matchmaker convention: index 0 = red team, index 1 = blue team.
/// </summary>
static MatchmadeTeamPayload SelectDuplicateRosterBySlot(MatchmadeResponse env, int userId)
{
MatchmadePlayer[] r = env.for_red?.Players;
MatchmadePlayer[] b = env.for_blue?.Players;
if (r == null || b == null || r.Length != 2 || b.Length != 2)
return null;
if (!SamePlayerOrder(r, b))
return null;
int idx = IndexOfUserId(r, userId);
if (idx == 0)
return env.for_red;
if (idx == 1)
return env.for_blue;
return null;
}
/// <summary>Pick the nested block for this client (port, your_team, players for UI).</summary>
public static MatchmadeTeamPayload SelectTeamPayload(MatchmadeResponse env, int userId, Team? teamDisambiguation = null)
{
if (env == null)
return null;
if (!string.IsNullOrEmpty(env.your_team))
{
string y = env.your_team.Trim().ToLowerInvariant();
if (y == "red")
return env.for_red;
if (y == "blue")
return env.for_blue;
}
bool onRed = PayloadContainsUserId(env.for_red, userId);
bool onBlue = PayloadContainsUserId(env.for_blue, userId);
if (onRed && !onBlue)
return env.for_red;
if (onBlue && !onRed)
return env.for_blue;
if (onRed && onBlue)
{
MatchmadeTeamPayload bySlot = SelectDuplicateRosterBySlot(env, userId);
if (bySlot != null)
return bySlot;
if (teamDisambiguation.HasValue)
{
if (teamDisambiguation.Value == Team.Red)
return env.for_red;
if (teamDisambiguation.Value == Team.Blue)
return env.for_blue;
}
Debug.LogWarning("MatchmadeResponse: both sides list this user but roster is not 1v1 duplicate order; set root your_team on the server or pass team disambiguation.");
}
return env.for_red;
}
/// <summary>Resolve self and opponent from the selected branch (duplicate full roster) or from both sides (split 1v1 rosters).</summary>
public static bool TryResolveMyAndOpponent(MatchmadeResponse env, MatchmadeTeamPayload branch, int userId, out MatchmadePlayer myPlayer, out MatchmadePlayer opponentPlayer)
{
myPlayer = null;
opponentPlayer = null;
if (env == null || branch == null || branch.Players == null || userId == 0)
return false;
MatchmadeTeamPayload other = ReferenceEquals(branch, env.for_blue) ? env.for_red : env.for_blue;
if (branch.Players.Length >= 2)
{
for (int i = 0; i < branch.Players.Length; i++)
{
if (branch.Players[i].UserId == userId)
{
myPlayer = branch.Players[i];
break;
}
}
if (myPlayer == null)
return false;
for (int i = 0; i < branch.Players.Length; i++)
{
if (branch.Players[i].UserId != myPlayer.UserId)
{
opponentPlayer = branch.Players[i];
break;
}
}
return opponentPlayer != null;
}
for (int i = 0; i < branch.Players.Length; i++)
{
if (branch.Players[i].UserId == userId)
{
myPlayer = branch.Players[i];
break;
}
}
if (myPlayer == null && branch.Players.Length == 1 && branch.Players[0].UserId == userId)
myPlayer = branch.Players[0];
if (other?.Players == null)
return false;
for (int i = 0; i < other.Players.Length; i++)
{
if (other.Players[i].UserId != userId)
{
opponentPlayer = other.Players[i];
break;
}
}
if (opponentPlayer == null && other.Players.Length == 1)
opponentPlayer = other.Players[0];
return myPlayer != null && opponentPlayer != null && myPlayer.UserId != opponentPlayer.UserId;
}
}