2025-11-06 18:57:10 +01:00
|
|
|
|
using System.Collections;
|
2025-11-06 10:10:54 +01:00
|
|
|
|
using System.Collections.Generic;
|
2025-11-06 11:11:15 +01:00
|
|
|
|
using System.Linq;
|
2025-11-06 10:10:54 +01:00
|
|
|
|
using AppleHills.Data.CardSystem;
|
|
|
|
|
|
using Data.CardSystem;
|
|
|
|
|
|
using Pixelplacement;
|
|
|
|
|
|
using UI.Core;
|
2025-11-06 11:11:15 +01:00
|
|
|
|
using UI.CardSystem.DragDrop;
|
|
|
|
|
|
using UI.DragAndDrop.Core;
|
2025-11-06 18:57:10 +01:00
|
|
|
|
using Unity.Cinemachine;
|
2025-11-06 10:10:54 +01:00
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
using UnityEngine.UI;
|
2025-11-06 18:57:10 +01:00
|
|
|
|
using UnityEngine.UI;
|
2025-11-06 10:10:54 +01:00
|
|
|
|
|
|
|
|
|
|
namespace UI.CardSystem
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// UI page for opening booster packs and displaying the cards received.
|
2025-11-06 11:11:15 +01:00
|
|
|
|
/// Manages the entire booster opening flow with drag-and-drop interaction.
|
2025-11-06 10:10:54 +01:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class BoosterOpeningPage : UIPage
|
|
|
|
|
|
{
|
|
|
|
|
|
[Header("UI References")]
|
|
|
|
|
|
[SerializeField] private CanvasGroup canvasGroup;
|
|
|
|
|
|
[SerializeField] private Button closeButton;
|
|
|
|
|
|
|
2025-11-06 11:11:15 +01:00
|
|
|
|
[Header("Booster Management")]
|
2025-11-06 19:31:33 +01:00
|
|
|
|
[SerializeField] private GameObject boosterPackPrefab; // Prefab to instantiate new boosters
|
|
|
|
|
|
[SerializeField] private SlotContainer bottomRightSlots; // Holds waiting boosters (max 3)
|
2025-11-06 11:11:15 +01:00
|
|
|
|
[SerializeField] private DraggableSlot centerOpeningSlot; // Where booster goes to open
|
|
|
|
|
|
|
2025-11-06 10:10:54 +01:00
|
|
|
|
[Header("Card Display")]
|
|
|
|
|
|
[SerializeField] private Transform cardDisplayContainer;
|
2025-11-06 11:11:15 +01:00
|
|
|
|
[SerializeField] private GameObject flippableCardPrefab; // Placeholder for card backs
|
|
|
|
|
|
[SerializeField] private float cardSpacing = 150f;
|
2025-11-06 10:10:54 +01:00
|
|
|
|
|
|
|
|
|
|
[Header("Settings")]
|
|
|
|
|
|
[SerializeField] private float cardRevealDelay = 0.5f;
|
2025-11-06 11:11:15 +01:00
|
|
|
|
[SerializeField] private float boosterDisappearDuration = 0.5f;
|
2025-11-06 18:57:10 +01:00
|
|
|
|
[SerializeField] private CinemachineImpulseSource impulseSource;
|
2025-11-06 21:31:16 +01:00
|
|
|
|
[SerializeField] private ParticleSystem openingParticleSystem;
|
2025-11-06 11:11:15 +01:00
|
|
|
|
|
|
|
|
|
|
private int _availableBoosterCount;
|
|
|
|
|
|
private BoosterPackDraggable _currentBoosterInCenter;
|
2025-11-06 19:31:33 +01:00
|
|
|
|
private List<BoosterPackDraggable> _activeBoostersInSlots = new List<BoosterPackDraggable>();
|
2025-11-06 11:11:15 +01:00
|
|
|
|
private List<GameObject> _currentRevealedCards = new List<GameObject>();
|
|
|
|
|
|
private CardData[] _currentCardData;
|
|
|
|
|
|
private int _revealedCardCount;
|
|
|
|
|
|
private bool _isProcessingOpening;
|
2025-11-06 19:31:33 +01:00
|
|
|
|
private const int MAX_VISIBLE_BOOSTERS = 3;
|
2025-11-06 10:10:54 +01:00
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
2025-11-06 11:11:15 +01:00
|
|
|
|
|
|
|
|
|
|
// Unsubscribe from slot events
|
|
|
|
|
|
if (centerOpeningSlot != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
centerOpeningSlot.OnOccupied -= OnBoosterPlacedInCenter;
|
2025-11-06 19:31:33 +01:00
|
|
|
|
centerOpeningSlot.OnVacated -= OnBoosterRemovedFromCenter;
|
2025-11-06 11:11:15 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Unsubscribe from booster events
|
|
|
|
|
|
UnsubscribeFromAllBoosters();
|
2025-11-06 10:10:54 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnCloseButtonClicked()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (UIPageController.Instance != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
UIPageController.Instance.PopPage();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-06 11:11:15 +01:00
|
|
|
|
/// <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()
|
|
|
|
|
|
{
|
2025-11-06 13:18:39 +01:00
|
|
|
|
Debug.Log($"[BoosterOpeningPage] InitializeBoosterDisplay called with {_availableBoosterCount} boosters available");
|
|
|
|
|
|
|
2025-11-06 19:31:33 +01:00
|
|
|
|
if (boosterPackPrefab == null)
|
2025-11-06 11:11:15 +01:00
|
|
|
|
{
|
2025-11-06 19:31:33 +01:00
|
|
|
|
Debug.LogWarning("BoosterOpeningPage: No booster pack prefab assigned!");
|
2025-11-06 11:11:15 +01:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-06 19:31:33 +01:00
|
|
|
|
if (bottomRightSlots == null || bottomRightSlots.SlotCount == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogWarning("BoosterOpeningPage: No slots available!");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Clear any existing boosters
|
|
|
|
|
|
_activeBoostersInSlots.Clear();
|
|
|
|
|
|
|
|
|
|
|
|
// Calculate how many boosters to show (max 3, or available count, whichever is lower)
|
|
|
|
|
|
int visibleCount = Mathf.Min(_availableBoosterCount, MAX_VISIBLE_BOOSTERS);
|
|
|
|
|
|
|
|
|
|
|
|
Debug.Log($"[BoosterOpeningPage] Will spawn {visibleCount} boosters");
|
|
|
|
|
|
|
|
|
|
|
|
// Spawn boosters and assign to slots
|
|
|
|
|
|
for (int i = 0; i < visibleCount; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
SpawnBoosterInSlot(i);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Subscribe to center slot events
|
|
|
|
|
|
if (centerOpeningSlot != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
centerOpeningSlot.OnOccupied += OnBoosterPlacedInCenter;
|
|
|
|
|
|
centerOpeningSlot.OnVacated += OnBoosterRemovedFromCenter;
|
|
|
|
|
|
Debug.Log($"[BoosterOpeningPage] Subscribed to center slot events");
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogWarning("[BoosterOpeningPage] centerOpeningSlot is null!");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Spawn a new booster and place it in the specified slot index
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void SpawnBoosterInSlot(int slotIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (slotIndex >= bottomRightSlots.SlotCount)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogWarning($"[BoosterOpeningPage] Slot index {slotIndex} out of range!");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DraggableSlot slot = bottomRightSlots.GetSlotAtIndex(slotIndex);
|
|
|
|
|
|
if (slot == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogWarning($"[BoosterOpeningPage] Slot {slotIndex} is null!");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-11-06 11:11:15 +01:00
|
|
|
|
|
2025-11-06 19:31:33 +01:00
|
|
|
|
// Instantiate booster
|
|
|
|
|
|
GameObject boosterObj = Instantiate(boosterPackPrefab, slot.transform);
|
|
|
|
|
|
BoosterPackDraggable booster = boosterObj.GetComponent<BoosterPackDraggable>();
|
2025-11-06 13:18:39 +01:00
|
|
|
|
|
2025-11-06 19:31:33 +01:00
|
|
|
|
if (booster != null)
|
2025-11-06 11:11:15 +01:00
|
|
|
|
{
|
2025-11-06 19:31:33 +01:00
|
|
|
|
// Reset state
|
|
|
|
|
|
booster.ResetTapCount();
|
|
|
|
|
|
booster.SetTapToOpenEnabled(false);
|
2025-11-06 11:11:15 +01:00
|
|
|
|
|
2025-11-06 19:31:33 +01:00
|
|
|
|
// Subscribe to events
|
|
|
|
|
|
booster.OnReadyToOpen += OnBoosterReadyToOpen;
|
2025-11-06 13:18:39 +01:00
|
|
|
|
|
2025-11-06 19:31:33 +01:00
|
|
|
|
// Assign to slot with animation
|
|
|
|
|
|
booster.AssignToSlot(slot, true);
|
2025-11-06 11:11:15 +01:00
|
|
|
|
|
2025-11-06 19:31:33 +01:00
|
|
|
|
// Track it
|
|
|
|
|
|
_activeBoostersInSlots.Add(booster);
|
|
|
|
|
|
|
|
|
|
|
|
Debug.Log($"[BoosterOpeningPage] Spawned booster in slot {slotIndex}");
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogWarning($"[BoosterOpeningPage] Spawned booster has no BoosterPackDraggable component!");
|
|
|
|
|
|
Destroy(boosterObj);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Remove and destroy the booster from the specified slot
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void RemoveBoosterFromSlot(int slotIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (slotIndex >= _activeBoostersInSlots.Count)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
BoosterPackDraggable booster = _activeBoostersInSlots[slotIndex];
|
|
|
|
|
|
if (booster != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Unsubscribe from events
|
|
|
|
|
|
booster.OnReadyToOpen -= OnBoosterReadyToOpen;
|
|
|
|
|
|
|
|
|
|
|
|
// Animate out and destroy
|
|
|
|
|
|
Transform boosterTransform = booster.transform;
|
|
|
|
|
|
Tween.LocalScale(boosterTransform, Vector3.zero, 0.3f, 0f, Tween.EaseInBack,
|
|
|
|
|
|
completeCallback: () =>
|
2025-11-06 11:11:15 +01:00
|
|
|
|
{
|
2025-11-06 19:31:33 +01:00
|
|
|
|
if (booster != null && booster.gameObject != null)
|
2025-11-06 13:18:39 +01:00
|
|
|
|
{
|
2025-11-06 19:31:33 +01:00
|
|
|
|
Destroy(booster.gameObject);
|
2025-11-06 13:18:39 +01:00
|
|
|
|
}
|
2025-11-06 19:31:33 +01:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Remove from slot
|
|
|
|
|
|
if (booster.CurrentSlot != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
booster.CurrentSlot.Vacate();
|
2025-11-06 11:11:15 +01:00
|
|
|
|
}
|
2025-11-06 19:31:33 +01:00
|
|
|
|
|
|
|
|
|
|
Debug.Log($"[BoosterOpeningPage] Removed booster from slot {slotIndex}");
|
2025-11-06 11:11:15 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-06 19:31:33 +01:00
|
|
|
|
_activeBoostersInSlots.RemoveAt(slotIndex);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Update visible boosters based on available count
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void UpdateVisibleBoosters()
|
|
|
|
|
|
{
|
|
|
|
|
|
int targetCount = Mathf.Min(_availableBoosterCount, MAX_VISIBLE_BOOSTERS);
|
|
|
|
|
|
|
|
|
|
|
|
// Remove excess boosters (from the end)
|
|
|
|
|
|
while (_activeBoostersInSlots.Count > targetCount)
|
2025-11-06 11:11:15 +01:00
|
|
|
|
{
|
2025-11-06 19:31:33 +01:00
|
|
|
|
int lastIndex = _activeBoostersInSlots.Count - 1;
|
|
|
|
|
|
RemoveBoosterFromSlot(lastIndex);
|
2025-11-06 13:18:39 +01:00
|
|
|
|
}
|
2025-11-06 19:31:33 +01:00
|
|
|
|
|
|
|
|
|
|
Debug.Log($"[BoosterOpeningPage] Updated visible boosters: {_activeBoostersInSlots.Count}/{targetCount}");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Try to spawn a new booster to maintain up to 3 visible
|
|
|
|
|
|
/// Pass decrementCount=true when called after placing a booster in center slot
|
|
|
|
|
|
/// (accounts for the booster that will be consumed)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void TrySpawnNewBooster(bool decrementCount = false)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Can we spawn more boosters?
|
|
|
|
|
|
if (_activeBoostersInSlots.Count >= MAX_VISIBLE_BOOSTERS)
|
|
|
|
|
|
return; // Already at max
|
|
|
|
|
|
|
|
|
|
|
|
// Use decremented count if this is called after placing in center
|
|
|
|
|
|
// (the booster in center will be consumed, so we check against count - 1)
|
|
|
|
|
|
int effectiveCount = decrementCount ? _availableBoosterCount - 1 : _availableBoosterCount;
|
|
|
|
|
|
|
|
|
|
|
|
if (_activeBoostersInSlots.Count >= effectiveCount)
|
|
|
|
|
|
return; // No more boosters available
|
|
|
|
|
|
|
|
|
|
|
|
// Find first available slot
|
|
|
|
|
|
for (int i = 0; i < MAX_VISIBLE_BOOSTERS; i++)
|
2025-11-06 13:18:39 +01:00
|
|
|
|
{
|
2025-11-06 19:31:33 +01:00
|
|
|
|
DraggableSlot slot = bottomRightSlots.GetSlotAtIndex(i);
|
|
|
|
|
|
if (slot != null && !slot.IsOccupied)
|
|
|
|
|
|
{
|
|
|
|
|
|
SpawnBoosterInSlot(i);
|
|
|
|
|
|
Debug.Log($"[BoosterOpeningPage] Spawned new booster in slot {i}");
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2025-11-06 11:11:15 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <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;
|
|
|
|
|
|
|
2025-11-06 19:31:33 +01:00
|
|
|
|
// Remove from active slots list
|
|
|
|
|
|
_activeBoostersInSlots.Remove(booster);
|
2025-11-06 11:11:15 +01:00
|
|
|
|
|
2025-11-06 19:31:33 +01:00
|
|
|
|
// Lock the slot so it can't be dragged out
|
|
|
|
|
|
Debug.Log($"[BoosterOpeningPage] Locking center slot. IsLocked before: {centerOpeningSlot.IsLocked}");
|
|
|
|
|
|
centerOpeningSlot.SetLocked(true);
|
|
|
|
|
|
Debug.Log($"[BoosterOpeningPage] IsLocked after: {centerOpeningSlot.IsLocked}");
|
|
|
|
|
|
|
2025-11-06 15:27:05 +01:00
|
|
|
|
// Configure booster for opening (disables drag, enables tapping, resets tap count)
|
2025-11-06 19:31:33 +01:00
|
|
|
|
Debug.Log($"[BoosterOpeningPage] Calling SetInOpeningSlot(true) on booster");
|
2025-11-06 15:27:05 +01:00
|
|
|
|
booster.SetInOpeningSlot(true);
|
|
|
|
|
|
|
|
|
|
|
|
// Subscribe to tap events for visual feedback
|
|
|
|
|
|
booster.OnTapped += OnBoosterTapped;
|
|
|
|
|
|
booster.OnReadyToOpen += OnBoosterReadyToOpen;
|
2025-11-06 21:31:16 +01:00
|
|
|
|
booster.OnBoosterOpened += OnBoosterOpened;
|
2025-11-06 15:27:05 +01:00
|
|
|
|
|
2025-11-06 19:31:33 +01:00
|
|
|
|
// Try to spawn a new booster to maintain 3 visible
|
|
|
|
|
|
// Use decrementCount=true because this booster will be consumed
|
|
|
|
|
|
TrySpawnNewBooster(decrementCount: true);
|
|
|
|
|
|
|
|
|
|
|
|
Debug.Log($"[BoosterOpeningPage] Booster placed in center, ready for taps. Active boosters in slots: {_activeBoostersInSlots.Count}");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Handle when a booster is removed from the center opening slot
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void OnBoosterRemovedFromCenter(DraggableObject draggable)
|
|
|
|
|
|
{
|
|
|
|
|
|
BoosterPackDraggable booster = draggable as BoosterPackDraggable;
|
|
|
|
|
|
if (booster == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
// If it's being removed back to a corner slot, add it back to tracking
|
|
|
|
|
|
if (booster.CurrentSlot != null && bottomRightSlots.HasSlot(booster.CurrentSlot))
|
|
|
|
|
|
{
|
|
|
|
|
|
_activeBoostersInSlots.Add(booster);
|
|
|
|
|
|
booster.SetInOpeningSlot(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_currentBoosterInCenter = null;
|
|
|
|
|
|
Debug.Log($"[BoosterOpeningPage] Booster removed from center");
|
2025-11-06 15:27:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnBoosterTapped(BoosterPackDraggable booster, int currentTaps, int maxTaps)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.Log($"[BoosterOpeningPage] Booster tapped: {currentTaps}/{maxTaps}");
|
|
|
|
|
|
|
2025-11-06 18:57:10 +01:00
|
|
|
|
// Fire Cinemachine impulse with random velocity (excluding Z)
|
|
|
|
|
|
if (impulseSource != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Generate random velocity vector (X and Y only, Z = 0)
|
|
|
|
|
|
Vector3 randomVelocity = new Vector3(
|
|
|
|
|
|
Random.Range(-1f, 1f),
|
|
|
|
|
|
Random.Range(-1f, 1f),
|
|
|
|
|
|
0f
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// Normalize to ensure consistent strength
|
|
|
|
|
|
randomVelocity.Normalize();
|
|
|
|
|
|
|
|
|
|
|
|
// Generate the impulse with strength 1 and random velocity
|
|
|
|
|
|
impulseSource.GenerateImpulse(randomVelocity);
|
|
|
|
|
|
}
|
2025-11-06 11:11:15 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-06 21:31:16 +01:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Handle when booster is opened - play particle effects
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void OnBoosterOpened(BoosterPackDraggable booster)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.Log($"[BoosterOpeningPage] Booster opened, playing particle effect");
|
|
|
|
|
|
|
|
|
|
|
|
// Reset and play particle system
|
|
|
|
|
|
if (openingParticleSystem != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Stop any existing playback
|
|
|
|
|
|
if (openingParticleSystem.isPlaying)
|
|
|
|
|
|
{
|
|
|
|
|
|
openingParticleSystem.Stop();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Clear existing particles
|
|
|
|
|
|
openingParticleSystem.Clear();
|
|
|
|
|
|
|
|
|
|
|
|
// Play the particle system
|
|
|
|
|
|
openingParticleSystem.Play();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-06 11:11:15 +01:00
|
|
|
|
/// <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;
|
|
|
|
|
|
|
2025-11-06 15:27:05 +01:00
|
|
|
|
Debug.Log($"[BoosterOpeningPage] Booster ready to open!");
|
|
|
|
|
|
|
|
|
|
|
|
// Trigger the actual opening sequence
|
|
|
|
|
|
booster.TriggerOpen();
|
2025-11-06 11:11:15 +01:00
|
|
|
|
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));
|
|
|
|
|
|
|
2025-11-06 19:31:33 +01:00
|
|
|
|
// Decrement available count
|
|
|
|
|
|
_availableBoosterCount--;
|
|
|
|
|
|
|
|
|
|
|
|
// Update visible boosters (remove from end if we drop below thresholds)
|
|
|
|
|
|
UpdateVisibleBoosters();
|
|
|
|
|
|
|
2025-11-06 11:11:15 +01:00
|
|
|
|
// Show card backs
|
|
|
|
|
|
SpawnCardBacks(_currentCardData.Length);
|
|
|
|
|
|
|
|
|
|
|
|
// Wait for player to reveal all cards
|
|
|
|
|
|
yield return StartCoroutine(WaitForCardReveals());
|
|
|
|
|
|
|
2025-11-06 19:31:33 +01:00
|
|
|
|
if (_availableBoosterCount <= 0)
|
2025-11-06 11:11:15 +01:00
|
|
|
|
{
|
|
|
|
|
|
// 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;
|
2025-11-06 19:31:33 +01:00
|
|
|
|
|
2025-11-06 11:11:15 +01:00
|
|
|
|
// 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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-06 21:31:16 +01:00
|
|
|
|
// Get FlippableCard component and setup the card data
|
|
|
|
|
|
FlippableCard flippableCard = cardObj.GetComponent<FlippableCard>();
|
|
|
|
|
|
if (flippableCard != null)
|
2025-11-06 11:11:15 +01:00
|
|
|
|
{
|
2025-11-06 21:31:16 +01:00
|
|
|
|
// Setup the card data (stored but not revealed yet)
|
|
|
|
|
|
flippableCard.SetupCard(_currentCardData[i]);
|
|
|
|
|
|
|
|
|
|
|
|
// Subscribe to reveal event to track when flipped
|
|
|
|
|
|
int cardIndex = i; // Capture for closure
|
|
|
|
|
|
flippableCard.OnCardRevealed += (card, data) => OnCardRevealed(cardIndex);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogWarning($"[BoosterOpeningPage] FlippableCard component not found on card {i}!");
|
2025-11-06 11:11:15 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_currentRevealedCards.Add(cardObj);
|
|
|
|
|
|
|
|
|
|
|
|
// Animate cards flying in
|
|
|
|
|
|
cardRect.localScale = Vector3.zero;
|
|
|
|
|
|
Tween.LocalScale(cardRect, Vector3.one, 0.3f, i * 0.1f, Tween.EaseOutBack);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2025-11-06 21:31:16 +01:00
|
|
|
|
/// Handle card reveal (when flipped)
|
2025-11-06 11:11:15 +01:00
|
|
|
|
/// </summary>
|
2025-11-06 21:31:16 +01:00
|
|
|
|
private void OnCardRevealed(int cardIndex)
|
2025-11-06 11:11:15 +01:00
|
|
|
|
{
|
2025-11-06 21:31:16 +01:00
|
|
|
|
Debug.Log($"[BoosterOpeningPage] Card {cardIndex} revealed!");
|
2025-11-06 11:11:15 +01:00
|
|
|
|
_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>
|
|
|
|
|
|
/// Clean up the page when hidden
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void CleanupPage()
|
|
|
|
|
|
{
|
|
|
|
|
|
UnsubscribeFromAllBoosters();
|
|
|
|
|
|
|
2025-11-06 19:31:33 +01:00
|
|
|
|
// Destroy all active boosters
|
|
|
|
|
|
foreach (var booster in _activeBoostersInSlots.ToList())
|
|
|
|
|
|
{
|
|
|
|
|
|
if (booster != null && booster.gameObject != null)
|
|
|
|
|
|
Destroy(booster.gameObject);
|
|
|
|
|
|
}
|
|
|
|
|
|
_activeBoostersInSlots.Clear();
|
|
|
|
|
|
|
2025-11-06 11:11:15 +01:00
|
|
|
|
// 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()
|
|
|
|
|
|
{
|
2025-11-06 19:31:33 +01:00
|
|
|
|
// Unsubscribe from active boosters in slots
|
|
|
|
|
|
foreach (var booster in _activeBoostersInSlots)
|
2025-11-06 11:11:15 +01:00
|
|
|
|
{
|
|
|
|
|
|
if (booster != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
booster.OnReadyToOpen -= OnBoosterReadyToOpen;
|
2025-11-06 19:31:33 +01:00
|
|
|
|
booster.OnTapped -= OnBoosterTapped;
|
2025-11-06 21:31:16 +01:00
|
|
|
|
booster.OnBoosterOpened -= OnBoosterOpened;
|
2025-11-06 11:11:15 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-06 19:31:33 +01:00
|
|
|
|
|
|
|
|
|
|
// Unsubscribe from center booster
|
|
|
|
|
|
if (_currentBoosterInCenter != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_currentBoosterInCenter.OnReadyToOpen -= OnBoosterReadyToOpen;
|
|
|
|
|
|
_currentBoosterInCenter.OnTapped -= OnBoosterTapped;
|
2025-11-06 21:31:16 +01:00
|
|
|
|
_currentBoosterInCenter.OnBoosterOpened -= OnBoosterOpened;
|
2025-11-06 19:31:33 +01:00
|
|
|
|
}
|
2025-11-06 11:11:15 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-06 10:10:54 +01:00
|
|
|
|
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();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|