using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using TMPro; using RR.Common.Controls; using RR.Common.Managers; using RR.Common.Prizes; using RR.Common.Settings; using RR.Common.UI.FX; using RR.Game2D.Core.Managers; namespace RR.Common.UI { /// /// Контроллер баннера выигрыша. /// Модальное окно, которое отображается при победе игрока. /// public class WinBannerController : Modal { [Header("Элементы баннера выигрыша")] [SerializeField] private TextMeshProUGUI winText; [SerializeField] private Button mainMenuButton; [Header("Опциональная разметка режимов")] [Tooltip("Корневой объект обычной победной плашки. Если не задан, используется только winText.")] [SerializeField] private GameObject winContentRoot; [Tooltip("Корневой объект призовой плашки. Если не задан, используется только winText.")] [SerializeField] private GameObject prizeContentRoot; [SerializeField] private TextMeshProUGUI prizeTitleText; [SerializeField] private TextMeshProUGUI prizeDetailsText; [Header("QR-коды")] [Tooltip("Контейнер для размещения QR-кодов. QR-коды создаются динамически.")] [SerializeField] private RectTransform qrCodesContainer; [Tooltip("Корневой объект секции QR. Если не задан, используется объект контейнера QR.")] [SerializeField] private GameObject qrSectionRoot; [Header("FX")] [SerializeField] private bool pulsePrizeContent = true; [SerializeField] private float prizePulseScale = 1.08f; [SerializeField] private float prizePulseDuration = 1.2f; /// /// Событие, вызываемое при закрытии баннера выигрыша. /// public event Action WinBannerDismissed; private readonly List _createdQRCodeObjects = new(); private Coroutine _prizePulseCoroutine; private Transform _prizePulseTarget; private Vector3 _prizePulseOriginalScale; protected override void Awake() { base.Awake(); if (mainMenuButton) { mainMenuButton.onClick.AddListener(OnMainMenuClicked); } } /// /// Показывает баннер выигрыша или призовой баннер для последнего уровня. /// /// Время игры в секундах. /// Конфиг приза последнего уровня. /// Итоговый счёт за игру. /// Нужно ли показывать итоговый счёт в баннере. public void ShowWin(float gameTime, LevelPrizeConfig prizeConfig, int finalScore, bool showFinalScore) { ApplyBannerState(ResolveBannerState(gameTime, prizeConfig ?? new LevelPrizeConfig(), finalScore, showFinalScore)); Open(); } private void ApplyBannerState(BannerState state) { if (winContentRoot != null) winContentRoot.SetActive(!state.IsPrizeBanner); if (prizeContentRoot != null) prizeContentRoot.SetActive(state.IsPrizeBanner); if (winText) winText.text = state.FallbackWinText; if (prizeTitleText) prizeTitleText.text = state.PrizeTitle; if (prizeDetailsText) prizeDetailsText.text = state.PrizeDetails; if ((prizeTitleText == null || prizeDetailsText == null) && winText) winText.text = state.LegacyCombinedText; DisplayQRCodes(state.ShowQRCodes); StartPrizeHighlight(state.IsPrizeBanner); } private BannerState ResolveBannerState(float gameTime, LevelPrizeConfig prizeConfig, int finalScore, bool showFinalScore) { string formattedTime = TimeFormatHelper.FormatTime(gameTime); string scoreLine = showFinalScore ? $"\nОчки за игру: {finalScore}" : string.Empty; bool canShowQRCodes = prizeConfig.type == PrizeType.QRCode && CanDisplayBigPrizeQRCodes() && HasQRCodes(prizeConfig.qrTier); string qrTierName = GetQRTierName(prizeConfig.qrTier); string prizeStandSummary = GetPrizeStandSummary(finalScore, showFinalScore); return prizeConfig.type switch { PrizeType.QRCode when canShowQRCodes => CreatePrizeState( "Ваш приз", $"Отсканируйте {qrTierName} QR-код, чтобы получить приз.\nВремя игры: {formattedTime}{scoreLine}", $"Ваш приз: {qrTierName} QR-код\nВремя игры: {formattedTime}{scoreLine}", showQRCodes: true), PrizeType.Hopper => CreatePrizeState( "Ваш приз", $"Капсулы: {prizeConfig.hopperCapsuleCount}\nВремя игры: {formattedTime}{scoreLine}", $"Ваш приз: капсулы\nКоличество: {prizeConfig.hopperCapsuleCount}\nВремя игры: {formattedTime}{scoreLine}"), PrizeType.PrizeStand => CreatePrizeState( "Ваш приз", $"{prizeStandSummary}\nВремя игры: {formattedTime}{scoreLine}", $"Ваш приз: витрина\n{prizeStandSummary}\nВремя игры: {formattedTime}{scoreLine}"), _ => CreateWinState(formattedTime, finalScore, showFinalScore) }; } private static BannerState CreatePrizeState(string title, string details, string legacyCombinedText, bool showQRCodes = false) { return new BannerState { IsPrizeBanner = true, PrizeTitle = title, PrizeDetails = details, LegacyCombinedText = legacyCombinedText, ShowQRCodes = showQRCodes, FallbackWinText = string.Empty }; } private static BannerState CreateWinState(string formattedTime, int finalScore, bool showFinalScore) { string winText = showFinalScore ? $"Время игры: {formattedTime}\nОчки за игру: {finalScore}" : $"Время игры: {formattedTime}"; return new BannerState { IsPrizeBanner = false, PrizeTitle = string.Empty, PrizeDetails = string.Empty, LegacyCombinedText = winText, FallbackWinText = winText, ShowQRCodes = false }; } /// /// Отображает QR-коды из PrizeSystemManager, если для финального уровня назначен QR-приз. /// private void DisplayQRCodes(bool shouldDisplay) { ClearQRCodes(); if (qrCodesContainer == null) return; GetQRSectionRoot().SetActive(shouldDisplay); if (!shouldDisplay || PrizeSystemManager.Instance == null) return; QRTier tier = PrizeSystemManager.Instance.GetPrizeConfigForCurrentLevel().qrTier; Texture2D[] qrCodes = PrizeSystemManager.Instance.GetQRCodes(tier); if (qrCodes == null || qrCodes.Length == 0) { GetQRSectionRoot().SetActive(false); return; } foreach (Texture2D qrTexture in qrCodes) { if (qrTexture == null) continue; GameObject qrObject = new GameObject("QRCode", typeof(RectTransform), typeof(Image)); qrObject.transform.SetParent(qrCodesContainer, false); Sprite qrSprite = Sprite.Create( qrTexture, new Rect(0, 0, qrTexture.width, qrTexture.height), new Vector2(0.5f, 0.5f) ); Image image = qrObject.GetComponent(); image.sprite = qrSprite; image.raycastTarget = false; QRCodeTierHighlight.Apply(qrObject, tier); _createdQRCodeObjects.Add(qrObject); } } private void StartPrizeHighlight(bool isPrizeBanner) { StopPrizeHighlight(); if (!pulsePrizeContent || !isPrizeBanner) return; GameObject target = prizeContentRoot != null ? prizeContentRoot : qrSectionRoot != null ? qrSectionRoot : qrCodesContainer != null ? qrCodesContainer.gameObject : null; if (target == null || !target.activeInHierarchy) return; _prizePulseTarget = target.transform; _prizePulseOriginalScale = _prizePulseTarget.localScale; _prizePulseCoroutine = StartCoroutine(PulsePrizeContent()); } private IEnumerator PulsePrizeContent() { float elapsed = 0f; while (elapsed < prizePulseDuration && _prizePulseTarget != null) { elapsed += Time.unscaledDeltaTime; float normalized = Mathf.Clamp01(elapsed / Mathf.Max(0.01f, prizePulseDuration)); float pulse = Mathf.Sin(normalized * Mathf.PI * 2f); float scale = Mathf.Lerp(1f, prizePulseScale, Mathf.Abs(pulse)); _prizePulseTarget.localScale = _prizePulseOriginalScale * scale; yield return null; } if (_prizePulseTarget != null) _prizePulseTarget.localScale = _prizePulseOriginalScale; _prizePulseCoroutine = null; _prizePulseTarget = null; } private void StopPrizeHighlight() { if (_prizePulseCoroutine != null) { StopCoroutine(_prizePulseCoroutine); _prizePulseCoroutine = null; } if (_prizePulseTarget != null) _prizePulseTarget.localScale = _prizePulseOriginalScale; _prizePulseTarget = null; } private static bool CanDisplayBigPrizeQRCodes() { return SettingsManager.Instance != null && SettingsManager.Instance.GetBool("prizes.giveBigPrize"); } private bool HasQRCodes(QRTier tier) { if (PrizeSystemManager.Instance == null) return false; Texture2D[] qrCodes = PrizeSystemManager.Instance.GetQRCodes(tier); if (qrCodes == null || qrCodes.Length == 0) return false; foreach (Texture2D qrCode in qrCodes) { if (qrCode != null) return true; } return false; } private GameObject GetQRSectionRoot() { return qrSectionRoot != null ? qrSectionRoot : qrCodesContainer.gameObject; } /// /// Очищает созданные QR-код объекты. /// private void ClearQRCodes() { foreach (GameObject obj in _createdQRCodeObjects) { if (obj != null) { Destroy(obj); } } _createdQRCodeObjects.Clear(); } private void OnMainMenuClicked() { Close(); WinBannerDismissed?.Invoke(); if (GameManager.Instance) { GameManager.Instance.SetPause(false); GameManager.Instance.LoadScene("MainMenu", false); } } protected override void OnDisable() { base.OnDisable(); StopPrizeHighlight(); ClearQRCodes(); if (qrCodesContainer != null) GetQRSectionRoot().SetActive(true); if (winContentRoot != null) winContentRoot.SetActive(true); if (prizeContentRoot != null) prizeContentRoot.SetActive(true); } private static string GetQRTierName(QRTier tier) { return tier switch { QRTier.Bronze => "бронзовый", QRTier.Silver => "серебряный", QRTier.Gold => "золотой", _ => "обычный" }; } private static string GetPrizeStandSummary(int finalScore, bool showFinalScore) { PrizeSystemManager manager = PrizeSystemManager.Instance; if (manager == null) return "Витрина недоступна"; PrizeStandConnectionInfo connection = manager.GetPrizeStandConnectionInfo(); PrizeStandSettings settings = manager.GetPrizeStandSettings(); int unlockedCount = 0; if (settings?.boxes != null) { for (int i = 0; i < settings.boxes.Count; i++) { PrizeStandBoxConfig box = settings.boxes[i]; if (box == null) continue; bool isConnected = connection.IsConnected && (connection.ConnectedBoxesCount <= 0 || box.boxNumber <= connection.ConnectedBoxesCount); bool isUnlocked = !showFinalScore || finalScore >= box.requiredScore; if (isConnected && isUnlocked) unlockedCount++; } } string connectionText = connection.IsConnected ? $"Витрина подключена, ячеек на устройстве: {connection.ConnectedBoxesCount}" : "Витрина не подключена"; return $"{connectionText}\nЯчеек доступно по очкам: {unlockedCount}"; } private struct BannerState { public bool IsPrizeBanner; public bool ShowQRCodes; public string PrizeTitle; public string PrizeDetails; public string LegacyCombinedText; public string FallbackWinText; } } }