Stash work!

This commit is contained in:
Michal Pikulski
2025-11-17 17:10:24 +01:00
parent c6f635f871
commit b5364a2bbc
29 changed files with 665 additions and 2444 deletions

View File

@@ -43,10 +43,14 @@ namespace UI.CardSystem
private List<StateMachine.Card> _pendingCornerCards = new List<StateMachine.Card>();
private const int MaxPendingCorner = 3;
// Pending card placement coordination
private StateMachine.Card _pendingPlacementCard;
private bool _waitingForPageFlip;
private bool _cardDragReleased;
// Page flip tracking (for card placement coordination)
private bool _isPageFlipping = false;
/// <summary>
/// Query method: Check if the book is currently flipping to a page.
/// Used by card states to know if they should wait before placing.
/// </summary>
public bool IsPageFlipping => _isPageFlipping;
internal override void OnManagedStart()
{
@@ -460,9 +464,7 @@ namespace UI.CardSystem
}
#endregion
#region New Pending Corner Card System
/// <summary>
/// Find a slot by its SlotIndex property
/// </summary>
@@ -482,48 +484,123 @@ namespace UI.CardSystem
}
public void SpawnPendingCornerCards()
{
RebuildCornerCards();
}
/// <summary>
/// Rebuild corner card display from scratch.
/// Called on initial spawn and after card is removed.
/// </summary>
private void RebuildCornerCards()
{
if (cardPrefab == null || bottomRightSlots == null) return;
// Step 1: Clear all existing cards from slots
CleanupPendingCornerCards();
// Get unique pending cards
// Step 2: Establish total amount and amount to display
var uniquePending = GetUniquePendingCards();
int totalPendingCards = uniquePending.Count;
int cardsToDisplay = Mathf.Min(totalPendingCards, MaxPendingCorner);
if (uniquePending.Count == 0)
if (cardsToDisplay == 0)
{
Logging.Debug("[AlbumViewPage] No pending cards to spawn");
Logging.Debug("[AlbumViewPage] No pending cards to display in corner");
return;
}
// Spawn min(unique count, MaxPendingCorner)
int spawnCount = Mathf.Min(uniquePending.Count, MaxPendingCorner);
Logging.Debug($"[AlbumViewPage] Spawning {spawnCount} pending corner cards (out of {uniquePending.Count} unique pending)");
Logging.Debug($"[AlbumViewPage] Rebuilding corner: displaying {cardsToDisplay} of {totalPendingCards} pending cards");
for (int i = 0; i < spawnCount; i++)
// Step 3: Spawn cards, starting from slot index 0 -> 1 -> 2
for (int slotIndex = 0; slotIndex < cardsToDisplay; slotIndex++)
{
var slot = FindSlotByIndex(i);
if (slot == null) break;
// Step 3.1: Get slot with this index
var slot = FindSlotByIndex(slotIndex);
if (slot == null)
{
Logging.Warning($"[AlbumViewPage] Slot {slotIndex} not found, stopping spawn");
break;
}
GameObject cardObj = Instantiate(cardPrefab, bottomRightSlots.transform);
var card = cardObj.GetComponent<StateMachine.Card>();
if (card != null)
{
card.SetupForAlbumPending();
card.AssignToSlot(slot, true);
// Subscribe to both drag events
card.Context.OnDragStarted += OnCardDragStarted;
card.Context.OnDragEnded += OnCardDragEnded;
_pendingCornerCards.Add(card);
}
else
{
Destroy(cardObj);
}
// Step 3.2 & 3.3: Spawn card in slot (matching transform, in PendingFaceDownState)
SpawnCardInSlot(slot);
}
}
/// <summary>
/// Spawn a single card in the specified slot.
/// Card will be in PendingFaceDownState and match slot transform.
/// </summary>
private void SpawnCardInSlot(DraggableSlot slot)
{
if (slot == null || cardPrefab == null) return;
// Instantiate card as child of slot (not container)
GameObject cardObj = Instantiate(cardPrefab, slot.transform);
var card = cardObj.GetComponent<StateMachine.Card>();
if (card == null)
{
Logging.Warning("[AlbumViewPage] Card prefab missing Card component!");
Destroy(cardObj);
return;
}
// Setup card for pending state (transitions to PendingFaceDownState)
card.SetupForAlbumPending();
// Assign to slot (handles transform matching)
card.AssignToSlot(slot, false); // false = instant, no animation
// Subscribe to drag events for reorganization
card.Context.OnDragStarted += OnCardDragStarted;
// Track in list
_pendingCornerCards.Add(card);
Logging.Debug($"[AlbumViewPage] Spawned card in slot {slot.SlotIndex}, state: {card.GetCurrentStateName()}");
}
/// <summary>
/// Handle card drag started - cleanup and unparent from corner slot
/// Rebuild happens in GetCardForPendingSlot after pending list is updated
/// </summary>
private void OnCardDragStarted(StateMachine.CardContext context)
{
if (context == null) return;
var card = context.GetComponent<StateMachine.Card>();
if (card == null) return;
// Only handle pending corner cards
if (!_pendingCornerCards.Contains(card)) return;
Logging.Debug($"[AlbumViewPage] Card drag started, removing from corner");
// 1. Remove from tracking (card is transitioning to placement flow)
_pendingCornerCards.Remove(card);
// 2. Unsubscribe from this card's events
if (card.Context != null)
{
card.Context.OnDragStarted -= OnCardDragStarted;
}
// 3. CRITICAL: Unparent from corner slot BEFORE rebuild happens
// This prevents the card from being destroyed when CleanupPendingCornerCards runs
// Reparent to this page's transform (or canvas) to keep it alive during drag
if (card.transform.parent != null)
{
card.transform.SetParent(transform, true); // Keep world position
Logging.Debug($"[AlbumViewPage] Card unparented from corner slot - safe for rebuild");
}
// Note: RebuildCornerCards() is called in GetCardForPendingSlot()
// after the card is removed from CardSystemManager's pending list
// The card is now safe from being destroyed since it's no longer a child of corner slots
}
/// <summary>
/// Get unique pending cards (one per definition+rarity combo)
/// </summary>
@@ -560,6 +637,7 @@ namespace UI.CardSystem
private void CleanupPendingCornerCards()
{
// First, unsubscribe and destroy tracked cards
foreach (var c in _pendingCornerCards)
{
if (c != null)
@@ -567,124 +645,86 @@ namespace UI.CardSystem
if (c.Context != null)
{
c.Context.OnDragStarted -= OnCardDragStarted;
c.Context.OnDragEnded -= OnCardDragEnded;
}
Destroy(c.gameObject);
}
}
_pendingCornerCards.Clear();
}
private void OnCardDragStarted(StateMachine.CardContext context)
{
if (context == null) return;
// Only handle if in PendingFaceDownState
var card = context.GetComponent<StateMachine.Card>();
if (card == null) return;
string stateName = card.GetCurrentStateName();
Logging.Debug($"[AlbumViewPage] OnCardDragStarted - Card state: {stateName}");
if (stateName != "PendingFaceDownState") return;
// Select smart pending card data
var selected = SelectSmartPendingCard();
if (selected == null)
// IMPORTANT: Also clear ALL children from corner slots
// This catches cards that were removed from tracking but not destroyed
// (e.g., cards being dragged to album)
if (bottomRightSlots != null)
{
Logging.Warning("[AlbumViewPage] No pending card data available!");
return; // no pending data
}
Logging.Debug($"[AlbumViewPage] Selected card: {selected.Name} ({selected.DefinitionId}), Zone: {selected.Zone}");
context.SetupCard(selected);
// Find target page based on card's zone using BookTabButtons
int targetPage = FindPageForZone(selected.Zone);
Logging.Debug($"[AlbumViewPage] Target page for zone {selected.Zone}: {targetPage}");
if (targetPage >= 0)
{
// Always flip to the zone's page (even if already there, for consistency)
Logging.Debug($"[AlbumViewPage] Flipping to page {targetPage} for zone {selected.Zone}");
_waitingForPageFlip = true;
_cardDragReleased = false;
_pendingPlacementCard = card;
NavigateToAlbumPage(targetPage, OnPageFlipComplete);
}
else
{
// No valid page found for zone - don't wait for flip
Logging.Warning($"[AlbumViewPage] No BookTabButton found for zone {selected.Zone}");
_waitingForPageFlip = false;
_cardDragReleased = false;
_pendingPlacementCard = card;
}
}
private void OnCardDragEnded(StateMachine.CardContext context)
{
if (context == null) return;
var card = context.GetComponent<StateMachine.Card>();
if (card == null) return;
// Only handle if this is the pending placement card
if (card != _pendingPlacementCard) return;
Logging.Debug($"[AlbumViewPage] Card drag released for: {card.CardData?.Name}");
// Mark drag as released - don't capture slot yet (pages may still be flipping)
_cardDragReleased = true;
// Try to place card (will check if flip is also complete)
TryPlaceCard();
}
private void OnPageFlipComplete()
{
Logging.Debug("[AlbumViewPage] Page flip complete");
_waitingForPageFlip = false;
// Try to place card (will check if drag is also released)
TryPlaceCard();
}
private void TryPlaceCard()
{
// Check if BOTH conditions are met:
// 1. Card has been drag-released
// 2. Page flip is complete (or wasn't needed)
if (_cardDragReleased && !_waitingForPageFlip)
{
if (_pendingPlacementCard == null || _pendingPlacementCard.CardData == null)
foreach (var slot in bottomRightSlots.Slots)
{
Logging.Warning("[AlbumViewPage] TryPlaceCard - No pending card or card data");
ResetPlacementState();
return;
if (slot == null || slot.transform == null) continue;
// Destroy all card children in this slot
for (int i = slot.transform.childCount - 1; i >= 0; i--)
{
var child = slot.transform.GetChild(i);
var card = child.GetComponent<StateMachine.Card>();
if (card != null)
{
// Unsubscribe if somehow still subscribed
if (card.Context != null)
{
card.Context.OnDragStarted -= OnCardDragStarted;
}
Destroy(child.gameObject);
Logging.Debug($"[AlbumViewPage] Cleaned up orphaned card from slot {slot.SlotIndex}");
}
}
}
// Find the correct slot for this card (AFTER pages have flipped)
var targetSlot = FindTargetSlotForCard(_pendingPlacementCard.CardData);
if (targetSlot == null)
{
Logging.Warning($"[AlbumViewPage] No slot found for card {_pendingPlacementCard.CardData.DefinitionId}");
// TODO: Return card to corner
ResetPlacementState();
return;
}
// Both conditions met and slot found - perform placement
PlaceCardInSlot(_pendingPlacementCard, targetSlot);
}
}
#region Query Methods for Card States (Data Providers)
/// <summary>
/// Query method: Get card data for a pending slot.
/// Called by PendingFaceDownState when drag starts.
/// IMPORTANT: This removes the card from pending list immediately, then rebuilds corner.
/// </summary>
public CardData GetCardForPendingSlot()
{
if (CardSystemManager.Instance == null) return null;
var pending = CardSystemManager.Instance.GetPendingRevealCards();
if (pending.Count == 0) return null;
// Try current page match
var pageDefs = GetDefinitionsOnCurrentPage();
var match = pending.Find(c => pageDefs.Contains(c.DefinitionId));
// If no match, use random
if (match == null)
{
int idx = Random.Range(0, pending.Count);
match = pending[idx];
}
// IMPORTANT: Remove from pending list immediately
// Card is now in "reveal flow" and will be added to collection when placed
if (match != null)
{
// Remove from pending using the manager (fires OnPendingCardRemoved event)
CardSystemManager.Instance.RemoveFromPending(match);
Logging.Debug($"[AlbumViewPage] Removed '{match.Name}' from pending cards, starting reveal flow");
// Rebuild corner cards AFTER removing from pending list
// This ensures the removed card doesn't get re-spawned
RebuildCornerCards();
}
return match;
}
/// <summary>
/// Find the AlbumCardSlot that accepts this card based on DefinitionId
/// Query method: Get target slot for a card.
/// Called by PendingFaceDownState to find where card should go.
/// </summary>
private AlbumCardSlot FindTargetSlotForCard(CardData cardData)
public AlbumCardSlot GetTargetSlotForCard(CardData cardData)
{
if (cardData == null) return null;
@@ -695,7 +735,6 @@ namespace UI.CardSystem
if (slot.TargetCardDefinition != null &&
slot.TargetCardDefinition.Id == cardData.DefinitionId)
{
Logging.Debug($"[AlbumViewPage] Found target slot for {cardData.DefinitionId}, slot: {slot}");
return slot;
}
}
@@ -703,98 +742,80 @@ namespace UI.CardSystem
return null;
}
private void PlaceCardInSlot(StateMachine.Card card, AlbumCardSlot slot)
/// <summary>
/// Service method: Navigate to the page for a specific card.
/// Called by PendingFaceDownState to flip book to correct zone page.
/// </summary>
public void NavigateToCardPage(CardData cardData, System.Action onComplete)
{
if (card == null || slot == null) return;
if (cardData == null || book == null)
{
onComplete?.Invoke();
return;
}
Logging.Debug($"[AlbumViewPage] Placing card '{card.CardData?.Name}' in slot - starting tween");
// Find target page based on card's zone
int targetPage = FindPageForZone(cardData.Zone);
// Reparent to slot immediately, keeping world position (card stays visible where it is)
card.transform.SetParent(slot.transform, true);
if (targetPage < 0)
{
Logging.Warning($"[AlbumViewPage] No page found for zone {cardData.Zone}");
onComplete?.Invoke();
return;
}
// Tween local position and scale simultaneously
float tweenDuration = 0.4f;
// Mark as flipping
_isPageFlipping = true;
Logging.Debug($"[AlbumViewPage] Starting page flip to page {targetPage}");
// Tween position to center of slot
Tween.LocalPosition(card.transform, Vector3.zero, tweenDuration, 0f, Tween.EaseOutBack);
// Get or add AutoFlip component
BookCurlPro.AutoFlip autoFlip = book.GetComponent<BookCurlPro.AutoFlip>();
if (autoFlip == null)
{
autoFlip = book.gameObject.AddComponent<BookCurlPro.AutoFlip>();
}
// Tween scale to normal (same duration and easing)
Tween.LocalScale(card.transform, Vector3.one, tweenDuration, 0f, Tween.EaseOutBack);
// Tween rotation to identity (use this for the completion callback since all tweens are synchronized)
Tween.LocalRotation(card.transform, Quaternion.identity, tweenDuration, 0f, Tween.EaseOutBack,
completeCallback: () =>
// Start flipping with callback
autoFlip.enabled = true;
autoFlip.StartFlipping(targetPage, () =>
{
// Mark as complete
_isPageFlipping = false;
Logging.Debug($"[AlbumViewPage] Page flip to {targetPage} completed");
// Call original callback if provided
onComplete?.Invoke();
});
}
/// <summary>
/// Notify that a card has been placed (for cleanup).
/// Called by PlacedInSlotState after placement is complete.
/// </summary>
public void NotifyCardPlaced(StateMachine.Card card)
{
if (card != null)
{
// Remove from tracking list
_pendingCornerCards.Remove(card);
// IMPORTANT: Unsubscribe from drag events
// Placed cards should never respond to AlbumViewPage drag events
if (card.Context != null)
{
// After tween completes, finalize
card.transform.localPosition = Vector3.zero;
card.transform.localRotation = Quaternion.identity;
// Resize to match slot
RectTransform cardRect = card.transform as RectTransform;
RectTransform slotRect = slot.transform as RectTransform;
if (cardRect != null && slotRect != null)
{
float targetHeight = slotRect.rect.height;
cardRect.sizeDelta = new Vector2(cardRect.sizeDelta.x, targetHeight);
}
// Transition to placed state
var placedState = card.GetStateComponent<StateMachine.States.CardPlacedInSlotState>("PlacedInSlotState");
if (placedState != null)
{
placedState.SetParentSlot(slot);
}
card.ChangeState("PlacedInSlotState");
// Assign card to slot (so slot remembers it has a card)
slot.AssignCard(card);
// Mark as placed in inventory
if (CardSystemManager.Instance != null)
{
CardSystemManager.Instance.MarkCardAsPlaced(card.CardData);
}
// Unsubscribe from drag events - card is now placed, shouldn't respond to future drags
if (card.Context != null)
{
card.Context.OnDragStarted -= OnCardDragStarted;
card.Context.OnDragEnded -= OnCardDragEnded;
}
// Remove from pending corner cards list
_pendingCornerCards.Remove(card);
// Register with album page for enlarge system
RegisterCardInAlbum(card);
Logging.Debug($"[AlbumViewPage] Card placement complete and unsubscribed from drag events");
// Reset state for next card
ResetPlacementState();
});
card.Context.OnDragStarted -= OnCardDragStarted;
}
// Register for enlarge/shrink functionality
RegisterCardInAlbum(card);
Logging.Debug($"[AlbumViewPage] Card placed and unsubscribed from corner events: {card.CardData?.Name}");
}
}
private void ResetPlacementState()
{
_pendingPlacementCard = null;
_waitingForPageFlip = false;
_cardDragReleased = false;
}
#endregion
private CardData SelectSmartPendingCard()
{
if (CardSystemManager.Instance == null) return null;
var pending = CardSystemManager.Instance.GetPendingRevealCards();
if (pending.Count == 0) return null;
// Try current page match
var pageDefs = GetDefinitionsOnCurrentPage();
var match = pending.Find(c => pageDefs.Contains(c.DefinitionId));
if (match != null) return match;
// Fallback random
int idx = Random.Range(0, pending.Count);
return pending[idx];
}
#region Helper Methods
/// <summary>
/// Find the target page for a card zone using BookTabButtons
@@ -864,29 +885,6 @@ namespace UI.CardSystem
return false;
}
private void NavigateToAlbumPage(int pageIndex, UnityEngine.Events.UnityAction onComplete = null)
{
if (book == null || pageIndex < 0) return;
// Get or add AutoFlip component
BookCurlPro.AutoFlip autoFlip = book.GetComponent<BookCurlPro.AutoFlip>();
if (autoFlip == null)
{
autoFlip = book.gameObject.AddComponent<BookCurlPro.AutoFlip>();
}
// Start flipping to target page with callback
autoFlip.enabled = true;
if (onComplete != null)
{
autoFlip.StartFlipping(pageIndex, onComplete);
}
else
{
autoFlip.StartFlipping(pageIndex);
}
}
#endregion
}
}