using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
///
/// Отвечает за отображение панели GameOver и анимацию кнопок
///
public class GameOverUI : MonoBehaviour
{
[SerializeField] private int playerId;
[SerializeField] private GameManager gameManager;
[Header("UI Elements")]
[SerializeField] private Button replayButton;
[SerializeField] private Button continueButton;
[SerializeField] private Button menuButton; // Кнопка меню
[SerializeField] private Button claimPrizeButton; // Кнопка "Забрать приз" (показывается вместо меню при наличии приза)
[SerializeField] private GameOver gameOverStatusImage; // Надпись победы/поражения (GameOver на firstImage)
[SerializeField] private GameOver panelBackground; // Фон панели (GameOver на самой панели)
[SerializeField] private GameOver medalSwitcher; // Медальон (GameOver на secondImage)
[SerializeField] private TMP_Text notEnoughMoneyText;
[SerializeField] private TMP_Text revengeTimerText; // текст таймера реванша (60 сек)
[SerializeField] private TMP_Text replayPriceText; // текст цены реванша со скидкой (отдельный)
[Header("Animation Settings")]
[SerializeField] private Image firstImage; // Картинка надписи победы/поражения
[SerializeField] private Image secondImage; // Медальон
[SerializeField] private float popDuration = 0.3f; // Длительность "попапа" в секундах
[SerializeField] private float popScale = 1.5f; // Максимальный масштаб при "попапе"
[SerializeField] private float delayBetweenElements = 0.15f; // Задержка между появлением элементов
[SerializeField] private float buttonSlideDuration = 0.25f; // Длительность слайда кнопки
[SerializeField] private float buttonSlideOffset = 500f; // Смещение по позиции (начальное)
[SerializeField] private float pulseScale = 1.06f; // Насколько увеличивается надпись при пульсации
[SerializeField] private float pulseDuration = 1.4f; // Длительность одного цикла пульсации
[SerializeField] private float menuButtonSoloOffsetX = -100f; // Сдвиг кнопки "Меню" влево, когда нет второй кнопки (5-й бот)
private Coroutine showAnimationCoroutine;
private Coroutine pulseCoroutine;
private Coroutine revengeTimerCoroutine;
private Vector2 replayButtonOriginalPos;
private Vector2 continueButtonOriginalPos;
private Vector2 menuButtonOriginalPos;
private bool positionsCached;
private bool eventsSubscribed;
// CanvasGroup для "прозрачности" кнопок (не меняем activeSelf)
private CanvasGroup replayCg;
private CanvasGroup continueCg;
private CanvasGroup menuCg;
// Для сохранения interactable в UpdateButtons между вызовами метода
private bool lastIsBotGame;
private bool lastIsWin;
/// Приз, который можно забрать кнопкой "Забрать приз".
private Prize currentPendingPrize;
private void OnEnable()
{
CreditSystem.OnInitialize += Subscribe;
if (!eventsSubscribed && (CreditSystem.onCoinAdded != null || CreditSystem.onCoinRemoved != null))
Subscribe();
EnsureCanvasGroups();
}
private void OnDisable()
{
if (eventsSubscribed)
{
if (CreditSystem.onCoinAdded != null) CreditSystem.onCoinAdded.RemoveListener(HandleCoinsChanged);
if (CreditSystem.onCoinRemoved != null) CreditSystem.onCoinRemoved.RemoveListener(HandleCoinsChanged);
eventsSubscribed = false;
}
CreditSystem.OnInitialize -= Subscribe;
}
private void EnsureCanvasGroups()
{
replayCg = EnsureCanvasGroup(replayButton);
continueCg = EnsureCanvasGroup(continueButton);
menuCg = EnsureCanvasGroup(menuButton);
}
private static CanvasGroup EnsureCanvasGroup(Button b)
{
if (b == null) return null;
var cg = b.GetComponent();
if (cg == null) cg = b.gameObject.AddComponent();
return cg;
}
private void Subscribe()
{
if (eventsSubscribed) return;
if (CreditSystem.onCoinAdded != null) CreditSystem.onCoinAdded.AddListener(HandleCoinsChanged);
if (CreditSystem.onCoinRemoved != null) CreditSystem.onCoinRemoved.AddListener(HandleCoinsChanged);
eventsSubscribed = true;
}
private void HandleCoinsChanged(int playerID, int newAmount)
{
// Обновляем кнопки только если изменились монеты именно этого игрока или его оппонента
int oponentId = playerId == 1 ? 2 : 1;
if (playerID == playerId || playerID == oponentId)
{
UpdateButtons();
}
}
public void Show(bool isWin, bool isBotGame, Prize pendingPrize = null)
{
lastIsWin = isWin;
lastIsBotGame = isBotGame;
currentPendingPrize = pendingPrize;
// Останавливаем старую анимацию если она ещё идёт
if (showAnimationCoroutine != null)
StopCoroutine(showAnimationCoroutine);
if (pulseCoroutine != null)
{
StopCoroutine(pulseCoroutine);
pulseCoroutine = null;
}
if (revengeTimerCoroutine != null)
{
StopCoroutine(revengeTimerCoroutine);
revengeTimerCoroutine = null;
}
if (revengeTimerText != null)
revengeTimerText.gameObject.SetActive(false);
if (replayPriceText != null)
replayPriceText.gameObject.SetActive(false);
EnsureCanvasGroups();
// 1) Сразу определяем, какие кнопки видимые
ApplyButtonVisibility(isWin, isBotGame, pendingPrize);
// 2) Переключаем спрайты
if (panelBackground != null)
panelBackground.GameOverSwitch(isWin);
if (gameOverStatusImage != null)
gameOverStatusImage.GameOverSwitch(isWin);
if (medalSwitcher != null)
medalSwitcher.GameOverSwitch(isWin);
// 3) Виндиста
if (isBotGame && !isWin)
VindistaController.Instance?.SetDiscountMode(true, playerId);
UpdateButtons();
if (gameManager != null && gameManager.uiManager != null)
gameManager.uiManager.OnPanelChanged?.Invoke();
PrepVisualsForAnimation();
int credits = CreditSystem.GetPlayerCredits(playerId);
showAnimationCoroutine = StartCoroutine(ShowElementsSequence(isWin, isBotGame, credits));
// Таймер реванша — только при поражении в игре с ботом
if (isBotGame && !isWin)
revengeTimerCoroutine = StartCoroutine(RevengeTimerCoroutine());
}
private void ApplyButtonVisibility(bool isWin, bool isBotGame, Prize pendingPrize = null)
{
bool isAtFinalBoss = gameManager != null && gameManager.currentBotLevel >= 4;
// Победа над финальным боссом — только кнопка меню
// Победа в PvE (не финал) — кнопка "Продолжить"
// Всё остальное (поражение, PvP) — кнопка "Реванш"
bool showContinueButton = isBotGame && isWin && !isAtFinalBoss;
bool showReplayButton = !(isBotGame && isWin); // скрыт при любой победе в PvE
bool showClaimPrize = pendingPrize != null; // кнопка "Забрать приз" вместо "Меню"
if (replayButton != null) replayButton.gameObject.SetActive(showReplayButton);
if (continueButton != null) continueButton.gameObject.SetActive(showContinueButton);
if (menuButton != null) menuButton.gameObject.SetActive(!showClaimPrize); // скрываем "Меню" если есть приз
if (claimPrizeButton != null) claimPrizeButton.gameObject.SetActive(showClaimPrize); // показываем "Забрать приз"
UpdateNotEnoughMoneyText(false);
}
///
/// Подготовка всех визуальных элементов: делаем начально всё с alpha=0 и смещен вниз,
/// при этом activeSelf не меняем.
///
private void PrepVisualsForAnimation()
{
CacheButtonPositionsIfNeeded();
// Картинки: scale=0
if (firstImage != null) firstImage.transform.localScale = Vector3.zero;
if (secondImage != null) secondImage.transform.localScale = Vector3.zero;
// Кнопки: alpha=0 и сдвиг вниз (только если активны)
PrepButtonVisual(replayButton, replayCg, replayButtonOriginalPos);
PrepButtonVisual(continueButton, continueCg, continueButtonOriginalPos);
// Анимируем либо menuButton, либо claimPrizeButton — тот что активен
Button activeMenuBtn = (claimPrizeButton != null && claimPrizeButton.gameObject.activeSelf)
? claimPrizeButton : menuButton;
var activeCg = EnsureCanvasGroup(activeMenuBtn);
PrepButtonVisual(activeMenuBtn, activeCg, GetMenuButtonTargetPos());
}
private void CacheButtonPositionsIfNeeded()
{
if (positionsCached) return;
if (replayButton != null)
{
var rt = replayButton.GetComponent();
if (rt != null) replayButtonOriginalPos = rt.anchoredPosition;
}
if (continueButton != null)
{
var rt = continueButton.GetComponent();
if (rt != null) continueButtonOriginalPos = rt.anchoredPosition;
}
if (menuButton != null)
{
var rt = menuButton.GetComponent();
if (rt != null) menuButtonOriginalPos = rt.anchoredPosition;
}
positionsCached = true;
}
private void PrepButtonVisual(Button b, CanvasGroup cg, Vector2 original)
{
if (b == null || cg == null) return;
if (!b.gameObject.activeSelf) return;
cg.alpha = 0f;
var rt = b.GetComponent();
if (rt != null)
rt.anchoredPosition = new Vector2(original.x, original.y - buttonSlideOffset);
}
private IEnumerator ShowElementsSequence(bool isWin, bool isBotGame, int credits)
{
// 1. Первая картинка
if (firstImage != null)
{
yield return StartCoroutine(PopInElement(firstImage.transform));
yield return new WaitForSeconds(delayBetweenElements);
}
// 2. Вторая картинка
if (secondImage != null)
{
yield return StartCoroutine(PopInElement(secondImage.transform));
yield return new WaitForSeconds(delayBetweenElements);
}
// 3. Кнопки действия
if (isBotGame)
yield return StartCoroutine(ShowBotGameButtonsAnimated(isWin, credits));
else
yield return StartCoroutine(ShowPvPGameButtonsAnimated(credits));
// 4. Меню
yield return StartCoroutine(ShowMenuButtonAnimated());
// 5. Пульсация надписи победы/поражения
if (firstImage != null)
pulseCoroutine = StartCoroutine(PulseLoop(firstImage.transform));
}
private IEnumerator PulseLoop(Transform element)
{
float half = pulseDuration * 0.5f;
while (element != null)
{
// Плавно увеличиваем
float elapsed = 0f;
while (elapsed < half)
{
elapsed += Time.deltaTime;
float t = Mathf.SmoothStep(0f, 1f, elapsed / half);
element.localScale = Vector3.one * Mathf.Lerp(1f, pulseScale, t);
yield return null;
}
// Плавно уменьшаем
elapsed = 0f;
while (elapsed < half)
{
elapsed += Time.deltaTime;
float t = Mathf.SmoothStep(0f, 1f, elapsed / half);
element.localScale = Vector3.one * Mathf.Lerp(pulseScale, 1f, t);
yield return null;
}
}
}
private IEnumerator PopInElement(Transform element)
{
float elapsed = 0f;
Vector3 targetScale = Vector3.one;
while (elapsed < popDuration)
{
elapsed += Time.deltaTime;
float progress = elapsed / popDuration;
float scale;
if (progress < 0.4f)
{
scale = Mathf.Lerp(0f, popScale, progress / 0.4f);
}
else
{
float returnProgress = (progress - 0.4f) / 0.6f;
scale = Mathf.Lerp(popScale, 1f, returnProgress);
}
element.localScale = Vector3.one * scale;
yield return null;
}
element.localScale = targetScale;
}
private IEnumerator SlideInButton(RectTransform rectTransform, Vector2 targetPosition)
{
Vector2 startPosition = rectTransform.anchoredPosition;
float elapsed = 0f;
while (elapsed < buttonSlideDuration)
{
elapsed += Time.deltaTime;
float progress = elapsed / buttonSlideDuration;
float easedProgress = 1f - Mathf.Pow(1f - progress, 3f);
rectTransform.anchoredPosition = Vector2.Lerp(startPosition, targetPosition, easedProgress);
yield return null;
}
rectTransform.anchoredPosition = targetPosition;
}
private IEnumerator FadeIn(CanvasGroup cg)
{
if (cg == null) yield break;
float elapsed = 0f;
while (elapsed < buttonSlideDuration)
{
elapsed += Time.deltaTime;
float t = Mathf.Clamp01(elapsed / buttonSlideDuration);
cg.alpha = t;
yield return null;
}
cg.alpha = 1f;
}
private IEnumerator SlideAndFadeIn(Button b, CanvasGroup cg, Vector2 targetPos)
{
if (b == null || cg == null) yield break;
if (!b.gameObject.activeSelf) yield break;
var rt = b.GetComponent();
if (rt == null)
{
cg.alpha = 1f;
yield break;
}
// Одновременно: slide + fade
Coroutine slide = StartCoroutine(SlideInButton(rt, targetPos));
Coroutine fade = StartCoroutine(FadeIn(cg));
yield return slide;
yield return fade;
}
private IEnumerator ShowBotGameButtonsAnimated(bool isWin, int credits)
{
if (isWin)
{
// Continue: следующий уровень бесплатный (рефанд + оплата = 0), всегда доступна
if (continueButton != null && continueButton.gameObject.activeSelf)
{
continueButton.interactable = true;
yield return StartCoroutine(SlideAndFadeIn(continueButton, continueCg, continueButtonOriginalPos));
}
}
else
{
if (replayButton != null && replayButton.gameObject.activeSelf)
{
int fullPrice = CreditSystem.BotGamePrice;
int replayPrice = CreditSystem.GetEffectiveBotPrice(isReplay: true);
// В режиме бесплатной игры кнопка всегда доступна
replayButton.interactable = CreditSystem.IsFreePlay || credits >= replayPrice;
var txt = replayButton.GetComponentInChildren();
if (txt != null)
txt.text = "Реванш!";
// Цена со скидкой — в отдельный текст
if (replayPriceText != null)
{
if (replayPrice < fullPrice)
replayPriceText.text = $"Реванш за {fullPrice} р. {replayPrice} р.";
else
replayPriceText.text = $"Реванш за {replayPrice} р.";
replayPriceText.gameObject.SetActive(true);
}
yield return StartCoroutine(SlideAndFadeIn(replayButton, replayCg, replayButtonOriginalPos));
}
}
}
private IEnumerator ShowPvPGameButtonsAnimated(int credits)
{
if (replayButton != null && replayButton.gameObject.activeSelf)
{
int oponentId = playerId == 1 ? 2 : 1;
int oponentCredits = CreditSystem.GetPlayerCredits(oponentId);
int price = CreditSystem.PlayerGamePrice;
// В режиме бесплатной игры кнопка всегда доступна
// Проверяем в сумме — игра возможна если вместе хватает на полную стоимость
replayButton.interactable = CreditSystem.IsFreePlay ||
credits + oponentCredits >= price;
var txt = replayButton.GetComponentInChildren();
if (txt != null) txt.text = "Еще раз!";
yield return StartCoroutine(SlideAndFadeIn(replayButton, replayCg, replayButtonOriginalPos));
}
}
private IEnumerator ShowMenuButtonAnimated()
{
// Анимируем активную кнопку — либо "Забрать приз", либо "Меню"
Button activeBtn = (claimPrizeButton != null && claimPrizeButton.gameObject.activeSelf)
? claimPrizeButton : menuButton;
var activeCg = EnsureCanvasGroup(activeBtn);
if (activeBtn != null && activeBtn.gameObject.activeSelf)
yield return StartCoroutine(SlideAndFadeIn(activeBtn, activeCg, GetMenuButtonTargetPos()));
}
///
/// Возвращает true только если обе кнопки (Реванш/Продолжить) скрыты — только кнопка "Меню".
///
private bool IsOnlyMenuButtonVisible()
{
bool replayVisible = replayButton != null && replayButton.gameObject.activeSelf;
bool continueVisible = continueButton != null && continueButton.gameObject.activeSelf;
return !replayVisible && !continueVisible;
}
///
/// Целевая позиция кнопки "Меню": при solo-режиме сдвигаем влево на menuButtonSoloOffsetX.
///
private Vector2 GetMenuButtonTargetPos()
{
if (IsOnlyMenuButtonVisible())
return new Vector2(menuButtonOriginalPos.x + menuButtonSoloOffsetX, menuButtonOriginalPos.y);
return menuButtonOriginalPos;
}
public void UpdateButtons()
{
int credits = CreditSystem.GetPlayerCredits(playerId);
int oponentId = playerId == 1 ? 2 : 1;
int oponentCredits = CreditSystem.GetPlayerCredits(oponentId);
// Continue: следующий уровень бесплатный — всегда доступна
if (continueButton != null && continueButton.gameObject.activeSelf)
continueButton.interactable = true;
if (replayButton != null && replayButton.gameObject.activeSelf)
{
// В режиме бесплатной игры кнопка всегда доступна
if (CreditSystem.IsFreePlay)
{
replayButton.interactable = true;
}
else if (lastIsBotGame)
{
int replayPrice = CreditSystem.GetEffectiveBotPrice(isReplay: true);
replayButton.interactable = credits >= replayPrice;
}
else
{
int price = CreditSystem.PlayerGamePrice;
replayButton.interactable = credits + oponentCredits >= price;
}
}
// notEnoughMoneyText не используется — вместо него replayPriceText со скидкой
UpdateNotEnoughMoneyText(false);
}
private void UpdateNotEnoughMoneyText(bool show)
{
if (notEnoughMoneyText != null)
notEnoughMoneyText.gameObject.SetActive(show);
}
///
/// Вызывается кнопкой "Забрать приз". Привязать в Inspector к claimPrizeButton.
///
public void OnClaimPrizeClicked()
{
if (currentPendingPrize == null || gameManager == null) return;
Prize prize = currentPendingPrize;
currentPendingPrize = null;
// Ставим флаг — после таймера PrizeReminder вернуть в меню
var reminder = FindObjectOfType(true);
if (reminder != null)
reminder.shouldReturnToMenu = true;
if (prize.PrizeType == PrizeType.Special)
{
gameManager.uiManager.ShowOverlayAnimated("QRCode", 0f);
return;
}
if (prize.PrizeType == PrizeType.Showcase)
{
gameManager.uiManager.ShowOverlayAnimated("PrizeSelection", 0f);
return;
}
gameManager.uiManager.ShowOverlayAnimated("PrizeReminder", 0f);
PrizeSystem.GivePrize(prize, playerId);
}
private IEnumerator RevengeTimerCoroutine()
{
float remaining = 60f;
if (revengeTimerText != null)
revengeTimerText.gameObject.SetActive(true);
while (remaining > 0f)
{
if (revengeTimerText != null)
revengeTimerText.text = Mathf.CeilToInt(remaining).ToString();
yield return new WaitForSeconds(1f);
remaining -= 1f;
}
// Время вышло — скрываем таймер и уходим в меню
if (revengeTimerText != null)
revengeTimerText.gameObject.SetActive(false);
if (menuButton != null)
menuButton.onClick.Invoke();
revengeTimerCoroutine = null;
}
}