Files
soccar2d/Assets/Scripts/GameOverCanvas.cs
T
2026-07-02 11:41:57 +05:30

659 lines
21 KiB
C#

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
{
public bool showOnLoad = false;
CanvasGroup canvasGroup;
[Header("Intro items")]
public RectTransform[] middleScalingItems;
public RectTransform[] comingFromMiddle;
public RectTransform[] prizeFrames;
public RectTransform[] comingDown;
Dictionary<RectTransform, Vector3> OriginalPositions;
Dictionary<RectTransform, Vector3> OriginalScales;
[Header("Values")]
public TMP_Text p1_name;
public TMP_Text p1_l10;
public TMP_Text p2_name;
public TMP_Text p2_l10;
public GameObject p1_red,p2_red,p1_blue,p2_blue;
public Image p1_icon, p2_icon;
public Sprite redIcon, blueIcon;
public RectTransform p1_winner, p2_winner;
[Header("Game Over Data")]
public Button btnRematch;
public Button btnLeave;
public TMP_Text rematchWarningLoser;
public TMP_Text txtBetRC;
public TMP_Text txtRewardRC;
public TMP_Text txtRewardCC;
public TMP_Text countdownTxt;
string returningToMenuText;
public float returnToMenuCountdown = 15f;
/// <summary>Each player needs at least 2.0 RC (20 coin units) to rematch.</summary>
const int MinimumRematchRcCoins = 20;
[Header("Bet Limit")]
public TMP_Text txtRcBalance;
public TMP_Text txtBetLimitWarning;
public CanvasGroup betLimitAdjustPanel;
//minVal: 2, max Val 500, when its 500, show ∞ on the text
public Slider betLimitSlider;
public TMP_Text txtBetLimitSliderValue;
public Button btnBetLimitAdjustAccept;
public Button btnBetLimitAdjustCancel;
const string PersonalBetLimitPrefsKey = "WalkInvest.PersonalBetLimit";
const int BetLimitMin = 2;
const int BetLimitMax = 500;
const int BetLimitUnlimited = 500;
int rematchOpponentBetLimitCoins = BetLimitUnlimited;
[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()
{
instance = this;
canvasGroup = GetComponent<CanvasGroup>();
Hide();
CollectOriginalPositionsAndScales();
returningToMenuText = countdownTxt.text;
}
void Start()
{
if(showOnLoad){
if(LevelLoadManager.instance == null){ //Load dummy data for testing
LevelLoadManager.myPlayer = new MatchmadePlayer(){Name = "test p1", l10_wins = 3, l10_losses = 1};
LevelLoadManager.opponentPlayer = new MatchmadePlayer(){Name = "test p2", l10_wins = 1, l10_losses = 3};
}
Show(Team.Red, Team.Red, 3, 1);
}
btnRematch.onClick.AddListener(OnBtnRematchClicked);
btnLeave.onClick.AddListener(OnBtnLeaveGameClicked);
betSlider.onValueChanged.AddListener(OnBetSliderValueChanged);
btnConfirmBet.onClick.AddListener(OnBetAccepted);
btnCancelBet.onClick.AddListener(OnBetCancelled);
SetupBetLimitUi();
}
void SetupBetLimitUi()
{
if (betLimitSlider != null)
{
betLimitSlider.minValue = BetLimitMin;
betLimitSlider.maxValue = BetLimitMax;
betLimitSlider.wholeNumbers = true;
betLimitSlider.onValueChanged.AddListener(OnBetLimitSliderValueChanged);
}
if (btnBetLimitAdjustAccept != null)
btnBetLimitAdjustAccept.onClick.AddListener(OnBetLimitAdjustAccepted);
if (btnBetLimitAdjustCancel != null)
btnBetLimitAdjustCancel.onClick.AddListener(CloseBetLimitAdjuster);
var warningButton = txtBetLimitWarning != null
? txtBetLimitWarning.GetComponentInChildren<Button>()
: null;
if (warningButton != null)
warningButton.onClick.AddListener(OpenBetLimitAdjuster);
}
public static int GetPersonalBetLimit()
{
return PlayerPrefs.GetInt(PersonalBetLimitPrefsKey, BetLimitUnlimited);
}
static int EffectiveBetLimitCap(int limitCoins) =>
limitCoins >= BetLimitUnlimited ? int.MaxValue : limitCoins;
int GetRematchMaxBetCoins()
{
int maxBet = Mathf.Min(
EffectiveBetLimitCap(rematchOpponentBetLimitCoins),
GetRcCoins(LevelLoadManager.myPlayer),
GetRcCoins(LevelLoadManager.opponentPlayer));
return Mathf.Max(0, maxBet);
}
void ApplyRematchBetSliderLimits()
{
if (betSlider == null)
return;
int maxBet = GetRematchMaxBetCoins();
betSlider.maxValue = maxBet;
if (maxBet <= 0)
{
betSlider.minValue = 0;
betSlider.value = 0;
}
else
{
betSlider.minValue = Mathf.Clamp(maxBet / 10f, 5, 10000);
betSlider.value = Mathf.Clamp(maxBet / 2, betSlider.minValue, maxBet);
}
OnBetSliderValueChanged(betSlider.value);
}
static void SetPersonalBetLimit(int limitCoins)
{
limitCoins = Mathf.Clamp(limitCoins, BetLimitMin, BetLimitMax);
PlayerPrefs.SetInt(PersonalBetLimitPrefsKey, limitCoins);
PlayerPrefs.Save();
}
static string FormatBetLimitLabel(int limitCoins)
{
if (limitCoins >= BetLimitUnlimited)
return "∞";
return CoinHelper.FormatString(limitCoins) + " RC";
}
static string FormatBetLimitWarningText(int limitCoins) =>
"Max Bet : " + FormatBetLimitLabel(limitCoins);
void UpdateBetLimitWarningText()
{
if (txtBetLimitWarning != null)
txtBetLimitWarning.text = FormatBetLimitWarningText(GetPersonalBetLimit());
}
void OnBetLimitSliderValueChanged(float value)
{
if (txtBetLimitSliderValue != null)
txtBetLimitSliderValue.text = FormatBetLimitLabel(Mathf.RoundToInt(value));
}
void ApplyBetLimitVisibility()
{
bool isLoser = myTeam != winningTeam;
if (txtBetLimitWarning != null)
txtBetLimitWarning.gameObject.SetActive(isLoser);
if (!isLoser)
return;
UpdateBetLimitWarningText();
}
public void OpenBetLimitAdjuster()
{
if (betLimitAdjustPanel == null)
return;
int limit = GetPersonalBetLimit();
if (betLimitSlider != null)
betLimitSlider.SetValueWithoutNotify(limit);
OnBetLimitSliderValueChanged(limit);
betLimitAdjustPanel.DOFade(1, 0.25f);
betLimitAdjustPanel.blocksRaycasts = true;
betLimitAdjustPanel.interactable = true;
}
public void CloseBetLimitAdjuster()
{
if (betLimitAdjustPanel == null)
return;
betLimitAdjustPanel.DOFade(0, 0.25f);
betLimitAdjustPanel.blocksRaycasts = false;
betLimitAdjustPanel.interactable = false;
}
void OnBetLimitAdjustAccepted()
{
if (betLimitSlider != null)
SetPersonalBetLimit(Mathf.RoundToInt(betLimitSlider.value));
UpdateBetLimitWarningText();
CloseBetLimitAdjuster();
}
void OnBetAccepted(){
NetPlayer.localPlayer.AcceptRematch((int)betSlider.value);
rematchPanel.interactable=false;
}
void OnBetCancelled(){
NetPlayer.localPlayer.AcceptRematch(0);
rematchPanel.interactable=false;
rematchPanel.blocksRaycasts=false;
rematchPanel.DOFade(0,0.3f).SetEase(Ease.InOutBack);
}
void OnBetSliderValueChanged(float value){
txtSelectedBet.text = CoinHelper.FormatString((int)value);
}
void OnBtnRematchClicked(){
if (!BothPlayersHaveMinimumRematchRc())
return;
if (txtBetLimitWarning != null)
txtBetLimitWarning.gameObject.SetActive(false);
NetPlayer.localPlayer.RequestRematch();
SetRematchPendingState(true);
}
public void OnRematchRequested(int opponentMaxBetLimitCoins){
rematchOpponentBetLimitCoins = opponentMaxBetLimitCoins;
if(winningTeam == myTeam){
//Only the loser can initiate the rematch; winner sets the stake
ShowRematchPanel();
}else{
rematchWarningLoser.text = "Waiting for opponent to accept";
}
}
void SetRematchPendingState(bool val){
btnRematch.interactable = false;
btnLeave.interactable = !val;
if(val){
StopCoroutine(countdownCoroutine);
countdownTxt.text = "";
}else{
countdownCoroutine = StartCoroutine(CoroutineCountdown());
}
}
void OnBtnLeaveGameClicked(){
LevelLoadManager.instance.Leave();
}
void CollectOriginalPositionsAndScales(){
OriginalPositions = new Dictionary<RectTransform, Vector3>();
OriginalScales = new Dictionary<RectTransform, Vector3>();
foreach (var item in comingFromMiddle)
{
OriginalPositions.Add(item, item.position);
OriginalScales.Add(item, item.localScale);
}
foreach (var item in middleScalingItems)
{
if(!OriginalPositions.ContainsKey(item)){
OriginalPositions.Add(item, item.position);
}
if(!OriginalScales.ContainsKey(item)){
OriginalScales.Add(item, item.localScale);
}
}
foreach (var item in comingDown)
{
if(!OriginalPositions.ContainsKey(item)){
OriginalPositions.Add(item, item.position);
}
if(!OriginalScales.ContainsKey(item)){
OriginalScales.Add(item, item.localScale);
}
}
foreach (var item in prizeFrames)
{
if(!OriginalPositions.ContainsKey(item)){
OriginalPositions.Add(item, item.position);
}
if(!OriginalScales.ContainsKey(item)){
OriginalScales.Add(item, item.localScale);
}
}
}
void Hide(){
canvasGroup.alpha = 0;
canvasGroup.blocksRaycasts = false;
canvasGroup.interactable = false;
rematchPanel.alpha=0;
rematchPanel.blocksRaycasts = false;
rematchPanel.interactable = false;
if (betLimitAdjustPanel != null)
{
betLimitAdjustPanel.alpha = 0;
betLimitAdjustPanel.blocksRaycasts = false;
betLimitAdjustPanel.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){
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 = LevelLoadManager.myPlayer.Name;
p2_name.text = LevelLoadManager.opponentPlayer.Name;
p1_l10.text = $"L10 {LevelLoadManager.myPlayer.l10_wins}-{LevelLoadManager.myPlayer.l10_losses}";
p2_l10.text = $"L10 {LevelLoadManager.opponentPlayer.l10_wins}-{LevelLoadManager.opponentPlayer.l10_losses}";
if(myTeam == winningTeam){
p1_winner.gameObject.SetActive(true);
p2_winner.gameObject.SetActive(false);
}else{
p1_winner.gameObject.SetActive(false);
p2_winner.gameObject.SetActive(true);
}
ApplyBetLimitVisibility();
}
float rcPrize = 0;
public void Show(Team winningTeam, Team myTeam, int redScore, int blueScore){
Setup(winningTeam, myTeam, redScore, blueScore);
canvasGroup.blocksRaycasts = true;
canvasGroup.interactable = true;
foreach (var item in middleScalingItems)
{
item.localScale = new Vector3(0, item.localScale.y, item.localScale.z);
}
foreach (var item in comingFromMiddle)
{
item.position = new Vector3(Screen.width/2f,OriginalPositions[item].y);
item.localScale = new Vector3(0, OriginalScales[item].y, OriginalScales[item].z);
}
foreach (var item in comingDown)
{
item.position = new Vector3(OriginalPositions[item].x, 0.3f);
item.localScale = new Vector3(OriginalScales[item].x, 0, OriginalScales[item].z);
}
foreach (var item in prizeFrames)
{
item.localScale = new Vector3(0, 0, 0);
}
if (GameTutorialManager.tutorialModeEnabled)
rcPrize = 0;
else if (winningTeam == myTeam)
rcPrize = CoinHelper.Format(GameManager.MatchRcPrizeCoins);
if (btnRematch != null)
btnRematch.gameObject.SetActive(!GameTutorialManager.tutorialModeEnabled);
UpdateRematchAvailability();
ApplyRcBalancesToUi(null);
StartCoroutine(CoroutineShow());
StartCoroutine(CoroutineFetchAndUpdateBalances());
}
IEnumerator CoroutineShow(){
float fadeInTime = 0.5f;
float scaleTime = 0.2f;
float moveTime = 0.3f;
canvasGroup.DOFade(1, fadeInTime).SetEase(Ease.InOutBack);
yield return new WaitForSeconds(fadeInTime);
foreach (var item in middleScalingItems)
{
item.DOScale(OriginalScales[item], scaleTime).SetEase(Ease.InOutBack);
}
yield return new WaitForSeconds(scaleTime * 0.5f);
foreach (var item in comingFromMiddle)
{
item.DOMove(OriginalPositions[item], moveTime).SetEase(Ease.InOutBack);
item.DOScale(OriginalScales[item], scaleTime).SetEase(Ease.InOutBack);
}
yield return new WaitForSeconds(scaleTime * 0.5f);
foreach (var item in prizeFrames)
{
item.DOScale(Vector3.one, scaleTime).SetEase(Ease.InOutBack);
}
yield return new WaitForSeconds(scaleTime * 0.5f);
//Rewards
float ccReward = GameTutorialManager.tutorialModeEnabled ? 0f : 100f;
StartCoroutine(CoroutineSetTextNumber(rcPrize, txtRewardRC, 10, "N1", "+", " RC"));
StartCoroutine(CoroutineSetTextNumber(ccReward, txtRewardCC, 100, "N0", "+", " CC"));
txtBetRC.text = GameTutorialManager.tutorialModeEnabled
? "0 RC"
: CoinHelper.FormatString(GameManager.MatchRcPrizeCoins) + " RC";
yield return new WaitForSeconds(1f);
foreach (var item in comingDown)
{
item.DOMove(OriginalPositions[item], 0.3f).SetEase(Ease.InOutBack);
item.DOScale(OriginalScales[item], 0.3f).SetEase(Ease.InOutBack);
}
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;
while(t < number){
t+=Time.deltaTime*speed;
text.text = prefix + t.ToString(formatting) + suffix;
yield return null;
}
text.text = prefix + number.ToString(formatting) + suffix;
}
IEnumerator CoroutineCountdown(){
TextColorFlash textColorFlash = countdownTxt.GetComponent<TextColorFlash>();
while(returnToMenuCountdown > 0){
returnToMenuCountdown--;
countdownTxt.text = returningToMenuText.Replace("{t}", returnToMenuCountdown.ToString("N0"));
if(returnToMenuCountdown < 5){
textColorFlash.duration = 5;
}
yield return new WaitForSecondsRealtime(1f);
}
LevelLoadManager.instance.Leave();
}
public void ShowRematchPanel(){
SetRematchPendingState(true);
ApplyRematchBetSliderLimits();
StartCoroutine(CoroutineShowRematchPanel());
}
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(){
btnRematch.interactable=false;
btnLeave.interactable=true;
rematchWarningLoser.text = "Rematch declined.";
StartCoroutine(CoroutineHideRematchPanel());
countdownCoroutine = StartCoroutine(CoroutineCountdown());
}
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;
if (txtRcBalance != null)
txtRcBalance.text = loadingPlaceholder ?? (my != null ? CoinHelper.FormatString((int)my.rc) + " RC" : "0.0 RC");
if (loadingPlaceholder == null)
{
UpdateRematchAvailability();
if (rematchPanel != null && rematchPanel.alpha > 0.01f)
ApplyRematchBetSliderLimits();
}
}
static int GetRcCoins(MatchmadePlayer player) =>
player == null ? 0 : Mathf.Max(0, Mathf.FloorToInt(player.rc));
bool BothPlayersHaveMinimumRematchRc()
{
return GetRcCoins(LevelLoadManager.myPlayer) >= MinimumRematchRcCoins
&& GetRcCoins(LevelLoadManager.opponentPlayer) >= MinimumRematchRcCoins;
}
void UpdateRematchAvailability()
{
if (GameTutorialManager.tutorialModeEnabled)
{
btnRematch.interactable = false;
if (rematchWarningLoser != null)
rematchWarningLoser.text = "";
return;
}
if (winningTeam == myTeam)
{
btnRematch.interactable = false;
if (rematchWarningLoser != null)
rematchWarningLoser.text = "Only the loser can request a rematch";
return;
}
if (!BothPlayersHaveMinimumRematchRc())
{
btnRematch.interactable = false;
if (rematchWarningLoser != null)
rematchWarningLoser.text = "Not enough RC";
return;
}
btnRematch.interactable = true;
if (rematchWarningLoser != null)
rematchWarningLoser.text = "";
}
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;
}
}