Stash work!
This commit is contained in:
@@ -42,6 +42,7 @@ namespace Data.CardSystem
|
||||
public event Action<CardData> OnCardCollected;
|
||||
public event Action<int> OnBoosterCountChanged;
|
||||
public event Action<CardData> OnPendingCardAdded;
|
||||
public event Action<CardData> OnPendingCardRemoved;
|
||||
public event Action<CardData> OnCardPlacedInAlbum;
|
||||
|
||||
internal override void OnManagedAwake()
|
||||
@@ -492,11 +493,28 @@ namespace Data.CardSystem
|
||||
#region Album System
|
||||
|
||||
/// <summary>
|
||||
/// Returns all cards waiting to be placed in the album
|
||||
/// Returns all pending reveal cards (cards waiting to be placed in album)
|
||||
/// </summary>
|
||||
public List<CardData> GetPendingRevealCards()
|
||||
{
|
||||
return new List<CardData>(_pendingRevealCards);
|
||||
return _pendingRevealCards;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a card from the pending reveal list and fire event.
|
||||
/// Called when a card starts being dragged to album slot.
|
||||
/// </summary>
|
||||
public bool RemoveFromPending(CardData card)
|
||||
{
|
||||
if (card == null) return false;
|
||||
|
||||
bool removed = _pendingRevealCards.Remove(card);
|
||||
if (removed)
|
||||
{
|
||||
OnPendingCardRemoved?.Invoke(card);
|
||||
Logging.Debug($"[CardSystemManager] Removed '{card.Name}' from pending reveal cards");
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -563,26 +581,37 @@ namespace Data.CardSystem
|
||||
|
||||
/// <summary>
|
||||
/// Marks a card as placed in the album
|
||||
/// Moves it from pending reveal to owned inventory
|
||||
/// Adds card to owned inventory and tracks as placed
|
||||
/// Note: Card may have already been removed from pending list during drag
|
||||
/// </summary>
|
||||
public void MarkCardAsPlaced(CardData card)
|
||||
{
|
||||
if (_pendingRevealCards.Remove(card))
|
||||
if (card == null)
|
||||
{
|
||||
// Add to owned inventory
|
||||
playerInventory.AddCard(card);
|
||||
|
||||
// Track as placed
|
||||
_placedInAlbumCardIds.Add(card.Id);
|
||||
|
||||
OnCardPlacedInAlbum?.Invoke(card);
|
||||
OnCardCollected?.Invoke(card);
|
||||
|
||||
Logging.Debug($"[CardSystemManager] Card '{card.Name}' placed in album and added to inventory.");
|
||||
Logging.Warning("[CardSystemManager] Attempted to place null card");
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to remove from pending (may already be removed during drag)
|
||||
bool wasInPending = _pendingRevealCards.Remove(card);
|
||||
|
||||
// Add to owned inventory (regardless of whether it was in pending)
|
||||
playerInventory.AddCard(card);
|
||||
|
||||
// Track as placed
|
||||
_placedInAlbumCardIds.Add(card.Id);
|
||||
|
||||
// Fire events
|
||||
OnCardPlacedInAlbum?.Invoke(card);
|
||||
OnCardCollected?.Invoke(card);
|
||||
|
||||
if (wasInPending)
|
||||
{
|
||||
Logging.Debug($"[CardSystemManager] Card '{card.Name}' removed from pending and added to inventory");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logging.Warning($"[CardSystemManager] Attempted to place card '{card.Name}' but it wasn't in pending reveal list.");
|
||||
Logging.Debug($"[CardSystemManager] Card '{card.Name}' added to inventory (was already removed from pending)");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,11 +88,44 @@ namespace UI.CardSystem.DragDrop
|
||||
{
|
||||
base.OnDragEndedHook();
|
||||
|
||||
// Optionally trigger open when dropped in specific zones
|
||||
if (canOpenOnDrop)
|
||||
// Find closest slot and assign to it (replaces removed auto-slotting from base class)
|
||||
SlotContainer[] containers = FindObjectsByType<SlotContainer>(FindObjectsSortMode.None);
|
||||
DraggableSlot closestSlot = null;
|
||||
float closestDistance = float.MaxValue;
|
||||
|
||||
// Get position (handle both overlay and world space canvas)
|
||||
Vector3 myPosition = (RectTransform != null &&
|
||||
GetComponentInParent<Canvas>()?.renderMode == RenderMode.ScreenSpaceOverlay)
|
||||
? RectTransform.position
|
||||
: transform.position;
|
||||
|
||||
// Find closest slot among all containers
|
||||
foreach (var container in containers)
|
||||
{
|
||||
// Could check if dropped in an "opening zone"
|
||||
// For now, just a placeholder
|
||||
DraggableSlot slot = container.FindClosestSlot(myPosition, this);
|
||||
if (slot != null)
|
||||
{
|
||||
Vector3 slotPosition = slot.RectTransform != null ? slot.RectTransform.position : slot.transform.position;
|
||||
float distance = Vector3.Distance(myPosition, slotPosition);
|
||||
if (distance < closestDistance)
|
||||
{
|
||||
closestDistance = distance;
|
||||
closestSlot = slot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assign to closest slot if found
|
||||
if (closestSlot != null)
|
||||
{
|
||||
Logging.Debug($"[BoosterPackDraggable] Drag ended, assigning to closest slot: {closestSlot.name}");
|
||||
AssignToSlot(closestSlot, true);
|
||||
}
|
||||
else if (CurrentSlot != null)
|
||||
{
|
||||
// No valid slot found, return to current slot
|
||||
Logging.Debug($"[BoosterPackDraggable] No valid slot found, snapping back to current slot");
|
||||
AssignToSlot(CurrentSlot, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,8 +66,8 @@ namespace UI.CardSystem.StateMachine
|
||||
}
|
||||
|
||||
// Default behavior: transition to DraggingState
|
||||
Logging.Debug($"[Card] Drag started on {CardData?.Name}, transitioning to DraggingState");
|
||||
ChangeState("DraggingState");
|
||||
// Logging.Debug($"[Card] Drag started on {CardData?.Name}, transitioning to DraggingState");
|
||||
// ChangeState("DraggingState");
|
||||
}
|
||||
|
||||
protected override void OnDragEndedHook()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using AppleHills.Data.CardSystem;
|
||||
using Core.SaveLoad;
|
||||
using UnityEngine;
|
||||
@@ -19,6 +19,9 @@ namespace UI.CardSystem.StateMachine
|
||||
[Header("Card Data")]
|
||||
private CardData cardData;
|
||||
|
||||
// Cached reference to AlbumViewPage (lazy-loaded)
|
||||
private AlbumViewPage _albumViewPage;
|
||||
|
||||
// Public accessors
|
||||
public CardDisplay CardDisplay => cardDisplay;
|
||||
public CardAnimator Animator => cardAnimator;
|
||||
@@ -26,8 +29,25 @@ namespace UI.CardSystem.StateMachine
|
||||
public Transform RootTransform => transform;
|
||||
public CardData CardData => cardData;
|
||||
|
||||
/// <summary>
|
||||
/// Get the AlbumViewPage instance (cached to avoid repeated FindFirstObjectByType calls)
|
||||
/// </summary>
|
||||
public AlbumViewPage AlbumViewPage
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_albumViewPage == null)
|
||||
{
|
||||
_albumViewPage = FindFirstObjectByType<AlbumViewPage>();
|
||||
}
|
||||
return _albumViewPage;
|
||||
}
|
||||
}
|
||||
|
||||
// Runtime state
|
||||
public bool IsClickable { get; set; } = true;
|
||||
|
||||
// TODO: Move to booster-specific states - this is workflow-specific, not generic context
|
||||
public bool SuppressRevealBadges { get; set; } = false; // Set by states to suppress NEW/REPEAT badges in revealed state
|
||||
|
||||
// Original transform data (captured on spawn for shrink animations)
|
||||
@@ -35,6 +55,7 @@ namespace UI.CardSystem.StateMachine
|
||||
public Vector3 OriginalPosition { get; private set; }
|
||||
public Quaternion OriginalRotation { get; private set; }
|
||||
|
||||
// TODO: Move to BoosterOpeningPage - reveal flow is booster-specific workflow coordination
|
||||
// Single event for reveal flow completion
|
||||
public event Action<CardContext> OnRevealFlowComplete;
|
||||
|
||||
@@ -44,9 +65,11 @@ namespace UI.CardSystem.StateMachine
|
||||
// Generic drag end event - fired when drag ends, consumers decide how to handle based on current state
|
||||
public event Action<CardContext> OnDragEnded;
|
||||
|
||||
// TODO: Move to booster-specific states - this tracks reveal workflow completion
|
||||
private bool _hasCompletedReveal = false;
|
||||
public bool HasCompletedReveal => _hasCompletedReveal;
|
||||
|
||||
// TODO: Move to booster-specific states - workflow coordination method
|
||||
// Helper method for states to signal completion
|
||||
public void NotifyRevealComplete()
|
||||
{
|
||||
|
||||
@@ -1,22 +1,46 @@
|
||||
using Core.SaveLoad;
|
||||
using Core;
|
||||
using Core.SaveLoad;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UI.CardSystem.StateMachine.States
|
||||
{
|
||||
/// <summary>
|
||||
/// Dragging revealed state for pending cards after flip.
|
||||
/// Shows card front without badges, handles placement or return to corner.
|
||||
/// Shows card front without badges, handles transition to placement after drag release.
|
||||
/// Queries AlbumViewPage for page flip status instead of tracking state internally.
|
||||
/// </summary>
|
||||
public class CardDraggingRevealedState : AppleState, ICardStateDragHandler
|
||||
{
|
||||
private CardContext _context;
|
||||
private Vector3 _originalScale;
|
||||
|
||||
// Placement info passed from PendingFaceDownState
|
||||
private AlbumCardSlot _targetSlot;
|
||||
private bool _dragAlreadyEnded = false; // Track if drag ended before we entered this state (instant-release case)
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_context = GetComponentInParent<CardContext>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set target slot from previous state
|
||||
/// Called by PendingFaceDownState before transition
|
||||
/// </summary>
|
||||
public void SetTargetSlot(AlbumCardSlot targetSlot)
|
||||
{
|
||||
_targetSlot = targetSlot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set flag indicating drag already ended before entering this state
|
||||
/// Called by PendingFaceDownState for instant-release case
|
||||
/// </summary>
|
||||
public void SetDragAlreadyEnded(bool ended)
|
||||
{
|
||||
_dragAlreadyEnded = ended;
|
||||
}
|
||||
|
||||
public override void OnEnterState()
|
||||
{
|
||||
if (_context == null) return;
|
||||
@@ -27,6 +51,14 @@ namespace UI.CardSystem.StateMachine.States
|
||||
}
|
||||
_originalScale = _context.RootTransform.localScale;
|
||||
_context.RootTransform.localScale = _originalScale * 1.15f;
|
||||
|
||||
// Check if drag already ended before we entered this state (instant-release case)
|
||||
if (_dragAlreadyEnded)
|
||||
{
|
||||
Logging.Debug("[CardDraggingRevealedState] Drag ended before state entry - handling placement immediately");
|
||||
_dragAlreadyEnded = false; // Clear flag
|
||||
HandlePlacement();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -38,21 +70,94 @@ namespace UI.CardSystem.StateMachine.States
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle drag end - just let AlbumViewPage handle placement logic
|
||||
/// Stay in this state until AlbumViewPage transitions us after tween
|
||||
/// Handle drag end - query AlbumViewPage for page flip status and place accordingly
|
||||
/// </summary>
|
||||
public bool OnCardDragEnded(CardContext ctx)
|
||||
{
|
||||
// Don't do anything - AlbumViewPage will:
|
||||
// 1. Wait for page flip to complete
|
||||
// 2. Find the correct slot
|
||||
// 3. Tween card to slot
|
||||
// 4. Transition to PlacedInSlotState
|
||||
|
||||
// Return true to prevent default behavior
|
||||
HandlePlacement();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle card placement logic - called from OnCardDragEnded or OnEnterState (instant-release)
|
||||
/// </summary>
|
||||
private void HandlePlacement()
|
||||
{
|
||||
if (_targetSlot == null)
|
||||
{
|
||||
Logging.Warning("[CardDraggingRevealedState] No target slot set - cannot place card");
|
||||
// Return to corner
|
||||
_context.StateMachine.ChangeState("PendingFaceDownState");
|
||||
return;
|
||||
}
|
||||
|
||||
// Query AlbumViewPage for page flip status
|
||||
var albumPage = _context.AlbumViewPage;
|
||||
if (albumPage == null)
|
||||
{
|
||||
Logging.Warning("[CardDraggingRevealedState] AlbumViewPage not found - placing immediately");
|
||||
TransitionToPlacement(_context);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if page is still flipping
|
||||
if (albumPage.IsPageFlipping)
|
||||
{
|
||||
// Wait for flip to complete
|
||||
Logging.Debug("[CardDraggingRevealedState] Page still flipping - waiting before placement");
|
||||
StartCoroutine(WaitForPageFlipThenPlace(_context, albumPage));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Flip already done - place immediately
|
||||
Logging.Debug("[CardDraggingRevealedState] Page flip complete - placing card immediately");
|
||||
TransitionToPlacement(_context);
|
||||
}
|
||||
}
|
||||
|
||||
private System.Collections.IEnumerator WaitForPageFlipThenPlace(CardContext ctx, AlbumViewPage albumPage)
|
||||
{
|
||||
// Wait until page flip completes (max 0.5 seconds timeout)
|
||||
float timeout = 0.5f;
|
||||
float elapsed = 0f;
|
||||
|
||||
while (albumPage.IsPageFlipping && elapsed < timeout)
|
||||
{
|
||||
yield return null;
|
||||
elapsed += Time.deltaTime;
|
||||
}
|
||||
|
||||
if (elapsed >= timeout)
|
||||
{
|
||||
Logging.Warning("[CardDraggingRevealedState] Page flip wait timed out");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logging.Debug("[CardDraggingRevealedState] Page flip completed, placing card");
|
||||
}
|
||||
|
||||
// Now place the card
|
||||
TransitionToPlacement(ctx);
|
||||
}
|
||||
|
||||
private void TransitionToPlacement(CardContext ctx)
|
||||
{
|
||||
// Pass target slot to PlacedInSlotState
|
||||
var card = ctx.GetComponent<Card>();
|
||||
if (card != null)
|
||||
{
|
||||
var placedState = card.GetStateComponent<CardPlacedInSlotState>("PlacedInSlotState");
|
||||
if (placedState != null)
|
||||
{
|
||||
placedState.SetPlacementInfo(_targetSlot);
|
||||
}
|
||||
}
|
||||
|
||||
// Transition to PlacedInSlotState
|
||||
// The state will handle animation and finalization in OnEnterState
|
||||
ctx.StateMachine.ChangeState("PlacedInSlotState");
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (_context?.RootTransform != null)
|
||||
|
||||
@@ -6,7 +6,8 @@ namespace UI.CardSystem.StateMachine.States
|
||||
{
|
||||
/// <summary>
|
||||
/// Card is in pending face-down state in corner, awaiting drag.
|
||||
/// On drag start, triggers flip animation and transitions to revealed dragging.
|
||||
/// On drag start, queries AlbumViewPage for card data and slot, triggers page navigation,
|
||||
/// then flips and transitions to dragging revealed state.
|
||||
/// </summary>
|
||||
public class CardPendingFaceDownState : AppleState, ICardStateDragHandler
|
||||
{
|
||||
@@ -15,6 +16,8 @@ namespace UI.CardSystem.StateMachine.States
|
||||
|
||||
private CardContext _context;
|
||||
private bool _isFlipping;
|
||||
private AlbumCardSlot _targetSlot;
|
||||
private bool _dragEndedDuringFlip = false; // Track if user released before card flip animation completed
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
@@ -26,6 +29,8 @@ namespace UI.CardSystem.StateMachine.States
|
||||
if (_context == null) return;
|
||||
|
||||
_isFlipping = false;
|
||||
_targetSlot = null;
|
||||
_dragEndedDuringFlip = false;
|
||||
|
||||
// Show card back, hide card front
|
||||
if (cardBackVisual != null)
|
||||
@@ -39,37 +44,67 @@ namespace UI.CardSystem.StateMachine.States
|
||||
_context.CardDisplay.gameObject.SetActive(false);
|
||||
_context.CardDisplay.transform.localRotation = Quaternion.Euler(0, 180, 0);
|
||||
}
|
||||
|
||||
// Scale down for corner display
|
||||
_context.RootTransform.localScale = _context.OriginalScale * 0.8f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle drag start - triggers flip animation and page navigation
|
||||
/// Handle drag start - STATE ORCHESTRATES ITS OWN FLOW
|
||||
/// </summary>
|
||||
public bool OnCardDragStarted(CardContext context)
|
||||
{
|
||||
if (_isFlipping) return true; // Already handling
|
||||
|
||||
// IMPORTANT: Data must be assigned by event listeners (AlbumViewPage) BEFORE we flip
|
||||
// The event system guarantees this because events are synchronous
|
||||
if (context.CardData == null)
|
||||
// Step 1: Find AlbumViewPage
|
||||
AlbumViewPage albumPage = Object.FindFirstObjectByType<AlbumViewPage>();
|
||||
if (albumPage == null)
|
||||
{
|
||||
Logging.Warning("[CardPendingFaceDownState] OnCardDragStarted called but no CardData assigned yet!");
|
||||
return true; // Don't flip without data
|
||||
Logging.Warning("[CardPendingFaceDownState] AlbumViewPage not found!");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Start flip animation (data is now guaranteed to be assigned)
|
||||
|
||||
// Step 2: Ask AlbumViewPage what card to display and prompt it to rebuild
|
||||
var cardData = albumPage.GetCardForPendingSlot();
|
||||
if (cardData == null)
|
||||
{
|
||||
Logging.Warning("[CardPendingFaceDownState] No card data available from AlbumViewPage!");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Step 3: Apply card data to context
|
||||
context.SetupCard(cardData);
|
||||
Logging.Debug($"[CardPendingFaceDownState] Assigned card data: {cardData.Name} ({cardData.Zone})");
|
||||
|
||||
// Step 4: Ask AlbumViewPage for target slot
|
||||
_targetSlot = albumPage.GetTargetSlotForCard(cardData);
|
||||
if (_targetSlot == null)
|
||||
{
|
||||
Logging.Warning($"[CardPendingFaceDownState] No slot found for card {cardData.DefinitionId}");
|
||||
// Still flip and show card, but won't be able to place it
|
||||
}
|
||||
|
||||
// Step 5: Request page navigation (no callback needed - AlbumViewPage tracks state)
|
||||
albumPage.NavigateToCardPage(cardData, null);
|
||||
|
||||
// Step 6: Start card flip animation
|
||||
StartFlipAnimation();
|
||||
|
||||
return true; // We handled it, prevent default DraggingState transition
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// We don't handle drag end in face-down state
|
||||
/// Handle drag end - if card flip animation still in progress, flag it for next state
|
||||
/// </summary>
|
||||
public bool OnCardDragEnded(CardContext context)
|
||||
{
|
||||
return false;
|
||||
if (_isFlipping)
|
||||
{
|
||||
// Card flip animation still in progress - user released immediately
|
||||
_dragEndedDuringFlip = true;
|
||||
Logging.Debug("[CardPendingFaceDownState] Drag ended during card flip - will pass to next state");
|
||||
return true; // We handled it
|
||||
}
|
||||
|
||||
return false; // Already transitioned to DraggingRevealedState, let it handle
|
||||
}
|
||||
|
||||
private void StartFlipAnimation()
|
||||
@@ -103,6 +138,24 @@ namespace UI.CardSystem.StateMachine.States
|
||||
private void OnFlipComplete()
|
||||
{
|
||||
// Transition to dragging revealed state
|
||||
// Pass target slot to next state (it will query AlbumViewPage for flip status)
|
||||
var card = _context.GetComponent<Card>();
|
||||
if (card != null)
|
||||
{
|
||||
var draggingState = card.GetStateComponent<CardDraggingRevealedState>("DraggingRevealedState");
|
||||
if (draggingState != null)
|
||||
{
|
||||
draggingState.SetTargetSlot(_targetSlot);
|
||||
|
||||
// If drag ended before we transitioned, tell next state to handle placement immediately
|
||||
if (_dragEndedDuringFlip)
|
||||
{
|
||||
draggingState.SetDragAlreadyEnded(true);
|
||||
Logging.Debug("[CardPendingFaceDownState] Passing drag-ended flag to DraggingRevealedState");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_context.StateMachine.ChangeState("DraggingRevealedState");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,41 +1,130 @@
|
||||
using Core;
|
||||
using Core;
|
||||
using Core.SaveLoad;
|
||||
using Data.CardSystem;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UI.CardSystem.StateMachine.States
|
||||
{
|
||||
/// <summary>
|
||||
/// Placed in slot state - card is in an album slot and can be clicked to enlarge.
|
||||
/// Manages the parent slot reference.
|
||||
/// Placed in slot state - card is being/has been placed in an album slot.
|
||||
/// SMART STATE: Handles snap-to-slot animation on entry, then finalizes placement.
|
||||
/// </summary>
|
||||
public class CardPlacedInSlotState : AppleState, ICardClickHandler
|
||||
{
|
||||
private CardContext _context;
|
||||
private AlbumCardSlot _parentSlot;
|
||||
private AlbumCardSlot _targetSlotForAnimation; // Set by DraggingRevealedState for animated placement
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_context = GetComponentInParent<CardContext>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set placement info from previous state (for animated placement from drag)
|
||||
/// </summary>
|
||||
public void SetPlacementInfo(AlbumCardSlot targetSlot)
|
||||
{
|
||||
_targetSlotForAnimation = targetSlot;
|
||||
}
|
||||
|
||||
public override void OnEnterState()
|
||||
{
|
||||
// Ensure card front is visible and facing camera
|
||||
// This is important when spawning cards directly into album (skipping booster flow)
|
||||
if (_context.CardDisplay != null)
|
||||
{
|
||||
_context.CardDisplay.gameObject.SetActive(true);
|
||||
_context.CardDisplay.transform.localRotation = Quaternion.Euler(0, 0, 0);
|
||||
}
|
||||
|
||||
Logging.Debug($"[CardPlacedInSlotState] Card placed in slot: {_context.CardData?.Name}");
|
||||
|
||||
// Card is now part of the album, no special visuals needed
|
||||
// Just wait for interaction
|
||||
// Check if this is animated placement (from drag) or direct placement (from spawn)
|
||||
if (_targetSlotForAnimation != null)
|
||||
{
|
||||
// Animated placement - play tween to slot
|
||||
Logging.Debug($"[CardPlacedInSlotState] Animating card '{_context.CardData?.Name}' to slot");
|
||||
AnimateToSlot(_targetSlotForAnimation);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Direct placement (spawned in slot) - already positioned correctly
|
||||
// Disable dragging for spawned cards too
|
||||
var card = _context.GetComponent<Card>();
|
||||
if (card != null)
|
||||
{
|
||||
card.SetDraggingEnabled(false);
|
||||
}
|
||||
Logging.Debug($"[CardPlacedInSlotState] Card '{_context.CardData?.Name}' directly placed in slot");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the parent slot this card belongs to
|
||||
/// Animate card to slot position and finalize placement
|
||||
/// </summary>
|
||||
private void AnimateToSlot(AlbumCardSlot slot)
|
||||
{
|
||||
var card = _context.GetComponent<Card>();
|
||||
if (card == null) return;
|
||||
|
||||
// Reparent to slot immediately, keeping world position
|
||||
card.transform.SetParent(slot.transform, true);
|
||||
|
||||
// Tween position, scale, rotation simultaneously
|
||||
float tweenDuration = 0.4f;
|
||||
|
||||
Pixelplacement.Tween.LocalPosition(card.transform, Vector3.zero, tweenDuration, 0f, Pixelplacement.Tween.EaseOutBack);
|
||||
Pixelplacement.Tween.LocalScale(card.transform, Vector3.one, tweenDuration, 0f, Pixelplacement.Tween.EaseOutBack);
|
||||
Pixelplacement.Tween.LocalRotation(card.transform, Quaternion.identity, tweenDuration, 0f, Pixelplacement.Tween.EaseOutBack,
|
||||
completeCallback: () => FinalizePlacement(card, slot));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalize placement after animation completes
|
||||
/// </summary>
|
||||
private void FinalizePlacement(Card card, AlbumCardSlot slot)
|
||||
{
|
||||
// Ensure final position/rotation
|
||||
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);
|
||||
}
|
||||
|
||||
// Set parent slot
|
||||
_parentSlot = slot;
|
||||
|
||||
// Disable dragging - cards in slots should only respond to clicks for enlargement
|
||||
card.SetDraggingEnabled(false);
|
||||
|
||||
// Notify slot
|
||||
slot.AssignCard(card);
|
||||
|
||||
// Mark as placed in inventory
|
||||
if (CardSystemManager.Instance != null)
|
||||
{
|
||||
CardSystemManager.Instance.MarkCardAsPlaced(card.CardData);
|
||||
}
|
||||
|
||||
// Notify AlbumViewPage for registration
|
||||
var albumPage = Object.FindFirstObjectByType<AlbumViewPage>();
|
||||
if (albumPage != null)
|
||||
{
|
||||
albumPage.NotifyCardPlaced(card);
|
||||
}
|
||||
|
||||
Logging.Debug($"[CardPlacedInSlotState] Card placement finalized: {card.CardData?.Name}");
|
||||
|
||||
// Clear animation target
|
||||
_targetSlotForAnimation = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the parent slot this card belongs to (for direct placement without animation)
|
||||
/// </summary>
|
||||
public void SetParentSlot(AlbumCardSlot slot)
|
||||
{
|
||||
|
||||
@@ -243,6 +243,10 @@ namespace UI.DragAndDrop.Core
|
||||
|
||||
public virtual void OnDrag(PointerEventData eventData)
|
||||
{
|
||||
// Just ship all this shit when disabled
|
||||
if (!_isDraggingEnabled)
|
||||
return;
|
||||
|
||||
if (!_isDragging)
|
||||
return;
|
||||
|
||||
@@ -269,6 +273,9 @@ namespace UI.DragAndDrop.Core
|
||||
|
||||
public virtual void OnEndDrag(PointerEventData eventData)
|
||||
{
|
||||
if (!_isDraggingEnabled)
|
||||
return;
|
||||
|
||||
if (!_isDragging)
|
||||
return;
|
||||
|
||||
@@ -282,14 +289,7 @@ namespace UI.DragAndDrop.Core
|
||||
if (_canvasGroup != null)
|
||||
_canvasGroup.blocksRaycasts = true;
|
||||
|
||||
// Find closest slot and snap
|
||||
FindAndSnapToSlot();
|
||||
|
||||
// Snap base rotation back to slot rotation (if in a slot)
|
||||
if (_currentSlot != null)
|
||||
{
|
||||
Tween.Rotation(transform, _currentSlot.transform.rotation, 0.3f, 0f, Tween.EaseOutBack);
|
||||
}
|
||||
// No auto-slotting - derived classes handle placement logic via OnDragEndedHook()
|
||||
|
||||
OnDragEnded?.Invoke(this);
|
||||
OnDragEndedHook();
|
||||
@@ -344,70 +344,8 @@ namespace UI.DragAndDrop.Core
|
||||
|
||||
#region Slot Management
|
||||
|
||||
protected virtual void FindAndSnapToSlot()
|
||||
{
|
||||
SlotContainer[] containers = FindObjectsByType<SlotContainer>(FindObjectsSortMode.None);
|
||||
DraggableSlot closestSlot = null;
|
||||
float closestDistance = float.MaxValue;
|
||||
|
||||
// Use RectTransform.position for overlay, transform.position for others
|
||||
Vector3 myPosition = (_canvas != null && _canvas.renderMode == RenderMode.ScreenSpaceOverlay && RectTransform != null)
|
||||
? RectTransform.position
|
||||
: transform.position;
|
||||
|
||||
foreach (var container in containers)
|
||||
{
|
||||
DraggableSlot slot = container.FindClosestSlot(myPosition, this);
|
||||
if (slot != null)
|
||||
{
|
||||
Vector3 slotPosition = slot.RectTransform != null ? slot.RectTransform.position : slot.transform.position;
|
||||
float distance = Vector3.Distance(myPosition, slotPosition);
|
||||
if (distance < closestDistance)
|
||||
{
|
||||
closestDistance = distance;
|
||||
closestSlot = slot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (closestSlot != null)
|
||||
{
|
||||
// Check if slot is occupied
|
||||
if (closestSlot.IsOccupied && closestSlot.Occupant != this)
|
||||
{
|
||||
// Swap with occupant
|
||||
SwapWithSlot(closestSlot);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Move to empty slot
|
||||
AssignToSlot(closestSlot, true);
|
||||
}
|
||||
}
|
||||
else if (_currentSlot != null)
|
||||
{
|
||||
// Return to current slot if no valid slot found
|
||||
SnapToCurrentSlot();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void SwapWithSlot(DraggableSlot targetSlot)
|
||||
{
|
||||
DraggableSlot mySlot = _currentSlot;
|
||||
DraggableObject otherObject = targetSlot.Occupant;
|
||||
|
||||
if (otherObject != null)
|
||||
{
|
||||
// Both objects swap slots
|
||||
targetSlot.Vacate();
|
||||
if (mySlot != null)
|
||||
mySlot.Vacate();
|
||||
|
||||
AssignToSlot(targetSlot, true);
|
||||
if (mySlot != null)
|
||||
otherObject.AssignToSlot(mySlot, true);
|
||||
}
|
||||
}
|
||||
// Auto-slotting removed - derived classes (Card, etc.) handle placement via state machines
|
||||
// AssignToSlot() and SnapToSlot() kept for explicit slot assignment
|
||||
|
||||
public virtual void AssignToSlot(DraggableSlot slot, bool animate)
|
||||
{
|
||||
@@ -461,14 +399,6 @@ namespace UI.DragAndDrop.Core
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void SnapToCurrentSlot()
|
||||
{
|
||||
if (_currentSlot != null)
|
||||
{
|
||||
SnapToSlot(_currentSlot);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Selection
|
||||
|
||||
Reference in New Issue
Block a user