Files
AppleHillsProduction/Assets/Scripts/CardSystem/Testing/CardTestController.cs
tschesky 235fa04eba Merge a card refresh (#59)
- **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
2025-11-18 08:40:59 +00:00

455 lines
15 KiB
C#

using System.Collections.Generic;
using AppleHills.Data.CardSystem;
using Core;
using Core.Lifecycle;
using Input;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UI.CardSystem.StateMachine;
using UI.CardSystem.StateMachine.States;
namespace UI.CardSystem.Testing
{
/// <summary>
/// Test controller for card state machine testing.
/// Provides UI controls to manually test state transitions, animations, and flows.
/// </summary>
public class CardTestController : ManagedBehaviour
{
[Header("Test Card")]
[SerializeField] private Card testCard;
[SerializeField] private CardData testCardData;
[Header("UI References")]
[SerializeField] private TextMeshProUGUI eventLogText;
[SerializeField] private Toggle isNewToggle;
[SerializeField] private Slider repeatCountSlider;
[SerializeField] private TextMeshProUGUI repeatCountLabel;
[SerializeField] private TMP_Dropdown rarityDropdown;
[SerializeField] private Toggle isClickableToggle;
[SerializeField] private TextMeshProUGUI currentStateText;
private List<string> _eventLog = new List<string>();
private CardContext _cardContext;
private Vector3 _originalCardPosition;
private Vector3 _originalCardScale;
private Vector2 _originalAnchoredPosition;
private void Awake()
{
if (testCard != null)
{
_cardContext = testCard.GetComponent<CardContext>();
_originalCardPosition = testCard.transform.position;
_originalCardScale = testCard.transform.localScale;
// Store original anchored position if it's a RectTransform
RectTransform rectTransform = testCard.GetComponent<RectTransform>();
if (rectTransform != null)
{
_originalAnchoredPosition = rectTransform.anchoredPosition;
}
// Subscribe to card events (new simplified event model)
if (_cardContext != null)
{
// TODO: FIX
// _cardContext.OnRevealFlowComplete += OnCardRevealFlowComplete;
}
// Subscribe to drag events to ensure card snaps back when released
testCard.OnDragStarted += OnCardDragStarted;
testCard.OnDragEnded += OnCardDragEnded;
}
// Setup UI listeners
if (repeatCountSlider != null)
{
repeatCountSlider.onValueChanged.AddListener(OnRepeatCountChanged);
}
if (isClickableToggle != null)
{
isClickableToggle.onValueChanged.AddListener(OnIsClickableToggled);
}
}
internal override void OnManagedAwake()
{
base.OnManagedAwake();
InputManager.Instance.SetInputMode(InputMode.UI);
}
private void Start()
{
// Initialize card with test data
if (testCard != null && testCardData != null && _cardContext != null)
{
_cardContext.SetupCard(testCardData);
}
LogEvent("Card Test Scene Initialized");
UpdateCurrentStateDisplay();
}
private void Update()
{
// Update current state display every frame
if (Time.frameCount % 30 == 0) // Every 0.5 seconds at 60fps
{
UpdateCurrentStateDisplay();
}
}
#region State Transition Buttons
/// <summary>
/// Reset card to default state (position, scale) before transitioning to a new state.
/// This prevents accumulation of tweens and ensures animations play correctly.
/// </summary>
private void ResetCardToDefault()
{
if (testCard == null || _cardContext == null) return;
// Stop all animations
if (_cardContext.Animator != null)
{
_cardContext.Animator.StopAllAnimations();
}
// Reset transform immediately
testCard.transform.localScale = _originalCardScale;
testCard.transform.position = _originalCardPosition;
// Reset anchored position if it's a RectTransform
RectTransform rectTransform = testCard.GetComponent<RectTransform>();
if (rectTransform != null)
{
rectTransform.anchoredPosition = _originalAnchoredPosition;
}
LogEvent("Card reset to default state");
}
public void TransitionToIdleState()
{
ResetCardToDefault();
_cardContext?.StateMachine.ChangeState("IdleState");
LogEvent("Transitioned to IdleState");
}
public void TransitionToRevealedState()
{
ResetCardToDefault();
_cardContext?.StateMachine.ChangeState("RevealedState");
LogEvent("Transitioned to RevealedState");
}
public void TransitionToEnlargedNewState()
{
ResetCardToDefault();
_cardContext?.StateMachine.ChangeState("EnlargedNewState");
LogEvent("Transitioned to EnlargedNewState");
}
public void TransitionToEnlargedRepeatState()
{
ResetCardToDefault();
_cardContext?.StateMachine.ChangeState("EnlargedRepeatState");
LogEvent("Transitioned to EnlargedRepeatState");
}
public void TransitionToDraggingState()
{
ResetCardToDefault();
_cardContext?.StateMachine.ChangeState("DraggingState");
LogEvent("Transitioned to DraggingState");
}
public void TransitionToAlbumEnlargedState()
{
ResetCardToDefault();
_cardContext?.StateMachine.ChangeState("AlbumEnlargedState");
LogEvent("Transitioned to AlbumEnlargedState");
}
#endregion
#region Simulation Buttons
public void SimulateNewCardFlow()
{
if (_cardContext == null) return;
// NOTE: These properties no longer exist in CardContext (removed to prevent stale data)
// States now query CardSystemManager directly
// This test controller manually manipulates state machine for testing only
_cardContext.IsClickable = true;
TransitionToIdleState();
LogEvent("Simulating NEW CARD flow - click card to flip (test bypasses collection checks)");
}
public void SimulateRepeatCardFlow()
{
if (_cardContext == null) return;
// NOTE: RepeatCardCount removed from CardContext
// Test directly transitions to state for visual testing
_cardContext.IsClickable = true;
TransitionToIdleState();
LogEvent($"Simulating REPEAT CARD flow (test bypasses collection checks)");
}
public void SimulateUpgradeFlow()
{
if (_cardContext == null) return;
// NOTE: WillTriggerUpgrade removed from CardContext
// Test directly transitions to state for visual testing
_cardContext.IsClickable = true;
TransitionToIdleState();
LogEvent("Simulating UPGRADE flow (test bypasses collection checks)");
}
public void TestDragAndSnap()
{
if (testCard == null) return;
// Enable dragging for the test
testCard.SetDraggingEnabled(true);
TransitionToRevealedState();
LogEvent("DRAG TEST enabled - drag the card and release to see it snap back");
}
#endregion
#region Card Setup Controls
public void ApplyCardSetup()
{
if (_cardContext == null) return;
bool isNew = isNewToggle != null && isNewToggle.isOn;
int repeatCount = repeatCountSlider != null ? Mathf.RoundToInt(repeatCountSlider.value) : 0;
// Apply rarity if needed
if (rarityDropdown != null && testCardData != null)
{
testCardData.Rarity = (CardRarity)rarityDropdown.value;
}
LogEvent($"Card setup applied: IsNew={isNew}, RepeatCount={repeatCount}");
}
private void OnRepeatCountChanged(float value)
{
if (repeatCountLabel != null)
{
repeatCountLabel.text = $"{Mathf.RoundToInt(value)}/5";
}
}
private void OnIsClickableToggled(bool isClickable)
{
if (_cardContext != null)
{
_cardContext.IsClickable = isClickable;
LogEvent($"Card clickable: {isClickable}");
}
}
#endregion
#region Animation Test Buttons
public void PlayFlipAnimation()
{
// Reset card first to prevent accumulation
ResetCardToDefault();
// Transition to IdleState and programmatically trigger flip
TransitionToIdleState();
// Get IdleState and trigger click
var idleState = testCard.GetComponentInChildren<CardIdleState>();
if (idleState != null)
{
idleState.OnPointerClick(null);
LogEvent("Playing flip animation");
}
}
public void PlayEnlargeAnimation()
{
if (_cardContext?.Animator != null)
{
ResetCardToDefault();
_cardContext.Animator.PlayEnlarge(1.5f);
LogEvent("Playing enlarge animation");
}
}
public void PlayShrinkAnimation()
{
if (_cardContext?.Animator != null)
{
// Don't reset for shrink - we want to shrink from current state
_cardContext.Animator.PlayShrink(Vector3.one, null);
LogEvent("Playing shrink animation");
}
}
public void StartIdleHoverAnimation()
{
if (_cardContext?.Animator != null)
{
// Reset card position first to prevent accumulation
ResetCardToDefault();
_cardContext.Animator.StartIdleHover(10f, 1.5f, restartIfActive: true);
LogEvent("Started idle hover animation");
}
}
public void StopIdleHoverAnimation()
{
if (_cardContext?.Animator != null)
{
_cardContext.Animator.StopIdleHover(_originalAnchoredPosition);
LogEvent("Stopped idle hover animation");
}
}
#endregion
#region Utility Buttons
public void ResetCardPosition()
{
if (testCard != null)
{
testCard.transform.position = _originalCardPosition;
testCard.transform.localScale = _originalCardScale;
// Reset anchored position if it's a RectTransform
RectTransform rectTransform = testCard.GetComponent<RectTransform>();
if (rectTransform != null)
{
rectTransform.anchoredPosition = _originalAnchoredPosition;
}
LogEvent("Card position reset");
}
}
public void ClearEventLog()
{
_eventLog.Clear();
UpdateEventLog();
LogEvent("Event log cleared");
}
#endregion
#region Event Handlers
private void OnCardRevealFlowComplete(CardContext context)
{
LogEvent($"Event: OnRevealFlowComplete - Card reveal complete for {context.CardData?.Name}");
}
private void OnCardDragStarted(UI.DragAndDrop.Core.DraggableObject draggable)
{
LogEvent("Event: OnDragStarted - Card is being dragged");
}
private void OnCardDragEnded(UI.DragAndDrop.Core.DraggableObject draggable)
{
LogEvent("Event: OnDragEnded - Snapping card back to spawn point");
// Snap card back to original position (no slotting in test scene)
if (testCard != null)
{
testCard.transform.position = _originalCardPosition;
// Return to idle state after drag
TransitionToIdleState();
}
}
#endregion
#region Event Log
private void LogEvent(string message)
{
string timestamp = $"[{Time.time:F2}s]";
_eventLog.Add($"{timestamp} {message}");
// Keep only last 20 events
if (_eventLog.Count > 20)
{
_eventLog.RemoveAt(0);
}
UpdateEventLog();
Logging.Debug($"[CardTest] {message}");
}
private void UpdateEventLog()
{
if (eventLogText != null)
{
eventLogText.text = string.Join("\n", _eventLog);
}
}
private void UpdateCurrentStateDisplay()
{
if (currentStateText != null && _cardContext != null && _cardContext.StateMachine != null)
{
// Get the active state by checking which child state GameObject is active
string stateName = "Unknown";
Transform stateMachineTransform = _cardContext.StateMachine.transform;
for (int i = 0; i < stateMachineTransform.childCount; i++)
{
Transform child = stateMachineTransform.GetChild(i);
if (child.gameObject.activeSelf)
{
stateName = child.name;
break;
}
}
currentStateText.text = $"Current State: {stateName}";
}
}
#endregion
private void OnDestroy()
{
// Unsubscribe from events
if (_cardContext != null)
{
// TODO: FIX
// _cardContext.OnRevealFlowComplete -= OnCardRevealFlowComplete;
}
if (testCard != null)
{
testCard.OnDragStarted -= OnCardDragStarted;
testCard.OnDragEnded -= OnCardDragEnded;
}
}
}
}