530 lines
20 KiB
C#
530 lines
20 KiB
C#
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using AppleHills.Data.CardSystem;
|
||
using Data.CardSystem;
|
||
using Pixelplacement;
|
||
using UI.Core;
|
||
using UI.CardSystem.DragDrop;
|
||
using UI.DragAndDrop.Core;
|
||
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
|
||
namespace UI.CardSystem
|
||
{
|
||
/// <summary>
|
||
/// UI page for opening booster packs and displaying the cards received.
|
||
/// Manages the entire booster opening flow with drag-and-drop interaction.
|
||
/// </summary>
|
||
public class BoosterOpeningPage : UIPage
|
||
{
|
||
[Header("UI References")]
|
||
[SerializeField] private CanvasGroup canvasGroup;
|
||
[SerializeField] private Button closeButton;
|
||
|
||
[Header("Booster Management")]
|
||
[SerializeField] private GameObject[] boosterPackInstances; // Booster prefabs/instances
|
||
[SerializeField] private SlotContainer bottomRightSlots; // Holds waiting boosters
|
||
[SerializeField] private DraggableSlot centerOpeningSlot; // Where booster goes to open
|
||
|
||
[Header("Card Display")]
|
||
[SerializeField] private Transform cardDisplayContainer;
|
||
[SerializeField] private GameObject flippableCardPrefab; // Placeholder for card backs
|
||
[SerializeField] private float cardSpacing = 150f;
|
||
|
||
[Header("Settings")]
|
||
[SerializeField] private float cardRevealDelay = 0.5f;
|
||
[SerializeField] private float boosterDisappearDuration = 0.5f;
|
||
|
||
private int _availableBoosterCount;
|
||
private BoosterPackDraggable _currentBoosterInCenter;
|
||
private List<GameObject> _currentRevealedCards = new List<GameObject>();
|
||
private CardData[] _currentCardData;
|
||
private int _revealedCardCount;
|
||
private bool _isProcessingOpening;
|
||
|
||
private void Awake()
|
||
{
|
||
// Make sure we have a CanvasGroup for transitions
|
||
if (canvasGroup == null)
|
||
canvasGroup = GetComponent<CanvasGroup>();
|
||
if (canvasGroup == null)
|
||
canvasGroup = gameObject.AddComponent<CanvasGroup>();
|
||
|
||
// Set up close button
|
||
if (closeButton != null)
|
||
{
|
||
closeButton.onClick.AddListener(OnCloseButtonClicked);
|
||
}
|
||
|
||
// UI pages should start disabled
|
||
gameObject.SetActive(false);
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
if (closeButton != null)
|
||
{
|
||
closeButton.onClick.RemoveListener(OnCloseButtonClicked);
|
||
}
|
||
|
||
// Unsubscribe from slot events
|
||
if (centerOpeningSlot != null)
|
||
{
|
||
centerOpeningSlot.OnOccupied -= OnBoosterPlacedInCenter;
|
||
}
|
||
|
||
// Unsubscribe from booster events
|
||
UnsubscribeFromAllBoosters();
|
||
}
|
||
|
||
private void OnCloseButtonClicked()
|
||
{
|
||
if (UIPageController.Instance != null)
|
||
{
|
||
UIPageController.Instance.PopPage();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Set the number of available booster packs before showing the page
|
||
/// </summary>
|
||
public void SetAvailableBoosterCount(int count)
|
||
{
|
||
_availableBoosterCount = count;
|
||
}
|
||
|
||
public override void TransitionIn()
|
||
{
|
||
base.TransitionIn();
|
||
InitializeBoosterDisplay();
|
||
}
|
||
|
||
public override void TransitionOut()
|
||
{
|
||
CleanupPage();
|
||
base.TransitionOut();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Initialize the booster pack display based on available count
|
||
/// </summary>
|
||
private void InitializeBoosterDisplay()
|
||
{
|
||
Debug.Log($"[BoosterOpeningPage] InitializeBoosterDisplay called with {_availableBoosterCount} boosters available");
|
||
|
||
if (boosterPackInstances == null || boosterPackInstances.Length == 0)
|
||
{
|
||
Debug.LogWarning("BoosterOpeningPage: No booster pack instances assigned!");
|
||
return;
|
||
}
|
||
|
||
// Calculate how many boosters to show (capped by array size)
|
||
int visibleCount = Mathf.Min(_availableBoosterCount, boosterPackInstances.Length);
|
||
|
||
Debug.Log($"[BoosterOpeningPage] Will show {visibleCount} boosters out of {boosterPackInstances.Length} instances");
|
||
|
||
// Show/hide boosters and assign to slots
|
||
for (int i = 0; i < boosterPackInstances.Length; i++)
|
||
{
|
||
if (boosterPackInstances[i] == null) continue;
|
||
|
||
bool shouldShow = i < visibleCount;
|
||
Debug.Log($"[BoosterOpeningPage] Booster {i} ({boosterPackInstances[i].name}): shouldShow={shouldShow}, position={boosterPackInstances[i].transform.position}");
|
||
|
||
boosterPackInstances[i].SetActive(shouldShow);
|
||
|
||
if (shouldShow)
|
||
{
|
||
// Get the booster draggable component
|
||
BoosterPackDraggable booster = boosterPackInstances[i].GetComponent<BoosterPackDraggable>();
|
||
if (booster != null)
|
||
{
|
||
// Reset state
|
||
booster.ResetTapCount();
|
||
booster.SetTapToOpenEnabled(false); // Disable tap-to-open until in center
|
||
|
||
// Subscribe to events
|
||
booster.OnReadyToOpen += OnBoosterReadyToOpen;
|
||
|
||
// Assign to bottom-right slot if slots available
|
||
if (bottomRightSlots != null && i < bottomRightSlots.SlotCount)
|
||
{
|
||
DraggableSlot slot = bottomRightSlots.GetSlotAtIndex(i);
|
||
if (slot != null)
|
||
{
|
||
Debug.Log($"[BoosterOpeningPage] Assigning booster {i} to slot {slot.name} at {slot.transform.position}");
|
||
booster.AssignToSlot(slot, false);
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning($"[BoosterOpeningPage] Slot {i} is null in bottomRightSlots!");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning($"[BoosterOpeningPage] No slot available for booster {i}. bottomRightSlots={bottomRightSlots}, SlotCount={bottomRightSlots?.SlotCount}");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning($"[BoosterOpeningPage] Booster {i} has no BoosterPackDraggable component!");
|
||
}
|
||
}
|
||
}
|
||
|
||
// Subscribe to center slot events
|
||
if (centerOpeningSlot != null)
|
||
{
|
||
centerOpeningSlot.OnOccupied += OnBoosterPlacedInCenter;
|
||
Debug.Log($"[BoosterOpeningPage] Subscribed to center slot {centerOpeningSlot.name} at {centerOpeningSlot.transform.position}");
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning("[BoosterOpeningPage] centerOpeningSlot is null!");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Handle when a booster is placed in the center opening slot
|
||
/// </summary>
|
||
private void OnBoosterPlacedInCenter(DraggableObject draggable)
|
||
{
|
||
BoosterPackDraggable booster = draggable as BoosterPackDraggable;
|
||
if (booster == null) return;
|
||
|
||
_currentBoosterInCenter = booster;
|
||
|
||
// Lock the slot so it can't be dragged out
|
||
centerOpeningSlot.SetLocked(true);
|
||
|
||
// Configure booster for opening (disables drag, enables tapping, resets tap count)
|
||
booster.SetInOpeningSlot(true);
|
||
|
||
// Subscribe to tap events for visual feedback
|
||
booster.OnTapped += OnBoosterTapped;
|
||
booster.OnReadyToOpen += OnBoosterReadyToOpen;
|
||
|
||
Debug.Log($"[BoosterOpeningPage] Booster placed in center, ready for {booster.CurrentTapCount} taps");
|
||
}
|
||
|
||
private void OnBoosterTapped(BoosterPackDraggable booster, int currentTaps, int maxTaps)
|
||
{
|
||
Debug.Log($"[BoosterOpeningPage] Booster tapped: {currentTaps}/{maxTaps}");
|
||
|
||
// Calculate shake intensity (increases with each tap)
|
||
float shakeIntensity = currentTaps / (float)maxTaps;
|
||
float shakeAmount = 10f + (shakeIntensity * 30f); // 10 to 40 units
|
||
|
||
// TODO: Shake visual feedback
|
||
// This would be handled by BoosterPackVisual if we add a shake method
|
||
}
|
||
|
||
/// <summary>
|
||
/// Handle tap-to-place: When player taps a booster in bottom slots, move it to center
|
||
/// </summary>
|
||
public void OnBoosterTappedInBottomSlot(BoosterPackDraggable booster)
|
||
{
|
||
if (_currentBoosterInCenter != null || centerOpeningSlot == null)
|
||
return; // Center slot already occupied
|
||
|
||
// Move booster to center slot
|
||
booster.AssignToSlot(centerOpeningSlot, true);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Handle when booster is ready to open (after max taps)
|
||
/// </summary>
|
||
private void OnBoosterReadyToOpen(BoosterPackDraggable booster)
|
||
{
|
||
if (_isProcessingOpening) return;
|
||
|
||
Debug.Log($"[BoosterOpeningPage] Booster ready to open!");
|
||
|
||
// Trigger the actual opening sequence
|
||
booster.TriggerOpen();
|
||
StartCoroutine(ProcessBoosterOpening(booster));
|
||
}
|
||
|
||
/// <summary>
|
||
/// Process the booster opening sequence
|
||
/// </summary>
|
||
private IEnumerator ProcessBoosterOpening(BoosterPackDraggable booster)
|
||
{
|
||
_isProcessingOpening = true;
|
||
|
||
// Call CardSystemManager to open the pack
|
||
if (CardSystemManager.Instance != null)
|
||
{
|
||
List<CardData> revealedCardsList = CardSystemManager.Instance.OpenBoosterPack();
|
||
_currentCardData = revealedCardsList.ToArray();
|
||
|
||
// Animate booster disappearing
|
||
yield return StartCoroutine(AnimateBoosterDisappear(booster));
|
||
|
||
// Show card backs
|
||
SpawnCardBacks(_currentCardData.Length);
|
||
|
||
// Wait for player to reveal all cards
|
||
yield return StartCoroutine(WaitForCardReveals());
|
||
|
||
// Check if more boosters available
|
||
_availableBoosterCount--;
|
||
|
||
if (_availableBoosterCount > 0)
|
||
{
|
||
// Show next booster
|
||
yield return StartCoroutine(ShowNextBooster());
|
||
}
|
||
else
|
||
{
|
||
// No more boosters, auto-close page
|
||
yield return new WaitForSeconds(1f);
|
||
if (UIPageController.Instance != null)
|
||
{
|
||
UIPageController.Instance.PopPage();
|
||
}
|
||
}
|
||
}
|
||
|
||
_isProcessingOpening = false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Animate the booster pack disappearing
|
||
/// </summary>
|
||
private IEnumerator AnimateBoosterDisappear(BoosterPackDraggable booster)
|
||
{
|
||
if (booster == null) yield break;
|
||
|
||
// Scale down and fade out
|
||
Transform boosterTransform = booster.transform;
|
||
|
||
Tween.LocalScale(boosterTransform, Vector3.zero, boosterDisappearDuration, 0f, Tween.EaseInBack);
|
||
|
||
// Also fade the visual if it has a CanvasGroup
|
||
CanvasGroup boosterCg = booster.GetComponentInChildren<CanvasGroup>();
|
||
if (boosterCg != null)
|
||
{
|
||
Tween.Value(1f, 0f, (val) => boosterCg.alpha = val, boosterDisappearDuration, 0f);
|
||
}
|
||
|
||
yield return new WaitForSeconds(boosterDisappearDuration);
|
||
|
||
// Destroy the booster
|
||
Destroy(booster.gameObject);
|
||
_currentBoosterInCenter = null;
|
||
|
||
// Unlock center slot
|
||
centerOpeningSlot.SetLocked(false);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Spawn card back placeholders for revealing
|
||
/// </summary>
|
||
private void SpawnCardBacks(int count)
|
||
{
|
||
if (flippableCardPrefab == null || cardDisplayContainer == null)
|
||
{
|
||
Debug.LogWarning("BoosterOpeningPage: Missing card prefab or container!");
|
||
return;
|
||
}
|
||
|
||
_currentRevealedCards.Clear();
|
||
_revealedCardCount = 0;
|
||
|
||
// Calculate positions
|
||
float totalWidth = (count - 1) * cardSpacing;
|
||
float startX = -totalWidth / 2f;
|
||
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
GameObject cardObj = Instantiate(flippableCardPrefab, cardDisplayContainer);
|
||
RectTransform cardRect = cardObj.GetComponent<RectTransform>();
|
||
|
||
if (cardRect != null)
|
||
{
|
||
cardRect.anchoredPosition = new Vector2(startX + (i * cardSpacing), 0);
|
||
}
|
||
|
||
// Add button to handle reveal on click
|
||
Button cardButton = cardObj.GetComponent<Button>();
|
||
if (cardButton == null)
|
||
{
|
||
cardButton = cardObj.AddComponent<Button>();
|
||
}
|
||
|
||
int cardIndex = i; // Capture for closure
|
||
cardButton.onClick.AddListener(() => OnCardClicked(cardIndex, cardObj));
|
||
|
||
_currentRevealedCards.Add(cardObj);
|
||
|
||
// Animate cards flying in
|
||
cardRect.localScale = Vector3.zero;
|
||
Tween.LocalScale(cardRect, Vector3.one, 0.3f, i * 0.1f, Tween.EaseOutBack);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Handle card click to reveal
|
||
/// </summary>
|
||
private void OnCardClicked(int cardIndex, GameObject cardObj)
|
||
{
|
||
if (cardIndex >= _currentCardData.Length) return;
|
||
|
||
// Flip/reveal animation (placeholder - just show card data for now)
|
||
CardDisplay cardDisplay = cardObj.GetComponent<CardDisplay>();
|
||
if (cardDisplay != null)
|
||
{
|
||
cardDisplay.SetupCard(_currentCardData[cardIndex]);
|
||
}
|
||
|
||
// Disable button so it can't be clicked again
|
||
Button cardButton = cardObj.GetComponent<Button>();
|
||
if (cardButton != null)
|
||
{
|
||
cardButton.interactable = false;
|
||
}
|
||
|
||
// Scale punch animation
|
||
Tween.LocalScale(cardObj.transform, Vector3.one * 1.2f, 0.15f, 0f, Tween.EaseOutBack,
|
||
completeCallback: () => {
|
||
Tween.LocalScale(cardObj.transform, Vector3.one, 0.15f, 0f, Tween.EaseInBack);
|
||
});
|
||
|
||
_revealedCardCount++;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Wait until all cards are revealed
|
||
/// </summary>
|
||
private IEnumerator WaitForCardReveals()
|
||
{
|
||
while (_revealedCardCount < _currentCardData.Length)
|
||
{
|
||
yield return null;
|
||
}
|
||
|
||
// All cards revealed, wait a moment
|
||
yield return new WaitForSeconds(1f);
|
||
|
||
// Clear cards
|
||
foreach (GameObject card in _currentRevealedCards)
|
||
{
|
||
if (card != null)
|
||
{
|
||
// Animate out
|
||
Tween.LocalScale(card.transform, Vector3.zero, 0.3f, 0f, Tween.EaseInBack,
|
||
completeCallback: () => Destroy(card));
|
||
}
|
||
}
|
||
|
||
_currentRevealedCards.Clear();
|
||
|
||
yield return new WaitForSeconds(0.5f);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Show the next booster pack
|
||
/// </summary>
|
||
private IEnumerator ShowNextBooster()
|
||
{
|
||
// Find the next inactive booster and activate it
|
||
for (int i = 0; i < boosterPackInstances.Length; i++)
|
||
{
|
||
if (boosterPackInstances[i] != null && !boosterPackInstances[i].activeSelf)
|
||
{
|
||
boosterPackInstances[i].SetActive(true);
|
||
|
||
BoosterPackDraggable booster = boosterPackInstances[i].GetComponent<BoosterPackDraggable>();
|
||
if (booster != null)
|
||
{
|
||
booster.ResetTapCount();
|
||
booster.SetTapToOpenEnabled(false);
|
||
booster.OnReadyToOpen += OnBoosterReadyToOpen;
|
||
|
||
// Assign to first available slot
|
||
DraggableSlot slot = bottomRightSlots?.GetAvailableSlots().FirstOrDefault();
|
||
if (slot != null)
|
||
{
|
||
booster.AssignToSlot(slot, true);
|
||
}
|
||
}
|
||
|
||
break;
|
||
}
|
||
}
|
||
|
||
yield return null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Clean up the page when hidden
|
||
/// </summary>
|
||
private void CleanupPage()
|
||
{
|
||
UnsubscribeFromAllBoosters();
|
||
|
||
// Clear any remaining cards
|
||
foreach (GameObject card in _currentRevealedCards)
|
||
{
|
||
if (card != null)
|
||
Destroy(card);
|
||
}
|
||
_currentRevealedCards.Clear();
|
||
|
||
_currentBoosterInCenter = null;
|
||
_isProcessingOpening = false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Unsubscribe from all booster events
|
||
/// </summary>
|
||
private void UnsubscribeFromAllBoosters()
|
||
{
|
||
if (boosterPackInstances == null) return;
|
||
|
||
foreach (GameObject boosterObj in boosterPackInstances)
|
||
{
|
||
if (boosterObj == null) continue;
|
||
|
||
BoosterPackDraggable booster = boosterObj.GetComponent<BoosterPackDraggable>();
|
||
if (booster != null)
|
||
{
|
||
booster.OnReadyToOpen -= OnBoosterReadyToOpen;
|
||
}
|
||
}
|
||
}
|
||
|
||
protected override void DoTransitionIn(System.Action onComplete)
|
||
{
|
||
// Simple fade in animation
|
||
if (canvasGroup != null)
|
||
{
|
||
canvasGroup.alpha = 0f;
|
||
Tween.Value(0f, 1f, (value) => canvasGroup.alpha = value, transitionDuration, 0f, Tween.EaseInOut, Tween.LoopType.None, null, onComplete, obeyTimescale: false);
|
||
}
|
||
else
|
||
{
|
||
// Fallback if no CanvasGroup
|
||
onComplete?.Invoke();
|
||
}
|
||
}
|
||
|
||
protected override void DoTransitionOut(System.Action onComplete)
|
||
{
|
||
// Simple fade out animation
|
||
if (canvasGroup != null)
|
||
{
|
||
Tween.Value(canvasGroup.alpha, 0f, (value) => canvasGroup.alpha = value, transitionDuration, 0f, Tween.EaseInOut, Tween.LoopType.None, null, onComplete, obeyTimescale: false);
|
||
}
|
||
else
|
||
{
|
||
// Fallback if no CanvasGroup
|
||
onComplete?.Invoke();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|