new API and rematch WIP
This commit is contained in:
@@ -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){
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user