- **Refactored Card Placement Flow** - Separated card presentation from orchestration logic - Extracted `CornerCardManager` for pending card lifecycle (spawn, shuffle, rebuild) - Extracted `AlbumNavigationService` for book page navigation and zone mapping - Extracted `CardEnlargeController` for backdrop management and card reparenting - Implemented controller pattern (non-MonoBehaviour) for complex logic - Cards now unparent from slots before rebuild to prevent premature destruction - **Improved Corner Card Display** - Fixed cards spawning on top of each other during rebuild - Implemented shuffle-to-front logic (remaining cards occupy slots 0→1→2) - Added smart card selection (prioritizes cards matching current album page) - Pending cards now removed from queue immediately on drag start - Corner cards rebuild after each placement with proper slot reassignment - **Enhanced Album Card Viewing** - Added dramatic scale increase when viewing cards from album slots - Implemented shrink animation when dismissing enlarged cards - Cards transition: `PlacedInSlotState` → `AlbumEnlargedState` → `PlacedInSlotState` - Backdrop shows/hides with card enlarge/shrink cycle - Cards reparent to enlarged container while viewing, return to slot after - **State Machine Improvements** - Added `CardStateNames` constants for type-safe state transitions - Implemented `ICardClickHandler` and `ICardStateDragHandler` interfaces - State transitions now use cached property indices - `BoosterCardContext` separated from `CardContext` for single responsibility - **Code Cleanup** - Identified unused `SlotContainerHelper.cs` (superseded by `CornerCardManager`) - Identified unused `BoosterPackDraggable.canOpenOnDrop` field - Identified unused `AlbumViewPage._previousInputMode` (hardcoded value) - Identified unused `Card.OnPlacedInAlbumSlot` event (no subscribers) Co-authored-by: Michal Pikulski <michal.a.pikulski@gmail.com> Co-authored-by: Michal Pikulski <michal@foolhardyhorizons.com> Reviewed-on: #59
177 lines
6.7 KiB
C#
177 lines
6.7 KiB
C#
using Core;
|
|
using Core.SaveLoad;
|
|
using UnityEngine;
|
|
|
|
namespace UI.CardSystem.StateMachine.States
|
|
{
|
|
/// <summary>
|
|
/// Card is in pending face-down state in corner, awaiting drag.
|
|
/// 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
|
|
{
|
|
[Header("State-Owned Visuals")]
|
|
[SerializeField] private GameObject cardBackVisual;
|
|
|
|
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()
|
|
{
|
|
_context = GetComponentInParent<CardContext>();
|
|
}
|
|
|
|
public override void OnEnterState()
|
|
{
|
|
if (_context == null) return;
|
|
|
|
_isFlipping = false;
|
|
_targetSlot = null;
|
|
_dragEndedDuringFlip = false;
|
|
|
|
// Reset scale to normal (in case transitioning from scaled state)
|
|
_context.RootTransform.localScale = Vector3.one;
|
|
|
|
// Show card back, hide card front
|
|
if (cardBackVisual != null)
|
|
{
|
|
cardBackVisual.SetActive(true);
|
|
cardBackVisual.transform.localRotation = Quaternion.Euler(0, 0, 0);
|
|
}
|
|
|
|
if (_context.CardDisplay != null)
|
|
{
|
|
_context.CardDisplay.gameObject.SetActive(false);
|
|
_context.CardDisplay.transform.localRotation = Quaternion.Euler(0, 180, 0);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle drag start - STATE ORCHESTRATES ITS OWN FLOW
|
|
/// </summary>
|
|
public bool OnCardDragStarted(CardContext context)
|
|
{
|
|
if (_isFlipping) return true; // Already handling
|
|
|
|
// Get AlbumViewPage from context (injected dependency)
|
|
var albumPage = context.AlbumViewPage;
|
|
if (albumPage == null)
|
|
{
|
|
Logging.Warning("[CardPendingFaceDownState] AlbumViewPage not injected!");
|
|
return true;
|
|
}
|
|
|
|
// 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
|
|
// Use UpdateCardData instead of SetupCard to preserve OriginalScale
|
|
// (card was already initialized with correct scale in SpawnCardInSlot)
|
|
context.UpdateCardData(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>
|
|
/// Handle drag end - if card flip animation still in progress, flag it for next state
|
|
/// </summary>
|
|
public bool OnCardDragEnded(CardContext context)
|
|
{
|
|
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()
|
|
{
|
|
_isFlipping = true;
|
|
|
|
// Scale up from corner size to normal dragging size
|
|
if (_context.Animator != null)
|
|
{
|
|
_context.Animator.AnimateScale(_context.OriginalScale * 1.15f, 0.3f);
|
|
}
|
|
|
|
// Play flip animation
|
|
if (_context.Animator != null)
|
|
{
|
|
_context.Animator.PlayFlip(
|
|
cardBack: cardBackVisual != null ? cardBackVisual.transform : null,
|
|
cardFront: _context.CardDisplay != null ? _context.CardDisplay.transform : null,
|
|
onComplete: OnFlipComplete
|
|
);
|
|
}
|
|
else
|
|
{
|
|
// No animator, just switch visibility immediately
|
|
if (cardBackVisual != null) cardBackVisual.SetActive(false);
|
|
if (_context.CardDisplay != null) _context.CardDisplay.gameObject.SetActive(true);
|
|
OnFlipComplete();
|
|
}
|
|
}
|
|
|
|
private void OnFlipComplete()
|
|
{
|
|
// Transition to dragging revealed state
|
|
// Pass target slot to next state (it will query AlbumService for flip status)
|
|
var card = _context.GetComponent<Card>();
|
|
if (card != null)
|
|
{
|
|
var draggingState = card.GetStateComponent<CardDraggingRevealedState>(CardStateNames.DraggingRevealed);
|
|
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(CardStateNames.DraggingRevealed);
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
// Hide card back when leaving state
|
|
if (cardBackVisual != null)
|
|
{
|
|
cardBackVisual.SetActive(false);
|
|
}
|
|
}
|
|
}
|
|
}
|