Refactor, cleanup code and add documentaiton
This commit is contained in:
51
Assets/Scripts/CardSystem/StateMachine/BoosterCardContext.cs
Normal file
51
Assets/Scripts/CardSystem/StateMachine/BoosterCardContext.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
|
||||
namespace UI.CardSystem.StateMachine
|
||||
{
|
||||
/// <summary>
|
||||
/// Booster-specific card context for reveal flow coordination.
|
||||
/// Separated from generic CardContext to maintain single responsibility.
|
||||
/// </summary>
|
||||
public class BoosterCardContext
|
||||
{
|
||||
// Booster reveal workflow state
|
||||
private bool _hasCompletedReveal = false;
|
||||
|
||||
/// <summary>
|
||||
/// Has this card completed its booster reveal flow?
|
||||
/// </summary>
|
||||
public bool HasCompletedReveal => _hasCompletedReveal;
|
||||
|
||||
/// <summary>
|
||||
/// Suppress NEW/REPEAT badges in revealed state
|
||||
/// </summary>
|
||||
public bool SuppressRevealBadges { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when reveal flow is complete (card dismissed)
|
||||
/// </summary>
|
||||
public event Action OnRevealFlowComplete;
|
||||
|
||||
/// <summary>
|
||||
/// Signal that this card has completed its reveal flow
|
||||
/// </summary>
|
||||
public void NotifyRevealComplete()
|
||||
{
|
||||
if (!_hasCompletedReveal)
|
||||
{
|
||||
_hasCompletedReveal = true;
|
||||
OnRevealFlowComplete?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset reveal state (for card reuse/pooling)
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
_hasCompletedReveal = false;
|
||||
SuppressRevealBadges = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 484a792a835a418bb690947b82e45da7
|
||||
timeCreated: 1763421968
|
||||
406
Assets/Scripts/CardSystem/StateMachine/CardAnimator.cs
Normal file
406
Assets/Scripts/CardSystem/StateMachine/CardAnimator.cs
Normal file
@@ -0,0 +1,406 @@
|
||||
using Pixelplacement;
|
||||
using Pixelplacement.TweenSystem;
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using AppleHills.Core.Settings;
|
||||
using Core;
|
||||
|
||||
namespace UI.CardSystem.StateMachine
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles common card animations that can be reused across states.
|
||||
/// Centralizes animation logic to avoid duplication.
|
||||
/// Animates the CARD ROOT TRANSFORM (all states follow the card).
|
||||
/// </summary>
|
||||
public class CardAnimator : MonoBehaviour
|
||||
{
|
||||
private Transform _transform;
|
||||
private RectTransform _rectTransform;
|
||||
private ICardSystemSettings _settings;
|
||||
private TweenBase _activeIdleHoverTween;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_transform = transform;
|
||||
_rectTransform = GetComponent<RectTransform>();
|
||||
_settings = GameManager.GetSettingsObject<ICardSystemSettings>();
|
||||
}
|
||||
|
||||
#region Scale Animations
|
||||
|
||||
/// <summary>
|
||||
/// Animate scale to target value
|
||||
/// </summary>
|
||||
public TweenBase AnimateScale(Vector3 targetScale, float? duration = null, Action onComplete = null)
|
||||
{
|
||||
return Tween.LocalScale(_transform, targetScale, duration ?? _settings.DefaultAnimationDuration, 0f,
|
||||
Tween.EaseInOut, completeCallback: onComplete);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pulse scale animation (scale up then back to normal)
|
||||
/// </summary>
|
||||
public void PulseScale(float pulseAmount = 1.1f, float duration = 0.2f, Action onComplete = null)
|
||||
{
|
||||
Vector3 originalScale = _transform.localScale;
|
||||
Vector3 pulseScale = originalScale * pulseAmount;
|
||||
|
||||
Tween.LocalScale(_transform, pulseScale, duration, 0f, Tween.EaseOutBack,
|
||||
completeCallback: () =>
|
||||
{
|
||||
Tween.LocalScale(_transform, originalScale, duration, 0f, Tween.EaseInBack,
|
||||
completeCallback: onComplete);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pop-in animation (scale from 0 to 1 with overshoot)
|
||||
/// </summary>
|
||||
public TweenBase PopIn(float duration = 0.5f, Action onComplete = null)
|
||||
{
|
||||
_transform.localScale = Vector3.zero;
|
||||
return Tween.LocalScale(_transform, Vector3.one, duration, 0f,
|
||||
Tween.EaseOutBack, completeCallback: onComplete);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pop-out animation (scale from current to 0)
|
||||
/// </summary>
|
||||
public TweenBase PopOut(float duration = 0.3f, Action onComplete = null)
|
||||
{
|
||||
return Tween.LocalScale(_transform, Vector3.zero, duration, 0f,
|
||||
Tween.EaseInBack, completeCallback: onComplete);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Position Animations (RectTransform)
|
||||
|
||||
/// <summary>
|
||||
/// Animate anchored position (for UI elements)
|
||||
/// </summary>
|
||||
public TweenBase AnimateAnchoredPosition(Vector2 targetPosition, float? duration = null, Action onComplete = null)
|
||||
{
|
||||
if (_rectTransform == null)
|
||||
{
|
||||
Debug.LogWarning("CardAnimator: No RectTransform found for anchored position animation");
|
||||
return null;
|
||||
}
|
||||
|
||||
return Tween.AnchoredPosition(_rectTransform, targetPosition, duration ?? _settings.DefaultAnimationDuration, 0f,
|
||||
Tween.EaseInOut, completeCallback: onComplete);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Animate local position
|
||||
/// </summary>
|
||||
public TweenBase AnimateLocalPosition(Vector3 targetPosition, float? duration = null, Action onComplete = null)
|
||||
{
|
||||
return Tween.LocalPosition(_transform, targetPosition, duration ?? _settings.DefaultAnimationDuration, 0f,
|
||||
Tween.EaseInOut, completeCallback: onComplete);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Rotation Animations
|
||||
|
||||
/// <summary>
|
||||
/// Animate local rotation to target
|
||||
/// </summary>
|
||||
public TweenBase AnimateLocalRotation(Quaternion targetRotation, float? duration = null, Action onComplete = null)
|
||||
{
|
||||
return Tween.LocalRotation(_transform, targetRotation, duration ?? _settings.DefaultAnimationDuration, 0f,
|
||||
Tween.EaseInOut, completeCallback: onComplete);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotate a child object (typically used by states for CardBackVisual, etc.)
|
||||
/// </summary>
|
||||
public TweenBase AnimateChildRotation(Transform childTransform, Quaternion targetRotation,
|
||||
float duration, Action onComplete = null)
|
||||
{
|
||||
return Tween.LocalRotation(childTransform, targetRotation, duration, 0f,
|
||||
Tween.EaseInOut, completeCallback: onComplete);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Flip Animations
|
||||
|
||||
/// <summary>
|
||||
/// Play card flip animation - rotates card back from 0° to 90°, then card front from 180° to 0°
|
||||
/// Based on FlippableCard.FlipToReveal()
|
||||
/// </summary>
|
||||
public void PlayFlip(Transform cardBack, Transform cardFront, float? duration = null, Action onComplete = null)
|
||||
{
|
||||
float flipDuration = duration ?? _settings.FlipDuration;
|
||||
|
||||
// Phase 1: Rotate both to 90 degrees (edge view)
|
||||
if (cardBack != null)
|
||||
{
|
||||
Tween.LocalRotation(cardBack, Quaternion.Euler(0, 90, 0), flipDuration * 0.5f, 0f, Tween.EaseInOut);
|
||||
}
|
||||
|
||||
if (cardFront != null)
|
||||
{
|
||||
Tween.LocalRotation(cardFront, Quaternion.Euler(0, 90, 0), flipDuration * 0.5f, 0f, Tween.EaseInOut,
|
||||
completeCallback: () =>
|
||||
{
|
||||
// At edge (90°), switch visibility
|
||||
if (cardBack != null)
|
||||
cardBack.gameObject.SetActive(false);
|
||||
if (cardFront != null)
|
||||
cardFront.gameObject.SetActive(true);
|
||||
|
||||
// Phase 2: Rotate front from 90 to 0 (show at correct orientation)
|
||||
Tween.LocalRotation(cardFront, Quaternion.Euler(0, 0, 0), flipDuration * 0.5f, 0f, Tween.EaseInOut,
|
||||
completeCallback: onComplete);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Play scale punch during flip animation for extra juice
|
||||
/// Based on FlippableCard.FlipToReveal()
|
||||
/// </summary>
|
||||
public void PlayFlipScalePunch(float? punchScale = null, float? duration = null)
|
||||
{
|
||||
float punch = punchScale ?? _settings.FlipScalePunch;
|
||||
float flipDuration = duration ?? _settings.FlipDuration;
|
||||
Vector3 originalScale = _transform.localScale;
|
||||
|
||||
Tween.LocalScale(_transform, originalScale * punch, flipDuration * 0.5f, 0f, Tween.EaseOutBack,
|
||||
completeCallback: () =>
|
||||
{
|
||||
Tween.LocalScale(_transform, originalScale, flipDuration * 0.5f, 0f, Tween.EaseInBack);
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Enlarge/Shrink Animations
|
||||
|
||||
/// <summary>
|
||||
/// Enlarge card to specified scale
|
||||
/// Based on FlippableCard.EnlargeCard() and AlbumCard.EnlargeCard()
|
||||
/// </summary>
|
||||
public void PlayEnlarge(float? targetScale = null, float? duration = null, Action onComplete = null)
|
||||
{
|
||||
float scale = targetScale ?? _settings.NewCardEnlargedScale;
|
||||
float scaleDuration = duration ?? _settings.ScaleDuration;
|
||||
|
||||
Tween.LocalScale(_transform, Vector3.one * scale, scaleDuration, 0f, Tween.EaseOutBack,
|
||||
completeCallback: onComplete);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shrink card back to original scale
|
||||
/// Based on AlbumCard.ShrinkCard() and FlippableCard.ReturnToNormalSize()
|
||||
/// </summary>
|
||||
public void PlayShrink(Vector3 targetScale, float? duration = null, Action onComplete = null)
|
||||
{
|
||||
float scaleDuration = duration ?? _settings.ScaleDuration;
|
||||
|
||||
Tween.LocalScale(_transform, targetScale, scaleDuration, 0f, Tween.EaseInBack,
|
||||
completeCallback: onComplete);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Combined Animations
|
||||
|
||||
/// <summary>
|
||||
/// Hover enter animation (lift and scale)
|
||||
/// For RectTransform UI elements
|
||||
/// </summary>
|
||||
public void HoverEnter(float liftAmount = 20f, float scaleMultiplier = 1.05f,
|
||||
float duration = 0.2f, Action onComplete = null)
|
||||
{
|
||||
if (_rectTransform != null)
|
||||
{
|
||||
Vector2 currentPos = _rectTransform.anchoredPosition;
|
||||
Vector2 targetPos = currentPos + Vector2.up * liftAmount;
|
||||
|
||||
Tween.AnchoredPosition(_rectTransform, targetPos, duration, 0f, Tween.EaseOutBack);
|
||||
Tween.LocalScale(_transform, Vector3.one * scaleMultiplier, duration, 0f,
|
||||
Tween.EaseOutBack, completeCallback: onComplete);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback for non-RectTransform
|
||||
Vector3 currentPos = _transform.localPosition;
|
||||
Vector3 targetPos = currentPos + Vector3.up * liftAmount;
|
||||
|
||||
Tween.LocalPosition(_transform, targetPos, duration, 0f, Tween.EaseOutBack);
|
||||
Tween.LocalScale(_transform, Vector3.one * scaleMultiplier, duration, 0f,
|
||||
Tween.EaseOutBack, completeCallback: onComplete);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hover exit animation (return to original position and scale)
|
||||
/// </summary>
|
||||
public void HoverExit(Vector2 originalPosition, float duration = 0.2f, Action onComplete = null)
|
||||
{
|
||||
if (_rectTransform != null)
|
||||
{
|
||||
Tween.AnchoredPosition(_rectTransform, originalPosition, duration, 0f, Tween.EaseInBack);
|
||||
Tween.LocalScale(_transform, Vector3.one, duration, 0f,
|
||||
Tween.EaseInBack, completeCallback: onComplete);
|
||||
}
|
||||
else
|
||||
{
|
||||
Tween.LocalPosition(_transform, originalPosition, duration, 0f, Tween.EaseInBack);
|
||||
Tween.LocalScale(_transform, Vector3.one, duration, 0f,
|
||||
Tween.EaseInBack, completeCallback: onComplete);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Idle hover animation (gentle bobbing loop)
|
||||
/// Returns the TweenBase so caller can stop it later.
|
||||
/// Only starts if not already running, or kills and restarts.
|
||||
/// </summary>
|
||||
public TweenBase StartIdleHover(float hoverHeight = 10f, float duration = 1.5f, bool restartIfActive = false)
|
||||
{
|
||||
// If already running, either skip or restart
|
||||
if (_activeIdleHoverTween != null)
|
||||
{
|
||||
if (!restartIfActive)
|
||||
{
|
||||
// Already running, skip
|
||||
return _activeIdleHoverTween;
|
||||
}
|
||||
|
||||
// Kill existing and restart
|
||||
_activeIdleHoverTween.Stop();
|
||||
_activeIdleHoverTween = null;
|
||||
}
|
||||
|
||||
if (_rectTransform != null)
|
||||
{
|
||||
Vector2 originalPos = _rectTransform.anchoredPosition;
|
||||
Vector2 targetPos = originalPos + Vector2.up * hoverHeight;
|
||||
|
||||
_activeIdleHoverTween = Tween.Value(0f, 1f,
|
||||
(val) =>
|
||||
{
|
||||
if (_rectTransform != null)
|
||||
{
|
||||
float t = Mathf.Sin(val * Mathf.PI * 2f) * 0.5f + 0.5f;
|
||||
_rectTransform.anchoredPosition = Vector2.Lerp(originalPos, targetPos, t);
|
||||
}
|
||||
},
|
||||
duration, 0f, Tween.EaseInOut, Tween.LoopType.Loop);
|
||||
|
||||
return _activeIdleHoverTween;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop idle hover animation and return to original position
|
||||
/// </summary>
|
||||
public void StopIdleHover(Vector2 originalPosition, float duration = 0.3f)
|
||||
{
|
||||
// Stop the tracked tween if it exists
|
||||
if (_activeIdleHoverTween != null)
|
||||
{
|
||||
_activeIdleHoverTween.Stop();
|
||||
_activeIdleHoverTween = null;
|
||||
}
|
||||
|
||||
// Return to original position
|
||||
if (_rectTransform != null)
|
||||
{
|
||||
Tween.AnchoredPosition(_rectTransform, originalPosition, duration, 0f, Tween.EaseInOut);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Flip Animations (Two-Phase)
|
||||
|
||||
/// <summary>
|
||||
/// Flip animation: Phase 1 - Rotate card back to edge (0° to 90°)
|
||||
/// Used by FlippingState to hide the back
|
||||
/// </summary>
|
||||
public void FlipPhase1_HideBack(Transform cardBackTransform, float duration, Action onHalfwayComplete)
|
||||
{
|
||||
Tween.LocalRotation(cardBackTransform, Quaternion.Euler(0, 90, 0), duration, 0f,
|
||||
Tween.EaseInOut, completeCallback: onHalfwayComplete);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flip animation: Phase 2 - Rotate card front from back to face (180° to 90° to 0°)
|
||||
/// Used by FlippingState to reveal the front
|
||||
/// </summary>
|
||||
public void FlipPhase2_RevealFront(Transform cardFrontTransform, float duration, Action onComplete)
|
||||
{
|
||||
// First rotate from 180 to 90 (edge)
|
||||
Tween.LocalRotation(cardFrontTransform, Quaternion.Euler(0, 90, 0), duration, 0f,
|
||||
Tween.EaseInOut,
|
||||
completeCallback: () =>
|
||||
{
|
||||
// Then rotate from 90 to 0 (face)
|
||||
Tween.LocalRotation(cardFrontTransform, Quaternion.Euler(0, 0, 0), duration, 0f,
|
||||
Tween.EaseInOut, completeCallback: onComplete);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scale punch during flip (makes flip more juicy)
|
||||
/// </summary>
|
||||
public void FlipScalePunch(float punchMultiplier = 1.1f, float totalDuration = 0.6f)
|
||||
{
|
||||
Vector3 originalScale = _transform.localScale;
|
||||
Vector3 punchScale = originalScale * punchMultiplier;
|
||||
|
||||
Tween.LocalScale(_transform, punchScale, totalDuration * 0.5f, 0f, Tween.EaseOutBack,
|
||||
completeCallback: () =>
|
||||
{
|
||||
Tween.LocalScale(_transform, originalScale, totalDuration * 0.5f, 0f, Tween.EaseInBack);
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Utility
|
||||
|
||||
/// <summary>
|
||||
/// Stop all active tweens on this transform
|
||||
/// </summary>
|
||||
public void StopAllAnimations()
|
||||
{
|
||||
Tween.Stop(_transform.GetInstanceID());
|
||||
if (_rectTransform != null)
|
||||
Tween.Stop(_rectTransform.GetInstanceID());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset transform to default values
|
||||
/// </summary>
|
||||
public void ResetTransform()
|
||||
{
|
||||
StopAllAnimations();
|
||||
_transform.localPosition = Vector3.zero;
|
||||
_transform.localRotation = Quaternion.identity;
|
||||
_transform.localScale = Vector3.one;
|
||||
|
||||
if (_rectTransform != null)
|
||||
_rectTransform.anchoredPosition = Vector2.zero;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get current anchored position (useful for saving before hover)
|
||||
/// </summary>
|
||||
public Vector2 GetAnchoredPosition()
|
||||
{
|
||||
return _rectTransform != null ? _rectTransform.anchoredPosition : Vector2.zero;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5eacab725f4346d091696042b9cd2a82
|
||||
timeCreated: 1762887143
|
||||
19
Assets/Scripts/CardSystem/StateMachine/CardStateMachine.cs
Normal file
19
Assets/Scripts/CardSystem/StateMachine/CardStateMachine.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Core.SaveLoad;
|
||||
|
||||
namespace UI.CardSystem.StateMachine
|
||||
{
|
||||
/// <summary>
|
||||
/// Card state machine that opts out of save system.
|
||||
/// Cards are transient UI elements that don't need persistence.
|
||||
/// </summary>
|
||||
public class CardStateMachine : AppleMachine
|
||||
{
|
||||
/// <summary>
|
||||
/// Opt out of save/load system - cards are transient and spawned from data.
|
||||
/// </summary>
|
||||
public override bool ShouldParticipateInSave()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 87ed5616041a4d878f452a8741e1eeab
|
||||
timeCreated: 1763385180
|
||||
26
Assets/Scripts/CardSystem/StateMachine/CardStateNames.cs
Normal file
26
Assets/Scripts/CardSystem/StateMachine/CardStateNames.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace UI.CardSystem.StateMachine
|
||||
{
|
||||
/// <summary>
|
||||
/// Centralized string constants for card state names.
|
||||
/// Prevents typos and provides compile-time checking for state transitions.
|
||||
/// </summary>
|
||||
public static class CardStateNames
|
||||
{
|
||||
// Booster Flow States
|
||||
public const string Idle = "IdleState";
|
||||
public const string EnlargedNew = "EnlargedNewState";
|
||||
public const string EnlargedRepeat = "EnlargedRepeatState";
|
||||
public const string EnlargedLegendaryRepeat = "EnlargedLegendaryRepeatState";
|
||||
public const string Revealed = "RevealedState";
|
||||
|
||||
// Album Placement Flow States
|
||||
public const string PendingFaceDown = "PendingFaceDownState";
|
||||
public const string DraggingRevealed = "DraggingRevealedState";
|
||||
public const string PlacedInSlot = "PlacedInSlotState";
|
||||
public const string AlbumEnlarged = "AlbumEnlargedState";
|
||||
|
||||
// Generic Drag State
|
||||
public const string Dragging = "DraggingState";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: de659f0ceb6f4ef6bab333d5f7de00ec
|
||||
timeCreated: 1763421948
|
||||
11
Assets/Scripts/CardSystem/StateMachine/ICardClickHandler.cs
Normal file
11
Assets/Scripts/CardSystem/StateMachine/ICardClickHandler.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace UI.CardSystem.StateMachine
|
||||
{
|
||||
/// <summary>
|
||||
/// Implement on a state component to receive routed click events
|
||||
/// from CardContext/CardDisplay.
|
||||
/// </summary>
|
||||
public interface ICardClickHandler
|
||||
{
|
||||
void OnCardClicked(CardContext context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fadf99afe6cc4785a6f45a47b4463923
|
||||
timeCreated: 1763307472
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace UI.CardSystem.StateMachine
|
||||
{
|
||||
/// <summary>
|
||||
/// Implement on a state component to receive routed drag events from Card.
|
||||
/// Similar to ICardClickHandler but for drag behavior.
|
||||
/// </summary>
|
||||
public interface ICardStateDragHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when drag starts. Return true to handle drag (prevent default DraggingState transition).
|
||||
/// </summary>
|
||||
bool OnCardDragStarted(CardContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Called when drag ends. Return true to handle drag end (prevent default state transitions).
|
||||
/// </summary>
|
||||
bool OnCardDragEnded(CardContext context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fc610b791f43409e8231085a70514e2c
|
||||
timeCreated: 1763374419
|
||||
3
Assets/Scripts/CardSystem/StateMachine/States.meta
Normal file
3
Assets/Scripts/CardSystem/StateMachine/States.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 43f3b0a00e934598a6a58abad11930a4
|
||||
timeCreated: 1762884650
|
||||
@@ -0,0 +1,126 @@
|
||||
using Core;
|
||||
using Core.SaveLoad;
|
||||
using UnityEngine;
|
||||
using AppleHills.Core.Settings;
|
||||
using Pixelplacement;
|
||||
|
||||
namespace UI.CardSystem.StateMachine.States
|
||||
{
|
||||
/// <summary>
|
||||
/// Album enlarged state - card is enlarged when clicked from album slot.
|
||||
/// Different from EnlargedNewState as it doesn't show "NEW" badge.
|
||||
/// </summary>
|
||||
public class CardAlbumEnlargedState : AppleState, ICardClickHandler
|
||||
{
|
||||
private CardContext _context;
|
||||
private ICardSystemSettings _settings;
|
||||
private Vector3 _originalScale;
|
||||
private Transform _originalParent;
|
||||
private Vector3 _originalLocalPosition;
|
||||
private Quaternion _originalLocalRotation;
|
||||
private Vector3 _originalWorldPosition;
|
||||
|
||||
// Events for page to manage backdrop and reparenting
|
||||
public event System.Action<CardAlbumEnlargedState> OnEnlargeRequested;
|
||||
public event System.Action<CardAlbumEnlargedState> OnShrinkRequested;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_context = GetComponentInParent<CardContext>();
|
||||
_settings = GameManager.GetSettingsObject<ICardSystemSettings>();
|
||||
}
|
||||
|
||||
public override void OnEnterState()
|
||||
{
|
||||
// Ensure card front is visible and facing camera
|
||||
if (_context.CardDisplay != null)
|
||||
{
|
||||
_context.CardDisplay.gameObject.SetActive(true);
|
||||
_context.CardDisplay.transform.localRotation = Quaternion.Euler(0, 0, 0);
|
||||
}
|
||||
|
||||
// Store original transform for restoration
|
||||
_originalScale = _context.RootTransform.localScale;
|
||||
_originalParent = _context.RootTransform.parent;
|
||||
_originalLocalPosition = _context.RootTransform.localPosition;
|
||||
_originalLocalRotation = _context.RootTransform.localRotation;
|
||||
_originalWorldPosition = _context.RootTransform.position;
|
||||
|
||||
// Notify page to show backdrop and reparent card to top layer (preserve world transform)
|
||||
OnEnlargeRequested?.Invoke(this);
|
||||
|
||||
// Animate into center and scale up significantly
|
||||
if (_context.Animator != null)
|
||||
{
|
||||
// Move to center of enlarged container (assumes zero is centered)
|
||||
_context.Animator.AnimateLocalPosition(Vector3.zero, _settings.ScaleDuration);
|
||||
// Enlarge using settings-controlled scale
|
||||
_context.Animator.PlayEnlarge(_settings.AlbumCardEnlargedScale, _settings.ScaleDuration);
|
||||
}
|
||||
|
||||
Logging.Debug($"[CardAlbumEnlargedState] Card enlarged from album: {_context.CardData?.Name}");
|
||||
}
|
||||
|
||||
public void OnCardClicked(CardContext context)
|
||||
{
|
||||
// Click to shrink back (play reverse of opening animation)
|
||||
Logging.Debug($"[CardAlbumEnlargedState] Card clicked while enlarged, shrinking back");
|
||||
|
||||
// Ask page to hide backdrop (state will handle moving back & reparenting)
|
||||
OnShrinkRequested?.Invoke(this);
|
||||
|
||||
// Animate back to original world position + shrink to original scale
|
||||
float duration = _settings.ScaleDuration;
|
||||
|
||||
// Move using world-space tween so we land exactly on the original position
|
||||
Tween.Position(context.RootTransform, _originalWorldPosition, duration, 0f, Tween.EaseInOut);
|
||||
|
||||
if (context.Animator != null)
|
||||
{
|
||||
context.Animator.PlayShrink(_originalScale, duration, onComplete: () =>
|
||||
{
|
||||
// Reparent back to original hierarchy and restore local transform
|
||||
context.RootTransform.SetParent(_originalParent, true);
|
||||
context.RootTransform.localPosition = _originalLocalPosition;
|
||||
context.RootTransform.localRotation = _originalLocalRotation;
|
||||
|
||||
// Transition back to placed state
|
||||
context.StateMachine.ChangeState(CardStateNames.PlacedInSlot);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback if no animator: snap back
|
||||
context.RootTransform.position = _originalWorldPosition;
|
||||
context.RootTransform.SetParent(_originalParent, true);
|
||||
context.RootTransform.localPosition = _originalLocalPosition;
|
||||
context.RootTransform.localRotation = _originalLocalRotation;
|
||||
context.StateMachine.ChangeState(CardStateNames.PlacedInSlot);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get original parent for restoration
|
||||
/// </summary>
|
||||
public Transform GetOriginalParent() => _originalParent;
|
||||
|
||||
/// <summary>
|
||||
/// Get original local position for restoration
|
||||
/// </summary>
|
||||
public Vector3 GetOriginalLocalPosition() => _originalLocalPosition;
|
||||
|
||||
/// <summary>
|
||||
/// Get original local rotation for restoration
|
||||
/// </summary>
|
||||
public Quaternion GetOriginalLocalRotation() => _originalLocalRotation;
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
// Restore original scale when exiting
|
||||
if (_context?.RootTransform != null)
|
||||
{
|
||||
_context.RootTransform.localScale = _originalScale;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5f33526d9bb8458d8dc5ba41a88561da
|
||||
timeCreated: 1762884900
|
||||
@@ -0,0 +1,169 @@
|
||||
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 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;
|
||||
if (_context.CardDisplay != null)
|
||||
{
|
||||
_context.CardDisplay.gameObject.SetActive(true);
|
||||
_context.CardDisplay.transform.localRotation = Quaternion.Euler(0,0,0);
|
||||
}
|
||||
_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>
|
||||
/// Already in dragging state, nothing to do
|
||||
/// </summary>
|
||||
public bool OnCardDragStarted(CardContext ctx)
|
||||
{
|
||||
return true; // Prevent default DraggingState transition
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle drag end - query AlbumViewPage for page flip status and place accordingly
|
||||
/// </summary>
|
||||
public bool OnCardDragEnded(CardContext ctx)
|
||||
{
|
||||
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(CardStateNames.PendingFaceDown);
|
||||
return;
|
||||
}
|
||||
|
||||
// Query AlbumViewPage for page flip status
|
||||
var albumPage = _context.AlbumViewPage;
|
||||
if (albumPage == null)
|
||||
{
|
||||
Logging.Warning("[CardDraggingRevealedState] AlbumViewPage not injected - 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>(CardStateNames.PlacedInSlot);
|
||||
if (placedState != null)
|
||||
{
|
||||
placedState.SetPlacementInfo(_targetSlot);
|
||||
}
|
||||
}
|
||||
|
||||
// Transition to PlacedInSlotState
|
||||
// The state will handle animation and finalization in OnEnterState
|
||||
ctx.StateMachine.ChangeState(CardStateNames.PlacedInSlot);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (_context?.RootTransform != null)
|
||||
{
|
||||
_context.RootTransform.localScale = _originalScale;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ce2483293cdd4680b5095afc1fcb2fde
|
||||
timeCreated: 1763322199
|
||||
@@ -0,0 +1,54 @@
|
||||
using Core.SaveLoad;
|
||||
using UnityEngine;
|
||||
using Core;
|
||||
using AppleHills.Core.Settings;
|
||||
|
||||
namespace UI.CardSystem.StateMachine.States
|
||||
{
|
||||
/// <summary>
|
||||
/// Dragging state - provides visual feedback when card is being dragged.
|
||||
/// The actual drag logic is handled by Card.cs (inherits from DraggableObject).
|
||||
/// This state only manages visual scaling during drag.
|
||||
/// </summary>
|
||||
public class CardDraggingState : AppleState
|
||||
{
|
||||
private CardContext _context;
|
||||
private ICardSystemSettings _settings;
|
||||
private Vector3 _originalScale;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_context = GetComponentInParent<CardContext>();
|
||||
_settings = GameManager.GetSettingsObject<ICardSystemSettings>();
|
||||
}
|
||||
|
||||
public override void OnEnterState()
|
||||
{
|
||||
// Ensure card front is visible and facing camera (in case we transitioned from an unexpected state)
|
||||
if (_context.CardDisplay != null)
|
||||
{
|
||||
_context.CardDisplay.gameObject.SetActive(true);
|
||||
_context.CardDisplay.transform.localRotation = Quaternion.Euler(0, 0, 0);
|
||||
}
|
||||
|
||||
// Store original scale
|
||||
_originalScale = _context.RootTransform.localScale;
|
||||
|
||||
// Scale up slightly during drag for visual feedback
|
||||
// DraggableObject handles actual position updates
|
||||
_context.RootTransform.localScale = _originalScale * _settings.DragScale;
|
||||
|
||||
Logging.Debug($"[CardDraggingState] Entered drag state for card: {_context.CardData?.Name}, scale: {_settings.DragScale}");
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
// Restore original scale when exiting drag
|
||||
if (_context?.RootTransform != null)
|
||||
{
|
||||
_context.RootTransform.localScale = _originalScale;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b17e4e1d7139446c9c4e0a813067331c
|
||||
timeCreated: 1762884899
|
||||
@@ -0,0 +1,53 @@
|
||||
// filepath: Assets/Scripts/UI/CardSystem/StateMachine/States/CardEnlargedLegendaryRepeatState.cs
|
||||
using Core;
|
||||
using Core.SaveLoad;
|
||||
using UnityEngine;
|
||||
using AppleHills.Core.Settings;
|
||||
|
||||
namespace UI.CardSystem.StateMachine.States
|
||||
{
|
||||
/// <summary>
|
||||
/// Enlarged state specifically for Legendary rarity presentation after an upgrade.
|
||||
/// Shows the legendary card enlarged and awaits a click to shrink back to revealed state.
|
||||
/// </summary>
|
||||
public class CardEnlargedLegendaryRepeatState : AppleState, ICardClickHandler
|
||||
{
|
||||
private CardContext _context;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_context = GetComponentInParent<CardContext>();
|
||||
}
|
||||
|
||||
public override void OnEnterState()
|
||||
{
|
||||
// Ensure card front is visible and facing camera
|
||||
if (_context.CardDisplay != null)
|
||||
{
|
||||
_context.CardDisplay.gameObject.SetActive(true);
|
||||
_context.CardDisplay.transform.localRotation = Quaternion.Euler(0, 0, 0);
|
||||
}
|
||||
|
||||
// Card is already enlarged from EnlargedRepeatState, so no need to enlarge again
|
||||
// Just await click to dismiss
|
||||
|
||||
Logging.Debug($"[CardEnlargedLegendaryRepeatState] Legendary card enlarged: {_context.CardData?.Name}");
|
||||
}
|
||||
|
||||
public void OnCardClicked(CardContext context)
|
||||
{
|
||||
// Click to shrink to original scale and go to revealed state
|
||||
if (context.Animator != null)
|
||||
{
|
||||
context.Animator.PlayShrink(context.OriginalScale, onComplete: () =>
|
||||
{
|
||||
context.StateMachine.ChangeState("RevealedState");
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
context.StateMachine.ChangeState("RevealedState");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 874e5574663a48b8a4feb3192821679a
|
||||
timeCreated: 1763319614
|
||||
@@ -0,0 +1,80 @@
|
||||
using Core.SaveLoad;
|
||||
using UnityEngine;
|
||||
using AppleHills.Core.Settings;
|
||||
using Core;
|
||||
|
||||
namespace UI.CardSystem.StateMachine.States
|
||||
{
|
||||
/// <summary>
|
||||
/// Enlarged state for NEW cards - shows "NEW CARD" badge and waits for tap to dismiss.
|
||||
/// Owns the NewCardBadge as a child GameObject.
|
||||
/// </summary>
|
||||
public class CardEnlargedNewState : AppleState, ICardClickHandler
|
||||
{
|
||||
[Header("State-Owned Visuals")]
|
||||
[SerializeField] private GameObject newCardBadge;
|
||||
|
||||
private CardContext _context;
|
||||
private ICardSystemSettings _settings;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_context = GetComponentInParent<CardContext>();
|
||||
_settings = GameManager.GetSettingsObject<ICardSystemSettings>();
|
||||
}
|
||||
|
||||
public override void OnEnterState()
|
||||
{
|
||||
// Ensure card front is visible and facing camera
|
||||
if (_context.CardDisplay != null)
|
||||
{
|
||||
_context.CardDisplay.gameObject.SetActive(true);
|
||||
_context.CardDisplay.transform.localRotation = Quaternion.Euler(0, 0, 0);
|
||||
}
|
||||
|
||||
// Check if we're already enlarged (coming from upgrade flow)
|
||||
bool alreadyEnlarged = _context.RootTransform.localScale.x >= _settings.NewCardEnlargedScale * 0.9f;
|
||||
|
||||
if (!alreadyEnlarged)
|
||||
{
|
||||
// Normal flow - enlarge the card
|
||||
if (_context.Animator != null)
|
||||
{
|
||||
_context.Animator.PlayEnlarge(_settings.NewCardEnlargedScale);
|
||||
}
|
||||
}
|
||||
|
||||
// Show NEW badge
|
||||
if (newCardBadge != null)
|
||||
{
|
||||
newCardBadge.SetActive(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnCardClicked(CardContext context)
|
||||
{
|
||||
// Tap to dismiss - shrink back to original scale and transition to revealed state
|
||||
if (context.Animator != null)
|
||||
{
|
||||
context.Animator.PlayShrink(context.OriginalScale, onComplete: () =>
|
||||
{
|
||||
context.StateMachine.ChangeState(CardStateNames.Revealed);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback if no animator
|
||||
context.StateMachine.ChangeState(CardStateNames.Revealed);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
// Hide NEW badge when leaving state
|
||||
if (newCardBadge != null)
|
||||
{
|
||||
newCardBadge.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 698741a53f314b598af359a81d914ed3
|
||||
timeCreated: 1762884651
|
||||
@@ -0,0 +1,217 @@
|
||||
using Core.SaveLoad;
|
||||
using UnityEngine;
|
||||
using AppleHills.Core.Settings;
|
||||
using Core;
|
||||
using AppleHills.Data.CardSystem;
|
||||
|
||||
namespace UI.CardSystem.StateMachine.States
|
||||
{
|
||||
/// <summary>
|
||||
/// Enlarged state for REPEAT cards - shows progress bar toward next rarity upgrade.
|
||||
/// Uses ProgressBarController to animate progress filling.
|
||||
/// Auto-upgrades card when threshold is reached.
|
||||
/// </summary>
|
||||
public class CardEnlargedRepeatState : AppleState, ICardClickHandler
|
||||
{
|
||||
[Header("State-Owned Visuals")]
|
||||
[SerializeField] private ProgressBarController progressBar;
|
||||
|
||||
private CardContext _context;
|
||||
private ICardSystemSettings _settings;
|
||||
private bool _waitingForTap = false;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_context = GetComponentInParent<CardContext>();
|
||||
_settings = GameManager.GetSettingsObject<ICardSystemSettings>();
|
||||
}
|
||||
|
||||
public override void OnEnterState()
|
||||
{
|
||||
// Ensure card front is visible and facing camera
|
||||
if (_context.CardDisplay != null)
|
||||
{
|
||||
_context.CardDisplay.gameObject.SetActive(true);
|
||||
_context.CardDisplay.transform.localRotation = Quaternion.Euler(0, 0, 0);
|
||||
}
|
||||
|
||||
_waitingForTap = false;
|
||||
|
||||
// Query current collection state for this card
|
||||
bool isNew = Data.CardSystem.CardSystemManager.Instance.IsCardNew(_context.CardData, out CardData existingCard);
|
||||
int currentOwnedCount = (existingCard != null) ? existingCard.CopiesOwned : 0;
|
||||
|
||||
// Show progress bar
|
||||
if (progressBar != null)
|
||||
{
|
||||
progressBar.gameObject.SetActive(true);
|
||||
|
||||
int currentCount = currentOwnedCount + 1; // +1 because we just got this card
|
||||
int maxCount = _settings.CardsToUpgrade;
|
||||
|
||||
progressBar.ShowProgress(currentCount, maxCount, OnProgressComplete);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logging.Warning("[CardEnlargedRepeatState] ProgressBar component not assigned!");
|
||||
OnProgressComplete();
|
||||
}
|
||||
|
||||
// Enlarge the card
|
||||
if (_context.Animator != null)
|
||||
{
|
||||
_context.Animator.PlayEnlarge(_settings.NewCardEnlargedScale);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnProgressComplete()
|
||||
{
|
||||
// Query current state again to determine if upgrade is triggered
|
||||
Data.CardSystem.CardSystemManager.Instance.IsCardNew(_context.CardData, out CardData existingCard);
|
||||
int currentOwnedCount = (existingCard != null) ? existingCard.CopiesOwned : 0;
|
||||
int countWithThisCard = currentOwnedCount + 1;
|
||||
|
||||
bool willUpgrade = (_context.CardData.Rarity < AppleHills.Data.CardSystem.CardRarity.Legendary) &&
|
||||
(countWithThisCard >= _settings.CardsToUpgrade);
|
||||
|
||||
if (willUpgrade)
|
||||
{
|
||||
Logging.Debug($"[CardEnlargedRepeatState] Card will trigger upgrade! ({countWithThisCard}/{_settings.CardsToUpgrade})");
|
||||
TriggerUpgrade();
|
||||
}
|
||||
else
|
||||
{
|
||||
// No upgrade - just wait for tap to dismiss
|
||||
Logging.Debug($"[CardEnlargedRepeatState] Progress shown ({countWithThisCard}/{_settings.CardsToUpgrade}), waiting for tap to dismiss");
|
||||
_waitingForTap = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void TriggerUpgrade()
|
||||
{
|
||||
CardData cardData = _context.CardData;
|
||||
CardRarity oldRarity = cardData.Rarity;
|
||||
CardRarity newRarity = oldRarity + 1;
|
||||
|
||||
Logging.Debug($"[CardEnlargedRepeatState] Upgrading card from {oldRarity} to {newRarity}");
|
||||
|
||||
var inventory = Data.CardSystem.CardSystemManager.Instance.GetCardInventory();
|
||||
|
||||
// Remove lower rarity card counts (set to 1 per new rule instead of zeroing out)
|
||||
CardRarity clearRarity = cardData.Rarity;
|
||||
while (clearRarity < newRarity)
|
||||
{
|
||||
var lower = inventory.GetCard(cardData.DefinitionId, clearRarity);
|
||||
if (lower != null) lower.CopiesOwned = 1; // changed from 0 to 1
|
||||
clearRarity += 1;
|
||||
}
|
||||
|
||||
// Check if higher rarity already exists BEFORE adding
|
||||
CardData existingHigher = inventory.GetCard(cardData.DefinitionId, newRarity);
|
||||
bool higherExists = existingHigher != null;
|
||||
|
||||
if (higherExists)
|
||||
{
|
||||
// Increment existing higher rarity copies
|
||||
existingHigher.CopiesOwned += 1;
|
||||
|
||||
// Update our displayed card to new rarity
|
||||
cardData.Rarity = newRarity;
|
||||
cardData.CopiesOwned = existingHigher.CopiesOwned; // reflect correct count
|
||||
|
||||
if (_context.CardDisplay != null)
|
||||
{
|
||||
_context.CardDisplay.SetupCard(cardData);
|
||||
}
|
||||
|
||||
// For repeat-at-higher-rarity: show a brief progress update at higher rarity while enlarged
|
||||
int ownedAtHigher = existingHigher.CopiesOwned;
|
||||
if (progressBar != null)
|
||||
{
|
||||
progressBar.ShowProgress(ownedAtHigher, _settings.CardsToUpgrade, () =>
|
||||
{
|
||||
// After showing higher-rarity progress, wait for tap to dismiss
|
||||
_waitingForTap = true;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
_waitingForTap = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create upgraded card as new rarity
|
||||
CardData upgradedCard = new CardData(cardData);
|
||||
upgradedCard.Rarity = newRarity;
|
||||
upgradedCard.CopiesOwned = 1;
|
||||
|
||||
// Add to inventory
|
||||
inventory.AddCard(upgradedCard);
|
||||
|
||||
// Update current display card to new rarity
|
||||
cardData.Rarity = newRarity;
|
||||
cardData.CopiesOwned = upgradedCard.CopiesOwned;
|
||||
|
||||
if (_context.CardDisplay != null)
|
||||
{
|
||||
_context.CardDisplay.SetupCard(cardData);
|
||||
}
|
||||
|
||||
// Branch based on whether legendary or not
|
||||
if (newRarity == CardRarity.Legendary)
|
||||
{
|
||||
// Show special enlarged legendary presentation, await click to shrink to revealed
|
||||
_context.StateMachine.ChangeState(CardStateNames.EnlargedLegendaryRepeat);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Treat as NEW at higher rarity (enlarged with NEW visuals handled there)
|
||||
_context.StateMachine.ChangeState(CardStateNames.EnlargedNew);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TransitionToNewCardView()
|
||||
{
|
||||
// Hide progress bar before transitioning
|
||||
if (progressBar != null)
|
||||
{
|
||||
progressBar.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
// Transition to EnlargedNewState (card is already enlarged, will show NEW badge)
|
||||
// State will query fresh collection data to determine if truly new
|
||||
_context.StateMachine.ChangeState(CardStateNames.EnlargedNew);
|
||||
}
|
||||
|
||||
public void OnCardClicked(CardContext context)
|
||||
{
|
||||
if (!_waitingForTap)
|
||||
return;
|
||||
|
||||
|
||||
// Tap to dismiss - shrink back to original scale and transition to revealed state
|
||||
if (context.Animator != null)
|
||||
{
|
||||
context.Animator.PlayShrink(context.OriginalScale, onComplete: () =>
|
||||
{
|
||||
context.StateMachine.ChangeState(CardStateNames.Revealed);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
context.StateMachine.ChangeState("RevealedState");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
// Hide progress bar when leaving state
|
||||
if (progressBar != null)
|
||||
{
|
||||
progressBar.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 257f0c81caa14481812a8ca0397bf567
|
||||
timeCreated: 1762884651
|
||||
177
Assets/Scripts/CardSystem/StateMachine/States/CardIdleState.cs
Normal file
177
Assets/Scripts/CardSystem/StateMachine/States/CardIdleState.cs
Normal file
@@ -0,0 +1,177 @@
|
||||
using Core.SaveLoad;
|
||||
using Pixelplacement.TweenSystem;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using AppleHills.Core.Settings;
|
||||
using AppleHills.Data.CardSystem;
|
||||
using Core;
|
||||
|
||||
namespace UI.CardSystem.StateMachine.States
|
||||
{
|
||||
/// <summary>
|
||||
/// Idle state - card back is showing with gentle hover animation.
|
||||
/// Waiting for click to flip and reveal.
|
||||
/// Based on FlippableCard's idle behavior.
|
||||
/// </summary>
|
||||
public class CardIdleState : AppleState, ICardClickHandler, IPointerEnterHandler, IPointerExitHandler, IPointerClickHandler
|
||||
{
|
||||
[Header("State-Owned Visuals")]
|
||||
[SerializeField] private GameObject cardBackVisual;
|
||||
|
||||
[Header("Idle Hover Settings")]
|
||||
[SerializeField] private bool enableIdleHover = true;
|
||||
|
||||
private CardContext _context;
|
||||
private ICardSystemSettings _settings;
|
||||
private TweenBase _idleHoverTween;
|
||||
private Vector2 _originalPosition;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_context = GetComponentInParent<CardContext>();
|
||||
_settings = GameManager.GetSettingsObject<ICardSystemSettings>();
|
||||
}
|
||||
|
||||
public override void OnEnterState()
|
||||
{
|
||||
// Show card back, hide card front
|
||||
if (cardBackVisual != null)
|
||||
{
|
||||
cardBackVisual.SetActive(true);
|
||||
// Ensure card back is at 0° rotation (facing camera)
|
||||
cardBackVisual.transform.localRotation = Quaternion.Euler(0, 0, 0);
|
||||
}
|
||||
|
||||
if (_context.CardDisplay != null)
|
||||
{
|
||||
_context.CardDisplay.gameObject.SetActive(false);
|
||||
// Ensure card front starts at 180° rotation (flipped away)
|
||||
_context.CardDisplay.transform.localRotation = Quaternion.Euler(0, 180, 0);
|
||||
}
|
||||
|
||||
// Save original position for hover animation
|
||||
RectTransform rectTransform = _context.RootTransform.GetComponent<RectTransform>();
|
||||
if (rectTransform != null)
|
||||
{
|
||||
_originalPosition = rectTransform.anchoredPosition;
|
||||
}
|
||||
|
||||
// Start idle hover animation
|
||||
if (enableIdleHover && _context.Animator != null)
|
||||
{
|
||||
_idleHoverTween = _context.Animator.StartIdleHover(_settings.IdleHoverHeight, _settings.IdleHoverDuration);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPointerEnter(PointerEventData eventData)
|
||||
{
|
||||
// Scale up slightly on hover
|
||||
if (_context.Animator != null)
|
||||
{
|
||||
_context.Animator.AnimateScale(Vector3.one * _settings.HoverScaleMultiplier, 0.2f);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPointerExit(PointerEventData eventData)
|
||||
{
|
||||
// Scale back to normal
|
||||
if (_context.Animator != null)
|
||||
{
|
||||
_context.Animator.AnimateScale(Vector3.one, 0.2f);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnCardClicked(CardContext context)
|
||||
{
|
||||
// Check if card is clickable (prevents multi-flip in booster opening)
|
||||
if (!context.IsClickable)
|
||||
{
|
||||
Logging.Debug($"[CardIdleState] Card is not clickable, ignoring click");
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop idle hover and pointer interactions
|
||||
StopIdleHover();
|
||||
|
||||
// Play flip animation directly
|
||||
if (context.Animator != null)
|
||||
{
|
||||
context.Animator.PlayFlip(
|
||||
cardBack: cardBackVisual != null ? cardBackVisual.transform : null,
|
||||
cardFront: context.CardDisplay != null ? context.CardDisplay.transform : null,
|
||||
onComplete: OnFlipComplete
|
||||
);
|
||||
|
||||
context.Animator.PlayFlipScalePunch();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPointerClick(PointerEventData eventData)
|
||||
{
|
||||
// Forward to same logic as routed click to keep behavior unified
|
||||
OnCardClicked(_context);
|
||||
}
|
||||
|
||||
private void OnFlipComplete()
|
||||
{
|
||||
// Query current collection state from CardSystemManager (don't use cached values)
|
||||
bool isNew = Data.CardSystem.CardSystemManager.Instance.IsCardNew(_context.CardData, out CardData existingCard);
|
||||
|
||||
// Transition based on whether this is a new card or repeat
|
||||
if (isNew)
|
||||
{
|
||||
// New card - show "NEW" badge and enlarge
|
||||
_context.StateMachine.ChangeState(CardStateNames.EnlargedNew);
|
||||
}
|
||||
else if (_context.CardData != null && _context.CardData.Rarity == AppleHills.Data.CardSystem.CardRarity.Legendary)
|
||||
{
|
||||
// Legendary repeat - skip enlarge, they can't upgrade
|
||||
// Add to inventory and move to revealed state
|
||||
if (Data.CardSystem.CardSystemManager.Instance != null)
|
||||
{
|
||||
Data.CardSystem.CardSystemManager.Instance.AddCardToInventoryDelayed(_context.CardData);
|
||||
}
|
||||
_context.StateMachine.ChangeState(CardStateNames.Revealed);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Repeat card - show progress toward upgrade
|
||||
_context.StateMachine.ChangeState(CardStateNames.EnlargedRepeat);
|
||||
}
|
||||
}
|
||||
|
||||
private void StopIdleHover()
|
||||
{
|
||||
if (_idleHoverTween != null)
|
||||
{
|
||||
_idleHoverTween.Stop();
|
||||
_idleHoverTween = null;
|
||||
|
||||
// Return to original position
|
||||
RectTransform rectTransform = _context.RootTransform.GetComponent<RectTransform>();
|
||||
if (rectTransform != null && _context.Animator != null)
|
||||
{
|
||||
_context.Animator.AnimateAnchoredPosition(_originalPosition, 0.3f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
// Stop idle hover animation when leaving state
|
||||
StopIdleHover();
|
||||
|
||||
// Reset scale
|
||||
if (_context?.Animator != null)
|
||||
{
|
||||
_context.Animator.AnimateScale(Vector3.one, 0.2f);
|
||||
}
|
||||
|
||||
// Hide card back when leaving state
|
||||
if (cardBackVisual != null)
|
||||
{
|
||||
cardBackVisual.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7da1bdc06be348f2979d3b92cc7ce723
|
||||
timeCreated: 1762884650
|
||||
@@ -0,0 +1,176 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6fab9d595905435b82253cd4d1bf49de
|
||||
timeCreated: 1763322180
|
||||
@@ -0,0 +1,150 @@
|
||||
using Core;
|
||||
using Core.SaveLoad;
|
||||
using Data.CardSystem;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UI.CardSystem.StateMachine.States
|
||||
{
|
||||
/// <summary>
|
||||
/// 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
|
||||
if (_context.CardDisplay != null)
|
||||
{
|
||||
_context.CardDisplay.gameObject.SetActive(true);
|
||||
_context.CardDisplay.transform.localRotation = Quaternion.Euler(0, 0, 0);
|
||||
}
|
||||
|
||||
// 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>
|
||||
/// 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)
|
||||
{
|
||||
_parentSlot = slot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the parent slot
|
||||
/// </summary>
|
||||
public AlbumCardSlot GetParentSlot()
|
||||
{
|
||||
return _parentSlot;
|
||||
}
|
||||
|
||||
public void OnCardClicked(CardContext context)
|
||||
{
|
||||
// Click to enlarge when in album
|
||||
Logging.Debug($"[CardPlacedInSlotState] Card clicked in slot, transitioning to enlarged state");
|
||||
context.StateMachine.ChangeState(CardStateNames.AlbumEnlarged);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 11a4dc9bbeed4623baf1675ab5679bd9
|
||||
timeCreated: 1762884899
|
||||
@@ -0,0 +1,76 @@
|
||||
using AppleHills.Data.CardSystem;
|
||||
using Core.SaveLoad;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UI.CardSystem.StateMachine.States
|
||||
{
|
||||
/// <summary>
|
||||
/// Revealed state - card is flipped and visible at normal size.
|
||||
/// This is the "waiting" state:
|
||||
/// - In booster flow: waiting for all cards to finish before animating to album
|
||||
/// - In album placement flow: waiting to be dragged to a slot
|
||||
/// Shows small idle badges for NEW or REPEAT cards.
|
||||
/// </summary>
|
||||
public class CardRevealedState : AppleState
|
||||
{
|
||||
[Header("State-Owned Visuals")]
|
||||
[SerializeField] private UnityEngine.GameObject newCardIdleBadge;
|
||||
[SerializeField] private UnityEngine.GameObject repeatCardIdleBadge;
|
||||
|
||||
private CardContext _context;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_context = GetComponentInParent<CardContext>();
|
||||
}
|
||||
|
||||
public override void OnEnterState()
|
||||
{
|
||||
// Ensure card front is visible and facing camera
|
||||
if (_context.CardDisplay != null)
|
||||
{
|
||||
_context.CardDisplay.gameObject.SetActive(true);
|
||||
_context.CardDisplay.transform.localRotation = Quaternion.Euler(0, 0, 0);
|
||||
}
|
||||
|
||||
// Show appropriate idle badge unless suppressed
|
||||
if (_context.BoosterContext.SuppressRevealBadges)
|
||||
{
|
||||
if (newCardIdleBadge != null) newCardIdleBadge.SetActive(false);
|
||||
if (repeatCardIdleBadge != null) repeatCardIdleBadge.SetActive(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool isNew = Data.CardSystem.CardSystemManager.Instance.IsCardNew(_context.CardData, out CardData existingCard);
|
||||
int currentOwnedCount = (existingCard != null) ? existingCard.CopiesOwned : 0;
|
||||
if (isNew)
|
||||
{
|
||||
if (newCardIdleBadge != null) newCardIdleBadge.SetActive(true);
|
||||
if (repeatCardIdleBadge != null) repeatCardIdleBadge.SetActive(false);
|
||||
}
|
||||
else if (currentOwnedCount > 0)
|
||||
{
|
||||
if (newCardIdleBadge != null) newCardIdleBadge.SetActive(false);
|
||||
if (repeatCardIdleBadge != null) repeatCardIdleBadge.SetActive(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (newCardIdleBadge != null) newCardIdleBadge.SetActive(false);
|
||||
if (repeatCardIdleBadge != null) repeatCardIdleBadge.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Fire reveal flow complete event (signals booster page that this card is done)
|
||||
_context.BoosterContext.NotifyRevealComplete();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
// Hide badges when leaving state
|
||||
if (newCardIdleBadge != null)
|
||||
newCardIdleBadge.SetActive(false);
|
||||
if (repeatCardIdleBadge != null)
|
||||
repeatCardIdleBadge.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 891aad90d6cc41869e497f94d1408859
|
||||
timeCreated: 1762884650
|
||||
Reference in New Issue
Block a user