Files
AppleHillsProduction/Assets/Scripts/UI/CardSystem/AlbumViewPage.cs
2025-11-16 20:35:54 +01:00

684 lines
26 KiB
C#

using System.Collections.Generic;
using System.Linq;
using AppleHills.Data.CardSystem;
using Core;
using Data.CardSystem;
using Pixelplacement;
using UI.Core;
using UI.DragAndDrop.Core;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Serialization;
namespace UI.CardSystem
{
/// <summary>
/// UI page for viewing the player's card collection in an album.
/// Manages booster pack button visibility and opening flow.
/// </summary>
public class AlbumViewPage : UIPage
{
[Header("UI References")]
[SerializeField] private CanvasGroup canvasGroup;
[SerializeField] private Button exitButton;
[SerializeField] private BookCurlPro.BookPro book;
[Header("Zone Navigation")]
[SerializeField] private Transform tabContainer; // Container holding all BookTabButton children
private BookTabButton[] zoneTabs; // Discovered zone tab buttons
[Header("Album Card Reveal")]
[SerializeField] private SlotContainer bottomRightSlots;
[FormerlySerializedAs("albumCardPlacementPrefab")]
[SerializeField] private GameObject cardPrefab; // New Card prefab for placement
[Header("Card Enlarge System")]
[SerializeField] private GameObject cardEnlargedBackdrop; // Backdrop to block interactions
[SerializeField] private Transform cardEnlargedContainer; // Container for enlarged cards (sits above backdrop)
[Header("Booster Pack UI")]
[SerializeField] private GameObject[] boosterPackButtons;
[SerializeField] private BoosterOpeningPage boosterOpeningPage;
private Input.InputMode _previousInputMode;
private List<StateMachine.Card> _activeCards = new List<StateMachine.Card>();
private const int MAX_VISIBLE_CARDS = 3;
internal override void OnManagedStart()
{
// Discover zone tabs from container
DiscoverZoneTabs();
// Make sure we have a CanvasGroup for transitions
if (canvasGroup == null)
canvasGroup = GetComponent<CanvasGroup>();
if (canvasGroup == null)
canvasGroup = gameObject.AddComponent<CanvasGroup>();
// Hide backdrop initially
if (cardEnlargedBackdrop != null)
{
cardEnlargedBackdrop.SetActive(false);
}
// Set up exit button
if (exitButton != null)
{
exitButton.onClick.AddListener(OnExitButtonClicked);
}
// Set up booster pack button listeners
SetupBoosterButtonListeners();
// Subscribe to book page flip events
if (book != null)
{
book.OnFlip.AddListener(OnPageFlipped);
Logging.Debug("[AlbumViewPage] Subscribed to book.OnFlip event");
}
else
{
Logging.Warning("[AlbumViewPage] Book reference is null, cannot subscribe to OnFlip event!");
}
// Subscribe to CardSystemManager events (managers are guaranteed to be initialized)
if (CardSystemManager.Instance != null)
{
CardSystemManager.Instance.OnBoosterCountChanged += OnBoosterCountChanged;
// NOTE: OnPendingCardAdded is subscribed in TransitionIn, not here
// This prevents spawning cards when page is not active
// Update initial button visibility
int initialCount = CardSystemManager.Instance.GetBoosterPackCount();
UpdateBoosterButtons(initialCount);
}
// UI pages should start disabled
gameObject.SetActive(false);
}
/// <summary>
/// Discover all BookTabButton components from the tab container
/// </summary>
private void DiscoverZoneTabs()
{
if (tabContainer == null)
{
Debug.LogError("[AlbumViewPage] Tab container is not assigned! Cannot discover zone tabs.");
zoneTabs = new BookTabButton[0];
return;
}
// Get all BookTabButton components from children
zoneTabs = tabContainer.GetComponentsInChildren<BookTabButton>(includeInactive: false);
if (zoneTabs == null || zoneTabs.Length == 0)
{
Logging.Warning($"[AlbumViewPage] No BookTabButton components found in tab container '{tabContainer.name}'!");
zoneTabs = new BookTabButton[0];
}
else
{
Logging.Debug($"[AlbumViewPage] Discovered {zoneTabs.Length} zone tabs from container '{tabContainer.name}'");
foreach (var tab in zoneTabs)
{
Logging.Debug($" - Tab: {tab.name}, Zone: {tab.Zone}, TargetPage: {tab.TargetPage}");
}
}
}
private void SetupBoosterButtonListeners()
{
if (boosterPackButtons == null) return;
for (int i = 0; i < boosterPackButtons.Length; i++)
{
if (boosterPackButtons[i] == null) continue;
// Unsubscribe from book events
if (book != null)
{
book.OnFlip.RemoveListener(OnPageFlipped);
}
Button button = boosterPackButtons[i].GetComponent<Button>();
if (button != null)
{
button.onClick.AddListener(OnBoosterButtonClicked);
}
}
}
internal override void OnManagedDestroy()
{
// Unsubscribe from CardSystemManager
if (CardSystemManager.Instance != null)
{
CardSystemManager.Instance.OnBoosterCountChanged -= OnBoosterCountChanged;
// NOTE: OnPendingCardAdded is unsubscribed in TransitionOut
}
// Clean up exit button
if (exitButton != null)
{
exitButton.onClick.RemoveListener(OnExitButtonClicked);
}
// Clean up booster button listeners
if (boosterPackButtons != null)
{
foreach (var buttonObj in boosterPackButtons)
{
if (buttonObj == null) continue;
Button button = buttonObj.GetComponent<Button>();
if (button != null)
{
button.onClick.RemoveListener(OnBoosterButtonClicked);
}
}
}
// Clean up active cards
CleanupActiveCards();
}
private void OnExitButtonClicked()
{
if (book != null && book.CurrentPaper != 1)
{
// Not on page 0, flip to page 0 first
BookCurlPro.AutoFlip autoFlip = book.GetComponent<BookCurlPro.AutoFlip>();
if (autoFlip == null)
{
autoFlip = book.gameObject.AddComponent<BookCurlPro.AutoFlip>();
}
autoFlip.enabled = true;
autoFlip.StartFlipping(1);
}
else
{
// Already on page 0 or no book reference, exit
// Restore input mode before popping
if (Input.InputManager.Instance != null)
{
Input.InputManager.Instance.SetInputMode(_previousInputMode);
Logging.Debug($"[AlbumViewPage] Restored input mode to {_previousInputMode} on exit");
}
if (UIPageController.Instance != null)
{
UIPageController.Instance.PopPage();
}
}
}
private void OnBoosterCountChanged(int newCount)
{
UpdateBoosterButtons(newCount);
}
private void UpdateBoosterButtons(int boosterCount)
{
if (boosterPackButtons == null || boosterPackButtons.Length == 0) return;
int visibleCount = Mathf.Min(boosterCount, boosterPackButtons.Length);
for (int i = 0; i < boosterPackButtons.Length; i++)
{
if (boosterPackButtons[i] != null)
{
boosterPackButtons[i].SetActive(i < visibleCount);
}
}
}
private void OnBoosterButtonClicked()
{
if (boosterOpeningPage != null && UIPageController.Instance != null)
{
// Pass current booster count to the opening page
int boosterCount = CardSystemManager.Instance?.GetBoosterPackCount() ?? 0;
boosterOpeningPage.SetAvailableBoosterCount(boosterCount);
UIPageController.Instance.PushPage(boosterOpeningPage);
}
}
public override void TransitionIn()
{
// Only store and switch input mode if this is the first time entering
// (when _previousInputMode hasn't been set yet)
if (Input.InputManager.Instance != null)
{
// Store the current input mode before switching
_previousInputMode = Input.InputMode.GameAndUI;
Input.InputManager.Instance.SetInputMode(Input.InputMode.UI);
Logging.Debug("[AlbumViewPage] Switched to UI-only input mode on first entry");
}
// Subscribe to pending card events while page is active
if (CardSystemManager.Instance != null)
{
CardSystemManager.Instance.OnPendingCardAdded += OnPendingCardAdded;
}
// Only spawn pending cards if we're already on an album page (not the menu)
if (IsInAlbumProper())
{
Logging.Debug("[AlbumViewPage] Opening directly to album page - spawning cards immediately");
SpawnPendingCards();
}
else
{
Logging.Debug("[AlbumViewPage] Opening to menu page - cards will spawn when entering album");
}
base.TransitionIn();
}
public override void TransitionOut()
{
// Unsubscribe from pending card events when page closes
if (CardSystemManager.Instance != null)
{
CardSystemManager.Instance.OnPendingCardAdded -= OnPendingCardAdded;
}
// Clean up active pending cards to prevent duplicates on next opening
CleanupActiveCards();
// Don't restore input mode here - only restore when actually exiting (in OnExitButtonClicked)
base.TransitionOut();
}
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)
{
// Clean up any enlarged card state before closing
CleanupEnlargedCardState();
// 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();
}
}
/// <summary>
/// Clean up enlarged card state when closing the album
/// </summary>
private void CleanupEnlargedCardState()
{
// Hide backdrop if visible
if (cardEnlargedBackdrop != null && cardEnlargedBackdrop.activeSelf)
{
cardEnlargedBackdrop.SetActive(false);
}
// If there's an enlarged card in the container, return it to its slot
if (cardEnlargedContainer != null && cardEnlargedContainer.childCount > 0)
{
for (int i = cardEnlargedContainer.childCount - 1; i >= 0; i--)
{
Transform cardTransform = cardEnlargedContainer.GetChild(i);
var card = cardTransform.GetComponent<StateMachine.Card>();
var state = cardTransform.GetComponentInChildren<StateMachine.States.CardAlbumEnlargedState>(true);
if (card != null && state != null)
{
Transform originalParent = state.GetOriginalParent();
if (originalParent != null)
{
cardTransform.SetParent(originalParent, true);
cardTransform.localPosition = state.GetOriginalLocalPosition();
cardTransform.localRotation = state.GetOriginalLocalRotation();
}
}
}
}
}
/// <summary>
/// Check if we're currently viewing the album proper (not the menu page)
/// </summary>
private bool IsInAlbumProper()
{
if (book == null)
{
Logging.Warning("[AlbumViewPage] Book reference is null in IsInAlbumProper check");
return false;
}
// Page 1 is the menu/cover, page 2+ are album pages with card slots
bool inAlbum = book.CurrentPaper > 1;
return inAlbum;
}
/// <summary>
/// Called when book page flips - show/hide pending cards based on whether we're in the album proper
/// </summary>
private void OnPageFlipped()
{
bool isInAlbum = IsInAlbumProper();
if (isInAlbum && _activeCards.Count == 0)
{
// Entering album proper and no cards spawned yet - spawn them with animation
Logging.Debug("[AlbumViewPage] Entering album proper - spawning pending cards with animation");
SpawnPendingCards();
}
else if (!isInAlbum && _activeCards.Count > 0)
{
// Returning to menu page - cleanup cards
Logging.Debug("[AlbumViewPage] Returning to menu page - cleaning up pending cards");
CleanupActiveCards();
}
else
{
Logging.Debug($"[AlbumViewPage] Page flipped but no card state change needed (already in correct state)");
}
}
#region Album Card Reveal System
/// <summary>
/// Spawn pending cards from CardSystemManager
/// Only spawns unique cards (one per definition+rarity, not one per copy)
/// </summary>
private void SpawnPendingCards()
{
if (CardSystemManager.Instance == null || bottomRightSlots == null || cardPrefab == null)
return;
var pending = CardSystemManager.Instance.GetPendingRevealCards();
var uniquePending = pending
.Where(c => c.CopiesOwned > 0)
.GroupBy(c => new { c.DefinitionId, c.Rarity })
.Select(g => g.First())
.ToList();
int spawnCount = Mathf.Min(uniquePending.Count, MAX_VISIBLE_CARDS);
Logging.Debug($"[AlbumViewPage] Spawning {spawnCount} unique pending cards (total pending: {pending.Count})");
for (int i = 0; i < spawnCount; i++)
{
SpawnCardInSlot(i, uniquePending[i]);
}
}
/// <summary>
/// Spawn a card in a specific slot using the new Card prefab
/// </summary>
private void SpawnCardInSlot(int slotIndex, CardData cardData)
{
if (cardData.CopiesOwned <= 0)
{
Logging.Warning($"[AlbumViewPage] Skipping spawn of card '{cardData.Name}' with {cardData.CopiesOwned} copies");
return;
}
DraggableSlot slot = FindSlotByIndex(slotIndex);
if (slot == null)
{
Logging.Warning($"[AlbumViewPage] Could not find slot with SlotIndex {slotIndex}");
return;
}
GameObject cardObj = Instantiate(cardPrefab, bottomRightSlots.transform);
var card = cardObj.GetComponent<StateMachine.Card>();
if (card != null)
{
// Cards spawned here are already revealed and can be dragged into album
card.SetupForAlbumPlacement(cardData);
// Assign to slot with animation (will apply size/position)
card.AssignToSlot(slot, true);
// Track placement completion to clean up
card.OnPlacedInAlbumSlot += OnCardPlacedInAlbum;
_activeCards.Add(card);
Logging.Debug($"[AlbumViewPage] Spawned pending card '{cardData.Name}' in slot {slotIndex}");
}
else
{
Logging.Warning($"[AlbumViewPage] Spawned card prefab missing Card component!");
Destroy(cardObj);
}
}
/// <summary>
/// Handle when a card is placed in an album slot from the pending list
/// Moves from pending to inventory, shuffles remaining, and spawns the next unique card.
/// </summary>
private void OnCardPlacedInAlbum(StateMachine.Card card, AlbumCardSlot slot)
{
if (card == null) return;
var data = card.CardData;
Logging.Debug($"[AlbumViewPage] Card placed in album slot: {data?.Name}");
// Move card from pending to inventory now
if (data != null && CardSystemManager.Instance != null)
{
CardSystemManager.Instance.MarkCardAsPlaced(data);
}
// Stop tracking and unsubscribe
card.OnPlacedInAlbumSlot -= OnCardPlacedInAlbum;
_activeCards.Remove(card);
// Shuffle remaining cards to front and spawn next
ShuffleCardsToFront();
TrySpawnNextCard();
}
/// <summary>
/// Shuffle active cards to occupy front slots
/// </summary>
private void ShuffleCardsToFront()
{
if (bottomRightSlots == null || _activeCards.Count == 0)
return;
List<DraggableObject> draggableList = _activeCards.Cast<DraggableObject>().ToList();
SlotContainerHelper.ShuffleToFront(bottomRightSlots, draggableList, animate: true);
}
/// <summary>
/// Try to spawn the next pending unique card
/// </summary>
private void TrySpawnNextCard()
{
if (CardSystemManager.Instance == null)
return;
if (_activeCards.Count >= MAX_VISIBLE_CARDS)
return;
var pending = CardSystemManager.Instance.GetPendingRevealCards();
var uniquePending = pending
.Where(c => c.CopiesOwned > 0)
.GroupBy(c => new { c.DefinitionId, c.Rarity })
.Select(g => g.First())
.ToList();
foreach (var cardData in uniquePending)
{
bool alreadySpawned = _activeCards.Any(c =>
c.CardData.DefinitionId == cardData.DefinitionId &&
c.CardData.Rarity == cardData.Rarity);
if (!alreadySpawned)
{
int nextSlotIndex = _activeCards.Count;
SpawnCardInSlot(nextSlotIndex, cardData);
break;
}
}
}
/// <summary>
/// Clean up all active pending cards
/// </summary>
private void CleanupActiveCards()
{
foreach (var card in _activeCards)
{
if (card != null && card.gameObject != null)
{
card.OnPlacedInAlbumSlot -= OnCardPlacedInAlbum;
Destroy(card.gameObject);
}
}
_activeCards.Clear();
}
#endregion
#region Card Enlarge System (Album Slots)
/// <summary>
/// Subscribe to a placed card's enlarged state events to manage backdrop and reparenting.
/// Called by AlbumCardSlot when it spawns an owned card in a slot.
/// </summary>
public void RegisterCardInAlbum(StateMachine.Card card)
{
if (card == null) return;
var enlargeState = card.GetStateComponent<StateMachine.States.CardAlbumEnlargedState>("AlbumEnlargedState");
if (enlargeState != null)
{
enlargeState.OnEnlargeRequested += OnCardEnlargeRequested;
enlargeState.OnShrinkRequested += OnCardShrinkRequested;
}
}
public void UnregisterCardInAlbum(StateMachine.Card card)
{
if (card == null) return;
var enlargeState = card.GetStateComponent<StateMachine.States.CardAlbumEnlargedState>("AlbumEnlargedState");
if (enlargeState != null)
{
enlargeState.OnEnlargeRequested -= OnCardEnlargeRequested;
enlargeState.OnShrinkRequested -= OnCardShrinkRequested;
}
}
private void OnCardEnlargeRequested(StateMachine.States.CardAlbumEnlargedState state)
{
if (state == null) return;
// Show backdrop
if (cardEnlargedBackdrop != null)
{
cardEnlargedBackdrop.SetActive(true);
}
// Reparent card root to enlarged container preserving world transform
if (cardEnlargedContainer != null)
{
var ctx = state.GetComponentInParent<StateMachine.CardContext>();
if (ctx != null)
{
ctx.RootTransform.SetParent(cardEnlargedContainer, true);
ctx.RootTransform.SetAsLastSibling();
}
}
}
private void OnCardShrinkRequested(StateMachine.States.CardAlbumEnlargedState state)
{
if (state == null) return;
// Hide backdrop
if (cardEnlargedBackdrop != null)
{
cardEnlargedBackdrop.SetActive(false);
}
// Reparent back to original parent and restore local transform
var ctx = state.GetComponentInParent<StateMachine.CardContext>();
if (ctx != null)
{
Transform originalParent = state.GetOriginalParent();
if (originalParent != null)
{
ctx.RootTransform.SetParent(originalParent, true);
ctx.RootTransform.localPosition = state.GetOriginalLocalPosition();
ctx.RootTransform.localRotation = state.GetOriginalLocalRotation();
}
}
}
#endregion
/// <summary>
/// Handle when a new card is added to pending queue
/// Only spawn if this unique card isn't already visualized
/// </summary>
private void OnPendingCardAdded(CardData card)
{
// Guard: Don't spawn cards with zero copies
if (card.CopiesOwned <= 0)
{
Logging.Warning($"[AlbumViewPage] Ignoring pending card '{card.Name}' with {card.CopiesOwned} copies");
return;
}
// Check if we already have a card with this definition + rarity spawned
bool alreadySpawned = _activeCards.Any(c =>
c.CardData.DefinitionId == card.DefinitionId &&
c.CardData.Rarity == card.Rarity);
if (alreadySpawned)
{
Logging.Debug($"[AlbumViewPage] Card '{card.Name}' already spawned, skipping duplicate spawn");
return; // Don't spawn duplicates
}
// Try to spawn if we have space
if (_activeCards.Count < MAX_VISIBLE_CARDS)
{
int nextSlotIndex = _activeCards.Count;
SpawnCardInSlot(nextSlotIndex, card);
}
}
/// <summary>
/// Find a slot by its SlotIndex property
/// </summary>
private DraggableSlot FindSlotByIndex(int slotIndex)
{
if (bottomRightSlots == null)
return null;
foreach (var slot in bottomRightSlots.Slots)
{
if (slot.SlotIndex == slotIndex)
{
return slot;
}
}
return null;
}
}
}