using System; using System.Collections; using Core; using Data.CardSystem; using Pixelplacement; using UnityEngine; using UnityEngine.UI; namespace UI.CardSystem { /// /// UI component for granting booster packs from minigames. /// Supports awarding 1 to N booster packs with visual animations. /// Shows up to 3 booster packs visually. For more than 3, duplicates the first pack and tweens them sequentially. /// public class MinigameBoosterGiver : MonoBehaviour { public static MinigameBoosterGiver Instance { get; private set; } [Header("Visual References")] [SerializeField] private GameObject visualContainer; [SerializeField] private RectTransform[] boosterImages; // Up to 3 booster pack visuals [SerializeField] private RectTransform glowImage; // Single glow effect for all boosters [SerializeField] private Button continueButton; [Header("Animation Settings")] [SerializeField] private float hoverAmount = 20f; [SerializeField] private float hoverDuration = 1.5f; [SerializeField] private float glowPulseMax = 1.1f; [SerializeField] private float glowPulseDuration = 1.2f; [Header("Disappear Animation")] [SerializeField] private Vector2 targetBottomLeftOffset = new Vector2(100f, 100f); [SerializeField] private float tweenDuration = 0.8f; [SerializeField] private float delayBetweenTweens = 0.2f; [SerializeField] private float disappearScale = 0.2f; private Vector3[] _boosterInitialPositions; private Vector3[] _boosterInitialScales; private Vector3 _glowInitialScale; private Coroutine _currentSequence; private Action _onCompleteCallback; private int _totalPackCount = 1; private void Awake() { // Singleton pattern if (Instance != null && Instance != this) { Logging.Warning("[MinigameBoosterGiver] Duplicate instance found. Destroying."); Destroy(gameObject); return; } Instance = this; // Cache initial values for all booster images if (boosterImages != null && boosterImages.Length > 0) { _boosterInitialPositions = new Vector3[boosterImages.Length]; _boosterInitialScales = new Vector3[boosterImages.Length]; for (int i = 0; i < boosterImages.Length; i++) { if (boosterImages[i] != null) { _boosterInitialPositions[i] = boosterImages[i].localPosition; _boosterInitialScales[i] = boosterImages[i].localScale; } } } if (glowImage != null) { _glowInitialScale = glowImage.localScale; } // Setup button listener if (continueButton != null) { continueButton.onClick.AddListener(OnContinueClicked); } // Start hidden if (visualContainer != null) { visualContainer.SetActive(false); } } /// /// Initialize the booster giver with a specific pack count. /// Shows up to 3 booster visuals based on count. /// /// Number of booster packs to grant public void Initialize(int packCount) { _totalPackCount = Mathf.Max(1, packCount); // Show up to 3 booster visuals int visualCount = Mathf.Min(_totalPackCount, boosterImages.Length); for (int i = 0; i < boosterImages.Length; i++) { bool shouldShow = i < visualCount; if (boosterImages[i] != null) { boosterImages[i].gameObject.SetActive(shouldShow); } } Logging.Debug($"[MinigameBoosterGiver] Initialized with {_totalPackCount} packs, showing {visualCount} visuals"); } private void OnDestroy() { if (Instance == this) { Instance = null; } if (continueButton != null) { continueButton.onClick.RemoveListener(OnContinueClicked); } } /// /// Public API to give a booster pack. Displays UI, starts animations, and waits for user interaction. /// /// Optional callback when the sequence completes and pack is granted public void GiveBooster(Action onComplete = null) { if (_currentSequence != null) { Logging.Warning("[MinigameBoosterGiver] Already running a sequence. Ignoring new request."); return; } _onCompleteCallback = onComplete; _currentSequence = StartCoroutine(GiveBoosterSequence()); } private IEnumerator GiveBoosterSequence() { // Show the visual if (visualContainer != null) { visualContainer.SetActive(true); } // Reset positions and scales for visible boosters int visualCount = Mathf.Min(_totalPackCount, boosterImages.Length); for (int i = 0; i < visualCount; i++) { if (boosterImages[i] != null) { boosterImages[i].localPosition = _boosterInitialPositions[i]; boosterImages[i].localScale = _boosterInitialScales[i]; } } // Reset glow scale if (glowImage != null) { glowImage.localScale = _glowInitialScale; } // Enable the continue button if (continueButton != null) { continueButton.interactable = true; } // Start idle hovering animation on all visible boosters (ping-pong) for (int i = 0; i < visualCount; i++) { if (boosterImages[i] != null) { Vector3 hoverTarget = _boosterInitialPositions[i] + Vector3.up * hoverAmount; Tween.LocalPosition(boosterImages[i], hoverTarget, hoverDuration, 0f, Tween.EaseLinear, Tween.LoopType.PingPong); } } // Start pulsing animation on the single glow (ping-pong scale) if (glowImage != null) { Vector3 glowPulseScale = _glowInitialScale * glowPulseMax; Tween.LocalScale(glowImage, glowPulseScale, glowPulseDuration, 0f, Tween.EaseOut, Tween.LoopType.PingPong); } // Wait for button click (handled by OnContinueClicked) yield return null; } private void OnContinueClicked() { if (_currentSequence == null) { return; // Not in a sequence } // Disable and hide button to prevent double-clicks if (continueButton != null) { continueButton.interactable = false; continueButton.gameObject.SetActive(false); } // Start moving all boosters to backpack StartCoroutine(MoveAllBoostersToBackpack()); } private IEnumerator MoveAllBoostersToBackpack() { // Show scrapbook button temporarily using HUD visibility context PlayerHudManager.HudVisibilityContext hudContext = null; GameObject scrapbookButton = PlayerHudManager.Instance?.GetScrabookButton(); if (scrapbookButton != null) { hudContext = PlayerHudManager.Instance.ShowElementTemporarily(scrapbookButton); } else { Logging.Warning("[MinigameBoosterGiver] Scrapbook button not found in PlayerHudManager."); } // Calculate target position once Vector3 targetPosition = GetTargetPosition(scrapbookButton); int remaining = _totalPackCount; int visualCount = Mathf.Min(_totalPackCount, boosterImages.Length); // Stop and fade out the single glow immediately if (glowImage != null) { Tween.Stop(glowImage.GetInstanceID()); Tween.LocalScale(glowImage, Vector3.zero, tweenDuration * 0.5f, 0f, Tween.EaseInBack); } // Phase 1: Animate duplicates for packs above 3 (if any) int duplicatesToAnimate = Mathf.Max(0, _totalPackCount - 3); for (int i = 0; i < duplicatesToAnimate; i++) { // Spawn duplicate at the first booster's current position if (boosterImages.Length > 0 && boosterImages[0] != null) { StartCoroutine(TweenPackToBackpack(boosterImages[0], targetPosition, isDuplicate: true)); } remaining--; // Wait for stagger delay before next if (i < duplicatesToAnimate - 1 || remaining > 0) { yield return new WaitForSeconds(delayBetweenTweens); } } // Phase 2: Animate the actual visible packs (up to 3) for (int i = 0; i < visualCount && i < remaining; i++) { if (boosterImages[i] != null) { // Stop hover animations first Tween.Stop(boosterImages[i].GetInstanceID()); // Tween the actual booster StartCoroutine(TweenPackToBackpack(boosterImages[i], targetPosition, isDuplicate: false)); } // Wait for stagger delay before next (except after last one) if (i < visualCount - 1 && i < remaining - 1) { yield return new WaitForSeconds(delayBetweenTweens); } } // Wait for the last tween to complete yield return new WaitForSeconds(tweenDuration); // Grant all booster packs at once if (CardSystemManager.Instance != null) { CardSystemManager.Instance.AddBoosterPack(_totalPackCount); Logging.Debug($"[MinigameBoosterGiver] Granted {_totalPackCount} booster pack(s)!"); } else { Logging.Warning("[MinigameBoosterGiver] CardSystemManager not found, cannot grant booster packs."); } // Hide scrapbook button by disposing the context hudContext?.Dispose(); // Hide the visual container if (visualContainer != null) { visualContainer.SetActive(false); } // Show button again for next use if (continueButton != null) { continueButton.gameObject.SetActive(true); } // Invoke completion callback _onCompleteCallback?.Invoke(); _onCompleteCallback = null; // Clear sequence reference _currentSequence = null; } /// /// Generic method to tween a booster pack to the backpack button. /// Handles both duplicates (instantiated clones) and actual booster visuals. /// private IEnumerator TweenPackToBackpack(RectTransform sourceRect, Vector3 targetPosition, bool isDuplicate) { RectTransform packToAnimate; if (isDuplicate) { // Create a clone at the source position GameObject clone = Instantiate(sourceRect.gameObject, sourceRect.parent); packToAnimate = clone.GetComponent(); if (packToAnimate != null) { packToAnimate.localPosition = sourceRect.localPosition; packToAnimate.localScale = sourceRect.localScale; packToAnimate.gameObject.SetActive(true); } } else { packToAnimate = sourceRect; } if (packToAnimate == null) { yield break; } // Tween to target position Tween.LocalPosition(packToAnimate, targetPosition, tweenDuration, 0f, Tween.EaseInBack); // Scale down Vector3 targetScale = packToAnimate.localScale * disappearScale; Tween.LocalScale(packToAnimate, targetScale, tweenDuration, 0f, Tween.EaseInBack); // Don't wait here - let animations overlap // Hide/destroy the booster after tween completes (duplicate or not) // Since we destroy the entire UI instance anyway, just clean up visual artifacts yield return new WaitForSeconds(tweenDuration); if (packToAnimate != null) { packToAnimate.gameObject.SetActive(false); } yield break; } /// /// Get the target position for the booster pack animation (scrapbook button or fallback). /// private Vector3 GetTargetPosition(GameObject scrapbookButton) { if (scrapbookButton != null && boosterImages.Length > 0 && boosterImages[0] != null) { RectTransform scrapbookRect = scrapbookButton.GetComponent(); if (scrapbookRect != null) { // Convert scrapbook button's world position to local position relative to booster's parent Canvas canvas = GetComponentInParent(); if (canvas != null && canvas.renderMode == RenderMode.ScreenSpaceOverlay) { // For overlay canvas, convert screen position to local position Vector2 screenPos = RectTransformUtility.WorldToScreenPoint(null, scrapbookRect.position); RectTransformUtility.ScreenPointToLocalPointInRectangle( boosterImages[0].parent as RectTransform, screenPos, null, out Vector2 localPoint); return localPoint; } else { // For world space or camera canvas return boosterImages[0].parent.InverseTransformPoint(scrapbookRect.position); } } } // Fallback position return GetFallbackPosition(); } private Vector3 GetFallbackPosition() { RectTransform canvasRect = GetComponentInParent()?.GetComponent(); if (canvasRect != null) { // Convert bottom-left corner with offset to local position Vector2 bottomLeft = new Vector2(-canvasRect.rect.width / 2f, -canvasRect.rect.height / 2f); return bottomLeft + targetBottomLeftOffset; } else { // Ultimate fallback if no canvas found if (boosterImages.Length > 0 && _boosterInitialPositions != null && _boosterInitialPositions.Length > 0) { return _boosterInitialPositions[0] + new Vector3(-500f, -500f, 0f); } return new Vector3(-500f, -500f, 0f); } } } }