Update assets, working save kerfuffle

This commit is contained in:
Michal Pikulski
2025-11-17 14:30:07 +01:00
parent ee07d89d3e
commit c6f635f871
30 changed files with 44358 additions and 462 deletions

View File

@@ -12,12 +12,12 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 2a80cc88c9884512b8b633110d838780, type: 3} m_Script: {fileID: 11500000, guid: 2a80cc88c9884512b8b633110d838780, type: 3}
m_Name: Card_KalkUlation 1 m_Name: Card_KalkUlation 1
m_EditorClassIdentifier: AppleHillsScripts::AppleHills.Data.CardSystem.CardDefinition m_EditorClassIdentifier: AppleHillsScripts::AppleHills.Data.CardSystem.CardDefinition
Id: 1006b95d-e3e1-4426-bc76-ab816e316b33 Id: 9f3fd9b8-3350-421e-b2ec-b4f019596506
Name: Kalk Ulation Name: Kalk Ulation
UseCustomFileName: 0 UseCustomFileName: 0
CustomFileName: CustomFileName:
Description: Card description Description: Card description
Rarity: 2 Rarity: 1
Zone: 4 Zone: 5
CardImage: {fileID: 5907816357319480503, guid: 84b744282e7e8084f935104f492f17b2, type: 3} CardImage: {fileID: 5907816357319480503, guid: 84b744282e7e8084f935104f492f17b2, type: 3}
CollectionIndex: 16 CollectionIndex: 15

View File

@@ -10,14 +10,14 @@ MonoBehaviour:
m_Enabled: 1 m_Enabled: 1
m_EditorHideFlags: 0 m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 2a80cc88c9884512b8b633110d838780, type: 3} m_Script: {fileID: 11500000, guid: 2a80cc88c9884512b8b633110d838780, type: 3}
m_Name: Card_KalkUlation 3 m_Name: Card_KalkUlation 2
m_EditorClassIdentifier: AppleHillsScripts::AppleHills.Data.CardSystem.CardDefinition m_EditorClassIdentifier: AppleHillsScripts::AppleHills.Data.CardSystem.CardDefinition
Id: 9f3fd9b8-3350-421e-b2ec-b4f019596506 Id: 1006b95d-e3e1-4426-bc76-ab816e316b33
Name: Kalk Ulation Name: Kalk Ulation
UseCustomFileName: 0 UseCustomFileName: 0
CustomFileName: CustomFileName:
Description: Card description Description: Card description
Rarity: 1 Rarity: 2
Zone: 4 Zone: 5
CardImage: {fileID: 5907816357319480503, guid: 84b744282e7e8084f935104f492f17b2, type: 3} CardImage: {fileID: 5907816357319480503, guid: 84b744282e7e8084f935104f492f17b2, type: 3}
CollectionIndex: 15 CollectionIndex: 16

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 82008856df7c51f47b1582de464ba44b guid: 4f4ec75013bc276429c2f4fa52d165e0
NativeFormatImporter: NativeFormatImporter:
externalObjects: {} externalObjects: {}
mainObjectFileID: 11400000 mainObjectFileID: 11400000

View File

@@ -18,6 +18,6 @@ MonoBehaviour:
CustomFileName: CustomFileName:
Description: Card description Description: Card description
Rarity: 0 Rarity: 0
Zone: 4 Zone: 5
CardImage: {fileID: 5907816357319480503, guid: 84b744282e7e8084f935104f492f17b2, type: 3} CardImage: {fileID: 5907816357319480503, guid: 84b744282e7e8084f935104f492f17b2, type: 3}
CollectionIndex: 14 CollectionIndex: 14

View File

@@ -12,12 +12,12 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 2a80cc88c9884512b8b633110d838780, type: 3} m_Script: {fileID: 11500000, guid: 2a80cc88c9884512b8b633110d838780, type: 3}
m_Name: Card_MormorMarmor 1 m_Name: Card_MormorMarmor 1
m_EditorClassIdentifier: AppleHillsScripts::AppleHills.Data.CardSystem.CardDefinition m_EditorClassIdentifier: AppleHillsScripts::AppleHills.Data.CardSystem.CardDefinition
Id: 798301d9-70a5-46d4-8b81-e375de0abfb3 Id: 9d7a1e8d-6c9f-4dc9-b013-cda836e7b413
Name: Mormor Marmor Name: Mormor Marmor
UseCustomFileName: 0 UseCustomFileName: 0
CustomFileName: CustomFileName:
Description: Card description Description: Card description
Rarity: 2 Rarity: 1
Zone: 5 Zone: 6
CardImage: {fileID: -1694013536, guid: c28c2d55edc2fbc4baf57d2672c0c3df, type: 3} CardImage: {fileID: -1694013536, guid: c28c2d55edc2fbc4baf57d2672c0c3df, type: 3}
CollectionIndex: 19 CollectionIndex: 18

View File

@@ -10,14 +10,14 @@ MonoBehaviour:
m_Enabled: 1 m_Enabled: 1
m_EditorHideFlags: 0 m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 2a80cc88c9884512b8b633110d838780, type: 3} m_Script: {fileID: 11500000, guid: 2a80cc88c9884512b8b633110d838780, type: 3}
m_Name: Card_MormorMarmor 3 m_Name: Card_MormorMarmor 2
m_EditorClassIdentifier: AppleHillsScripts::AppleHills.Data.CardSystem.CardDefinition m_EditorClassIdentifier: AppleHillsScripts::AppleHills.Data.CardSystem.CardDefinition
Id: 9d7a1e8d-6c9f-4dc9-b013-cda836e7b413 Id: 798301d9-70a5-46d4-8b81-e375de0abfb3
Name: Mormor Marmor Name: Mormor Marmor
UseCustomFileName: 0 UseCustomFileName: 0
CustomFileName: CustomFileName:
Description: Card description Description: Card description
Rarity: 1 Rarity: 2
Zone: 5 Zone: 6
CardImage: {fileID: -1694013536, guid: c28c2d55edc2fbc4baf57d2672c0c3df, type: 3} CardImage: {fileID: -1694013536, guid: c28c2d55edc2fbc4baf57d2672c0c3df, type: 3}
CollectionIndex: 18 CollectionIndex: 19

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 80e3766cc597fd94f895f5cd6aa2bcc6 guid: 53996921ed2094948aa317efe4ca6630
NativeFormatImporter: NativeFormatImporter:
externalObjects: {} externalObjects: {}
mainObjectFileID: 11400000 mainObjectFileID: 11400000

View File

@@ -18,6 +18,6 @@ MonoBehaviour:
CustomFileName: CustomFileName:
Description: Card description Description: Card description
Rarity: 0 Rarity: 0
Zone: 5 Zone: 6
CardImage: {fileID: -1694013536, guid: c28c2d55edc2fbc4baf57d2672c0c3df, type: 3} CardImage: {fileID: -1694013536, guid: c28c2d55edc2fbc4baf57d2672c0c3df, type: 3}
CollectionIndex: 17 CollectionIndex: 17

View File

@@ -20,8 +20,10 @@ namespace AppleHills.Editor
// Cache for display // Cache for display
private Dictionary<CardRarity, List<CardData>> cardsByRarity = new Dictionary<CardRarity, List<CardData>>(); private Dictionary<CardRarity, List<CardData>> cardsByRarity = new Dictionary<CardRarity, List<CardData>>();
private List<CardData> pendingCards = new List<CardData>();
private int totalCards = 0; private int totalCards = 0;
private int totalUniqueCards = 0; private int totalUniqueCards = 0;
private int totalPendingCards = 0;
private int boosterCount = 0; private int boosterCount = 0;
private string lastEventMessage = ""; private string lastEventMessage = "";
@@ -72,6 +74,7 @@ namespace AppleHills.Editor
CardSystemManager.Instance.OnBoosterOpened += OnBoosterOpened; CardSystemManager.Instance.OnBoosterOpened += OnBoosterOpened;
CardSystemManager.Instance.OnCardCollected += OnCardCollected; CardSystemManager.Instance.OnCardCollected += OnCardCollected;
CardSystemManager.Instance.OnBoosterCountChanged += OnBoosterCountChanged; CardSystemManager.Instance.OnBoosterCountChanged += OnBoosterCountChanged;
CardSystemManager.Instance.OnPendingCardAdded += OnPendingCardAdded;
isSubscribed = true; isSubscribed = true;
Debug.Log("[CardSystemLivePreview] Subscribed to CardSystemManager events"); Debug.Log("[CardSystemLivePreview] Subscribed to CardSystemManager events");
@@ -87,6 +90,7 @@ namespace AppleHills.Editor
CardSystemManager.Instance.OnBoosterOpened -= OnBoosterOpened; CardSystemManager.Instance.OnBoosterOpened -= OnBoosterOpened;
CardSystemManager.Instance.OnCardCollected -= OnCardCollected; CardSystemManager.Instance.OnCardCollected -= OnCardCollected;
CardSystemManager.Instance.OnBoosterCountChanged -= OnBoosterCountChanged; CardSystemManager.Instance.OnBoosterCountChanged -= OnBoosterCountChanged;
CardSystemManager.Instance.OnPendingCardAdded -= OnPendingCardAdded;
} }
isSubscribed = false; isSubscribed = false;
@@ -114,21 +118,33 @@ namespace AppleHills.Editor
Repaint(); Repaint();
} }
private void OnPendingCardAdded(CardData card)
{
lastEventMessage = $"Pending card added: {card.Name} ({card.Rarity})";
RefreshData();
Repaint();
}
private void RefreshData() private void RefreshData()
{ {
if (!Application.isPlaying || CardSystemManager.Instance == null) return; if (!Application.isPlaying || CardSystemManager.Instance == null) return;
var allCards = CardSystemManager.Instance.GetAllCollectedCards(); // Get ONLY collected cards (not pending) to avoid duplicates
var collectedCards = CardSystemManager.Instance.GetCollectionOnly();
// Group by rarity // Group by rarity
cardsByRarity.Clear(); cardsByRarity.Clear();
cardsByRarity[CardRarity.Normal] = allCards.Where(c => c.Rarity == CardRarity.Normal).ToList(); cardsByRarity[CardRarity.Normal] = collectedCards.Where(c => c.Rarity == CardRarity.Normal).ToList();
cardsByRarity[CardRarity.Rare] = allCards.Where(c => c.Rarity == CardRarity.Rare).ToList(); cardsByRarity[CardRarity.Rare] = collectedCards.Where(c => c.Rarity == CardRarity.Rare).ToList();
cardsByRarity[CardRarity.Legendary] = allCards.Where(c => c.Rarity == CardRarity.Legendary).ToList(); cardsByRarity[CardRarity.Legendary] = collectedCards.Where(c => c.Rarity == CardRarity.Legendary).ToList();
totalCards = allCards.Sum(c => c.CopiesOwned); totalCards = collectedCards.Sum(c => c.CopiesOwned);
totalUniqueCards = allCards.Count; totalUniqueCards = collectedCards.Count;
boosterCount = CardSystemManager.Instance.GetBoosterPackCount(); boosterCount = CardSystemManager.Instance.GetBoosterPackCount();
// Get pending cards separately
pendingCards = CardSystemManager.Instance.GetPendingRevealCards();
totalPendingCards = pendingCards.Count;
} }
private void Update() private void Update()
@@ -173,6 +189,7 @@ namespace AppleHills.Editor
EditorGUILayout.LabelField("Total Unique Cards:", totalUniqueCards.ToString()); EditorGUILayout.LabelField("Total Unique Cards:", totalUniqueCards.ToString());
EditorGUILayout.LabelField("Total Cards Owned:", totalCards.ToString()); EditorGUILayout.LabelField("Total Cards Owned:", totalCards.ToString());
EditorGUILayout.LabelField("Booster Packs:", boosterCount.ToString()); EditorGUILayout.LabelField("Booster Packs:", boosterCount.ToString());
EditorGUILayout.LabelField("Pending Reveal:", totalPendingCards.ToString(), EditorStyles.boldLabel);
if (!string.IsNullOrEmpty(lastEventMessage)) if (!string.IsNullOrEmpty(lastEventMessage))
{ {
@@ -187,6 +204,13 @@ namespace AppleHills.Editor
// Collection by Rarity // Collection by Rarity
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
// Pending Cards Section
if (totalPendingCards > 0)
{
DrawPendingCardsSection();
EditorGUILayout.Space();
}
DrawRaritySection(CardRarity.Legendary, Color.yellow); DrawRaritySection(CardRarity.Legendary, Color.yellow);
DrawRaritySection(CardRarity.Rare, new Color(0.5f, 0.5f, 1f)); // Light blue DrawRaritySection(CardRarity.Rare, new Color(0.5f, 0.5f, 1f)); // Light blue
DrawRaritySection(CardRarity.Normal, Color.white); DrawRaritySection(CardRarity.Normal, Color.white);
@@ -208,6 +232,52 @@ namespace AppleHills.Editor
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
} }
private void DrawPendingCardsSection()
{
var oldColor = GUI.backgroundColor;
GUI.backgroundColor = new Color(1f, 0.8f, 0.4f); // Orange tint for pending
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUI.backgroundColor = oldColor;
GUILayout.Label($"⏳ Pending Reveal ({pendingCards.Count} cards)", EditorStyles.boldLabel);
// Group pending by rarity for display
var pendingByRarity = pendingCards.GroupBy(c => c.Rarity).OrderByDescending(g => g.Key);
foreach (var rarityGroup in pendingByRarity)
{
EditorGUILayout.Space(5);
var rarityColor = GetRarityColor(rarityGroup.Key);
oldColor = GUI.color;
GUI.color = rarityColor;
GUILayout.Label($"{rarityGroup.Key} ({rarityGroup.Count()})", EditorStyles.miniBoldLabel);
GUI.color = oldColor;
foreach (var card in rarityGroup.OrderBy(c => c.Name))
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(" • " + card.Name, GUILayout.Width(200));
EditorGUILayout.LabelField(card.Zone.ToString(), GUILayout.Width(100));
EditorGUILayout.LabelField($"Copies: {card.CopiesOwned}", GUILayout.Width(80));
EditorGUILayout.EndHorizontal();
}
}
EditorGUILayout.EndVertical();
}
private Color GetRarityColor(CardRarity rarity)
{
switch (rarity)
{
case CardRarity.Legendary: return Color.yellow;
case CardRarity.Rare: return new Color(0.5f, 0.5f, 1f);
case CardRarity.Normal: return Color.white;
default: return Color.gray;
}
}
private void DrawRaritySection(CardRarity rarity, Color color) private void DrawRaritySection(CardRarity rarity, Color color)
{ {
if (!cardsByRarity.ContainsKey(rarity) || cardsByRarity[rarity].Count == 0) if (!cardsByRarity.ContainsKey(rarity) || cardsByRarity[rarity].Count == 0)

View File

@@ -1,6 +1,5 @@
using UnityEngine; using UnityEngine;
using System.Collections; using UnityEngine.Events;
using System;
namespace BookCurlPro namespace BookCurlPro
{ {
@@ -13,6 +12,10 @@ namespace BookCurlPro
public float DelayBeforeStart; public float DelayBeforeStart;
public float TimeBetweenPages = 5; public float TimeBetweenPages = 5;
public bool AutoStartFlip = true; public bool AutoStartFlip = true;
[Header("Events")]
public UnityEvent OnTargetReached;
bool flippingStarted = false; bool flippingStarted = false;
bool isPageFlipping = false; bool isPageFlipping = false;
float elapsedTime = 0; float elapsedTime = 0;
@@ -42,6 +45,24 @@ namespace BookCurlPro
PageFlipper.FlipPage(ControledBook, PageFlipTime, FlipMode.LeftToRight, () => { isPageFlipping = false; }); PageFlipper.FlipPage(ControledBook, PageFlipTime, FlipMode.LeftToRight, () => { isPageFlipping = false; });
} }
int targetPaper; int targetPaper;
/// <summary>
/// Start flipping to target page with optional completion callback
/// </summary>
public void StartFlipping(int target, UnityAction callback)
{
if (callback != null)
{
if (OnTargetReached == null)
OnTargetReached = new UnityEvent();
OnTargetReached.RemoveAllListeners();
OnTargetReached.AddListener(callback);
}
StartFlipping(target);
}
public void StartFlipping(int target) public void StartFlipping(int target)
{ {
isBookInteractable = ControledBook.interactable; isBookInteractable = ControledBook.interactable;
@@ -75,7 +96,9 @@ namespace BookCurlPro
flippingStarted = false; flippingStarted = false;
ControledBook.interactable = isBookInteractable; ControledBook.interactable = isBookInteractable;
this.enabled = false; this.enabled = false;
// Invoke target reached event
OnTargetReached?.Invoke();
} }
nextPageCountDown = PageFlipTime + TimeBetweenPages + Time.deltaTime; nextPageCountDown = PageFlipTime + TimeBetweenPages + Time.deltaTime;

View File

@@ -803,7 +803,7 @@ GameObject:
m_Component: m_Component:
- component: {fileID: 6260183383577703002} - component: {fileID: 6260183383577703002}
- component: {fileID: 5488000345852367841} - component: {fileID: 5488000345852367841}
- component: {fileID: 3191598984258052350} - component: {fileID: 1811552910330330231}
m_Layer: 0 m_Layer: 0
m_Name: CardStateMachine m_Name: CardStateMachine
m_TagString: Untagged m_TagString: Untagged
@@ -852,7 +852,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 55938fb1577dd4ad3af7e994048c86f6, type: 3} m_Script: {fileID: 11500000, guid: 55938fb1577dd4ad3af7e994048c86f6, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: PixelplacementAssembly::Pixelplacement.Initialization m_EditorClassIdentifier: PixelplacementAssembly::Pixelplacement.Initialization
--- !u!114 &3191598984258052350 --- !u!114 &1811552910330330231
MonoBehaviour: MonoBehaviour:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0} m_CorrespondingSourceObject: {fileID: 0}
@@ -861,10 +861,10 @@ MonoBehaviour:
m_GameObject: {fileID: 5611296005305622910} m_GameObject: {fileID: 5611296005305622910}
m_Enabled: 1 m_Enabled: 1
m_EditorHideFlags: 0 m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6f56763d30b94bf6873d395a6c116eb5, type: 3} m_Script: {fileID: 11500000, guid: 87ed5616041a4d878f452a8741e1eeab, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: AppleHillsScripts::Core.SaveLoad.AppleMachine m_EditorClassIdentifier: AppleHillsScripts::UI.CardSystem.StateMachine.CardStateMachine
defaultState: {fileID: 0} defaultState: {fileID: 8567459193246203383}
currentState: {fileID: 0} currentState: {fileID: 0}
_unityEventsFolded: 0 _unityEventsFolded: 0
verbose: 0 verbose: 0
@@ -948,7 +948,7 @@ GameObject:
- component: {fileID: 7618067314731501553} - component: {fileID: 7618067314731501553}
- component: {fileID: 4048607250111842459} - component: {fileID: 4048607250111842459}
m_Layer: 0 m_Layer: 0
m_Name: PlaceInSlotState m_Name: PlacedInSlotState
m_TagString: Untagged m_TagString: Untagged
m_Icon: {fileID: 0} m_Icon: {fileID: 0}
m_NavMeshLayer: 0 m_NavMeshLayer: 0
@@ -1071,7 +1071,7 @@ MonoBehaviour:
selectionOffset: 50 selectionOffset: 50
context: {fileID: 5882185627204126092} context: {fileID: 5882185627204126092}
animator: {fileID: 8871437021056565164} animator: {fileID: 8871437021056565164}
stateMachine: {fileID: 3191598984258052350} stateMachine: {fileID: 0}
initialState: IdleState initialState: IdleState
--- !u!1 &8567459193246203383 --- !u!1 &8567459193246203383
GameObject: GameObject:

View File

@@ -360,6 +360,10 @@ PrefabInstance:
propertyPath: m_SizeDelta.x propertyPath: m_SizeDelta.x
value: 0 value: 0
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 185814890104990467, guid: 88a05fdd940194543ade1cc2bcdada5f, type: 3}
propertyPath: zone
value: 3
objectReference: {fileID: 0}
- target: {fileID: 225698963612346310, guid: 88a05fdd940194543ade1cc2bcdada5f, type: 3} - target: {fileID: 225698963612346310, guid: 88a05fdd940194543ade1cc2bcdada5f, type: 3}
propertyPath: m_AnchorMax.x propertyPath: m_AnchorMax.x
value: 0 value: 0
@@ -468,6 +472,10 @@ PrefabInstance:
propertyPath: m_SizeDelta.y propertyPath: m_SizeDelta.y
value: 0 value: 0
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 994625896264652594, guid: 88a05fdd940194543ade1cc2bcdada5f, type: 3}
propertyPath: zone
value: 5
objectReference: {fileID: 0}
- target: {fileID: 1028249730971655938, guid: 88a05fdd940194543ade1cc2bcdada5f, type: 3} - target: {fileID: 1028249730971655938, guid: 88a05fdd940194543ade1cc2bcdada5f, type: 3}
propertyPath: m_SizeDelta.x propertyPath: m_SizeDelta.x
value: 0 value: 0
@@ -872,6 +880,10 @@ PrefabInstance:
propertyPath: m_SizeDelta.y propertyPath: m_SizeDelta.y
value: 0 value: 0
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 3049533675929530111, guid: 88a05fdd940194543ade1cc2bcdada5f, type: 3}
propertyPath: zone
value: 6
objectReference: {fileID: 0}
- target: {fileID: 3054687965411081415, guid: 88a05fdd940194543ade1cc2bcdada5f, type: 3} - target: {fileID: 3054687965411081415, guid: 88a05fdd940194543ade1cc2bcdada5f, type: 3}
propertyPath: m_SizeDelta.x propertyPath: m_SizeDelta.x
value: 0 value: 0
@@ -1324,6 +1336,10 @@ PrefabInstance:
propertyPath: m_SizeDelta.x propertyPath: m_SizeDelta.x
value: 0 value: 0
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 6429946768665127855, guid: 88a05fdd940194543ade1cc2bcdada5f, type: 3}
propertyPath: zone
value: 1
objectReference: {fileID: 0}
- target: {fileID: 6450935215454210476, guid: 88a05fdd940194543ade1cc2bcdada5f, type: 3} - target: {fileID: 6450935215454210476, guid: 88a05fdd940194543ade1cc2bcdada5f, type: 3}
propertyPath: m_SizeDelta.x propertyPath: m_SizeDelta.x
value: 0 value: 0
@@ -1452,6 +1468,10 @@ PrefabInstance:
propertyPath: m_AnchoredPosition.y propertyPath: m_AnchoredPosition.y
value: 0 value: 0
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 6982294778394446152, guid: 88a05fdd940194543ade1cc2bcdada5f, type: 3}
propertyPath: zone
value: 2
objectReference: {fileID: 0}
- target: {fileID: 6992159917976237618, guid: 88a05fdd940194543ade1cc2bcdada5f, type: 3} - target: {fileID: 6992159917976237618, guid: 88a05fdd940194543ade1cc2bcdada5f, type: 3}
propertyPath: m_SizeDelta.x propertyPath: m_SizeDelta.x
value: 0 value: 0
@@ -1708,6 +1728,10 @@ PrefabInstance:
propertyPath: m_SizeDelta.x propertyPath: m_SizeDelta.x
value: 0 value: 0
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 9183285670530916085, guid: 88a05fdd940194543ade1cc2bcdada5f, type: 3}
propertyPath: zone
value: 0
objectReference: {fileID: 0}
- target: {fileID: 9203784639608826734, guid: 88a05fdd940194543ade1cc2bcdada5f, type: 3} - target: {fileID: 9203784639608826734, guid: 88a05fdd940194543ade1cc2bcdada5f, type: 3}
propertyPath: m_SizeDelta.x propertyPath: m_SizeDelta.x
value: 0 value: 0

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -82,7 +82,7 @@ namespace Core.SaveLoad
private void Start() private void Start()
{ {
// Direct registration - SaveLoadManager guaranteed available (priority 25) // Direct registration - SaveLoadManager guaranteed available (priority 25)
if (SaveLoadManager.Instance != null) if (SaveLoadManager.Instance != null && ShouldParticipateInSave())
{ {
SaveLoadManager.Instance.RegisterParticipant(this); SaveLoadManager.Instance.RegisterParticipant(this);
} }
@@ -126,6 +126,15 @@ namespace Core.SaveLoad
// Match ManagedBehaviour convention: SceneName/GameObjectName/ComponentType // Match ManagedBehaviour convention: SceneName/GameObjectName/ComponentType
return $"{sceneName}/{gameObject.name}/AppleMachine"; return $"{sceneName}/{gameObject.name}/AppleMachine";
} }
/// <summary>
/// Returns true to participate in save/load system.
/// Override this in derived classes to opt out (return false).
/// </summary>
public virtual bool ShouldParticipateInSave()
{
return true;
}
private string GetSceneName() private string GetSceneName()
{ {

View File

@@ -29,6 +29,13 @@
/// Used to prevent double-restoration when inactive objects become active. /// Used to prevent double-restoration when inactive objects become active.
/// </summary> /// </summary>
bool HasBeenRestored { get; } bool HasBeenRestored { get; }
/// <summary>
/// Returns true if this participant wants to participate in save/load system.
/// Return false to opt out (participant will not be saved or restored).
/// Useful for transient objects like UI elements that don't need persistence.
/// </summary>
bool ShouldParticipateInSave();
} }
} }

View File

@@ -130,6 +130,13 @@ namespace Core.SaveLoad
participants[saveId] = participant; participants[saveId] = participant;
Logging.Debug($"[SaveLoadManager] Registered participant: {saveId}"); Logging.Debug($"[SaveLoadManager] Registered participant: {saveId}");
// Skip restoration for participants that opt out
if (!participant.ShouldParticipateInSave())
{
Logging.Debug($"[SaveLoadManager] Participant opted out of save/load: {saveId}");
return;
}
// If we have save data loaded and the participant hasn't been restored yet // If we have save data loaded and the participant hasn't been restored yet
if (IsSaveDataLoaded && currentSaveData != null && !participant.HasBeenRestored) if (IsSaveDataLoaded && currentSaveData != null && !participant.HasBeenRestored)
{ {
@@ -446,6 +453,13 @@ namespace Core.SaveLoad
{ {
string saveId = kvp.Key; string saveId = kvp.Key;
ISaveParticipant participant = kvp.Value; ISaveParticipant participant = kvp.Value;
// Skip participants that opt out of save system
if (!participant.ShouldParticipateInSave())
{
Logging.Debug($"[SaveLoadManager] Skipping participant (opted out): {saveId}");
continue;
}
try try
{ {
@@ -630,6 +644,13 @@ namespace Core.SaveLoad
{ {
string saveId = kvp.Key; string saveId = kvp.Key;
ISaveParticipant participant = kvp.Value; ISaveParticipant participant = kvp.Value;
// Skip participants that opt out of save system
if (!participant.ShouldParticipateInSave())
{
Logging.Debug($"[SaveLoadManager] Skipping participant (opted out): {saveId}");
continue;
}
try try
{ {

View File

@@ -325,6 +325,14 @@ namespace Data.CardSystem
return allCards; return allCards;
} }
/// <summary>
/// Returns only owned/collected cards (excludes pending reveal cards)
/// </summary>
public List<CardData> GetCollectionOnly()
{
return new List<CardData>(playerInventory.GetAllCards());
}
/// <summary> /// <summary>
/// Returns cards from a specific zone (both owned and pending) /// Returns cards from a specific zone (both owned and pending)
/// </summary> /// </summary>

View File

@@ -1,5 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using AppleHills.Data.CardSystem; using AppleHills.Data.CardSystem;
using Core; using Core;
using Data.CardSystem; using Data.CardSystem;
@@ -25,8 +24,7 @@ namespace UI.CardSystem
[Header("Zone Navigation")] [Header("Zone Navigation")]
[SerializeField] private Transform tabContainer; // Container holding all BookTabButton children [SerializeField] private Transform tabContainer; // Container holding all BookTabButton children
private BookTabButton[] _zoneTabs; // Discovered zone tab buttons
private BookTabButton[] zoneTabs; // Discovered zone tab buttons
[Header("Album Card Reveal")] [Header("Album Card Reveal")]
[SerializeField] private SlotContainer bottomRightSlots; [SerializeField] private SlotContainer bottomRightSlots;
@@ -42,10 +40,13 @@ namespace UI.CardSystem
[SerializeField] private BoosterOpeningPage boosterOpeningPage; [SerializeField] private BoosterOpeningPage boosterOpeningPage;
private Input.InputMode _previousInputMode; private Input.InputMode _previousInputMode;
private List<StateMachine.Card> _activeCards = new List<StateMachine.Card>();
private const int MAX_VISIBLE_CARDS = 3;
private List<StateMachine.Card> _pendingCornerCards = new List<StateMachine.Card>(); private List<StateMachine.Card> _pendingCornerCards = new List<StateMachine.Card>();
private const int MAX_PENDING_CORNER = 3; private const int MaxPendingCorner = 3;
// Pending card placement coordination
private StateMachine.Card _pendingPlacementCard;
private bool _waitingForPageFlip;
private bool _cardDragReleased;
internal override void OnManagedStart() internal override void OnManagedStart()
{ {
@@ -108,22 +109,22 @@ namespace UI.CardSystem
if (tabContainer == null) if (tabContainer == null)
{ {
Debug.LogError("[AlbumViewPage] Tab container is not assigned! Cannot discover zone tabs."); Debug.LogError("[AlbumViewPage] Tab container is not assigned! Cannot discover zone tabs.");
zoneTabs = new BookTabButton[0]; _zoneTabs = new BookTabButton[0];
return; return;
} }
// Get all BookTabButton components from children // Get all BookTabButton components from children
zoneTabs = tabContainer.GetComponentsInChildren<BookTabButton>(includeInactive: false); _zoneTabs = tabContainer.GetComponentsInChildren<BookTabButton>(includeInactive: false);
if (zoneTabs == null || zoneTabs.Length == 0) if (_zoneTabs == null || _zoneTabs.Length == 0)
{ {
Logging.Warning($"[AlbumViewPage] No BookTabButton components found in tab container '{tabContainer.name}'!"); Logging.Warning($"[AlbumViewPage] No BookTabButton components found in tab container '{tabContainer.name}'!");
zoneTabs = new BookTabButton[0]; _zoneTabs = new BookTabButton[0];
} }
else else
{ {
Logging.Debug($"[AlbumViewPage] Discovered {zoneTabs.Length} zone tabs from container '{tabContainer.name}'"); Logging.Debug($"[AlbumViewPage] Discovered {_zoneTabs.Length} zone tabs from container '{tabContainer.name}'");
foreach (var tab in zoneTabs) foreach (var tab in _zoneTabs)
{ {
Logging.Debug($" - Tab: {tab.name}, Zone: {tab.Zone}, TargetPage: {tab.TargetPage}"); Logging.Debug($" - Tab: {tab.name}, Zone: {tab.Zone}, TargetPage: {tab.TargetPage}");
} }
@@ -159,7 +160,6 @@ namespace UI.CardSystem
if (CardSystemManager.Instance != null) if (CardSystemManager.Instance != null)
{ {
CardSystemManager.Instance.OnBoosterCountChanged -= OnBoosterCountChanged; CardSystemManager.Instance.OnBoosterCountChanged -= OnBoosterCountChanged;
// NOTE: OnPendingCardAdded is unsubscribed in TransitionOut
} }
// Clean up exit button // Clean up exit button
@@ -253,7 +253,6 @@ namespace UI.CardSystem
public override void TransitionIn() public override void TransitionIn()
{ {
// Only store and switch input mode if this is the first time entering // Only store and switch input mode if this is the first time entering
// (when _previousInputMode hasn't been set yet)
if (Input.InputManager.Instance != null) if (Input.InputManager.Instance != null)
{ {
// Store the current input mode before switching // Store the current input mode before switching
@@ -262,12 +261,6 @@ namespace UI.CardSystem
Logging.Debug("[AlbumViewPage] Switched to UI-only input mode on first entry"); Logging.Debug("[AlbumViewPage] Switched to UI-only input mode on first entry");
} }
// Subscribe to pending card events while page is active
if (CardSystemManager.Instance != null)
{
CardSystemManager.Instance.OnPendingCardAdded += OnPendingCardAdded;
}
// Only spawn pending cards if we're already on an album page (not the menu) // Only spawn pending cards if we're already on an album page (not the menu)
if (IsInAlbumProper()) if (IsInAlbumProper())
{ {
@@ -284,12 +277,6 @@ namespace UI.CardSystem
public override void TransitionOut() public override void TransitionOut()
{ {
// Unsubscribe from pending card events when page closes
if (CardSystemManager.Instance != null)
{
CardSystemManager.Instance.OnPendingCardAdded -= OnPendingCardAdded;
}
// Clean up active pending cards to prevent duplicates on next opening // Clean up active pending cards to prevent duplicates on next opening
CleanupPendingCornerCards(); CleanupPendingCornerCards();
@@ -402,165 +389,6 @@ namespace UI.CardSystem
} }
} }
#region Album Card Reveal System
/// <summary>
/// Spawn pending cards from CardSystemManager
/// Only spawns unique cards (one per definition+rarity, not one per copy)
/// </summary>
private void SpawnPendingCards()
{
if (CardSystemManager.Instance == null || bottomRightSlots == null || cardPrefab == null)
return;
var pending = CardSystemManager.Instance.GetPendingRevealCards();
var uniquePending = pending
.Where(c => c.CopiesOwned > 0)
.GroupBy(c => new { c.DefinitionId, c.Rarity })
.Select(g => g.First())
.ToList();
int spawnCount = Mathf.Min(uniquePending.Count, MAX_VISIBLE_CARDS);
Logging.Debug($"[AlbumViewPage] Spawning {spawnCount} unique pending cards (total pending: {pending.Count})");
for (int i = 0; i < spawnCount; i++)
{
SpawnCardInSlot(i, uniquePending[i]);
}
}
/// <summary>
/// Spawn a card in a specific slot using the new Card prefab
/// </summary>
private void SpawnCardInSlot(int slotIndex, CardData cardData)
{
if (cardData.CopiesOwned <= 0)
{
Logging.Warning($"[AlbumViewPage] Skipping spawn of card '{cardData.Name}' with {cardData.CopiesOwned} copies");
return;
}
DraggableSlot slot = FindSlotByIndex(slotIndex);
if (slot == null)
{
Logging.Warning($"[AlbumViewPage] Could not find slot with SlotIndex {slotIndex}");
return;
}
GameObject cardObj = Instantiate(cardPrefab, bottomRightSlots.transform);
var card = cardObj.GetComponent<StateMachine.Card>();
if (card != null)
{
// Cards spawned here are already revealed and can be dragged into album
card.SetupForAlbumPlacement(cardData);
// Assign to slot with animation (will apply size/position)
card.AssignToSlot(slot, true);
// Track placement completion to clean up
card.OnPlacedInAlbumSlot += OnCardPlacedInAlbum;
_activeCards.Add(card);
Logging.Debug($"[AlbumViewPage] Spawned pending card '{cardData.Name}' in slot {slotIndex}");
}
else
{
Logging.Warning($"[AlbumViewPage] Spawned card prefab missing Card component!");
Destroy(cardObj);
}
}
/// <summary>
/// Handle when a card is placed in an album slot from the pending list
/// Moves from pending to inventory, shuffles remaining, and spawns the next unique card.
/// </summary>
private void OnCardPlacedInAlbum(StateMachine.Card card, AlbumCardSlot slot)
{
if (card == null) return;
var data = card.CardData;
Logging.Debug($"[AlbumViewPage] Card placed in album slot: {data?.Name}");
// Move card from pending to inventory now
if (data != null && CardSystemManager.Instance != null)
{
CardSystemManager.Instance.MarkCardAsPlaced(data);
}
// Stop tracking and unsubscribe
card.OnPlacedInAlbumSlot -= OnCardPlacedInAlbum;
_activeCards.Remove(card);
// Shuffle remaining cards to front and spawn next
ShuffleCardsToFront();
TrySpawnNextCard();
}
/// <summary>
/// Shuffle active cards to occupy front slots
/// </summary>
private void ShuffleCardsToFront()
{
if (bottomRightSlots == null || _activeCards.Count == 0)
return;
List<DraggableObject> draggableList = _activeCards.Cast<DraggableObject>().ToList();
SlotContainerHelper.ShuffleToFront(bottomRightSlots, draggableList, animate: true);
}
/// <summary>
/// Try to spawn the next pending unique card
/// </summary>
private void TrySpawnNextCard()
{
if (CardSystemManager.Instance == null)
return;
if (_activeCards.Count >= MAX_VISIBLE_CARDS)
return;
var pending = CardSystemManager.Instance.GetPendingRevealCards();
var uniquePending = pending
.Where(c => c.CopiesOwned > 0)
.GroupBy(c => new { c.DefinitionId, c.Rarity })
.Select(g => g.First())
.ToList();
foreach (var cardData in uniquePending)
{
bool alreadySpawned = _activeCards.Any(c =>
c.CardData.DefinitionId == cardData.DefinitionId &&
c.CardData.Rarity == cardData.Rarity);
if (!alreadySpawned)
{
int nextSlotIndex = _activeCards.Count;
SpawnCardInSlot(nextSlotIndex, cardData);
break;
}
}
}
/// <summary>
/// Clean up all active pending cards
/// </summary>
private void CleanupActiveCards()
{
foreach (var card in _activeCards)
{
if (card != null && card.gameObject != null)
{
card.OnPlacedInAlbumSlot -= OnCardPlacedInAlbum;
Destroy(card.gameObject);
}
}
_activeCards.Clear();
}
#endregion
#region Card Enlarge System (Album Slots) #region Card Enlarge System (Album Slots)
/// <summary> /// <summary>
@@ -633,37 +461,7 @@ namespace UI.CardSystem
#endregion #endregion
/// <summary> #region New Pending Corner Card System
/// Handle when a new card is added to pending queue
/// Only spawn if this unique card isn't already visualized
/// </summary>
private void OnPendingCardAdded(CardData card)
{
// Guard: Don't spawn cards with zero copies
if (card.CopiesOwned <= 0)
{
Logging.Warning($"[AlbumViewPage] Ignoring pending card '{card.Name}' with {card.CopiesOwned} copies");
return;
}
// Check if we already have a card with this definition + rarity spawned
bool alreadySpawned = _activeCards.Any(c =>
c.CardData.DefinitionId == card.DefinitionId &&
c.CardData.Rarity == card.Rarity);
if (alreadySpawned)
{
Logging.Debug($"[AlbumViewPage] Card '{card.Name}' already spawned, skipping duplicate spawn");
return; // Don't spawn duplicates
}
// Try to spawn if we have space
if (_activeCards.Count < MAX_VISIBLE_CARDS)
{
int nextSlotIndex = _activeCards.Count;
SpawnCardInSlot(nextSlotIndex, card);
}
}
/// <summary> /// <summary>
/// Find a slot by its SlotIndex property /// Find a slot by its SlotIndex property
@@ -687,17 +485,36 @@ namespace UI.CardSystem
{ {
if (cardPrefab == null || bottomRightSlots == null) return; if (cardPrefab == null || bottomRightSlots == null) return;
CleanupPendingCornerCards(); CleanupPendingCornerCards();
for (int i = 0; i < MAX_PENDING_CORNER; i++)
// Get unique pending cards
var uniquePending = GetUniquePendingCards();
if (uniquePending.Count == 0)
{
Logging.Debug("[AlbumViewPage] No pending cards to spawn");
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)");
for (int i = 0; i < spawnCount; i++)
{ {
var slot = FindSlotByIndex(i); var slot = FindSlotByIndex(i);
if (slot == null) break; if (slot == null) break;
GameObject cardObj = Instantiate(cardPrefab, bottomRightSlots.transform); GameObject cardObj = Instantiate(cardPrefab, bottomRightSlots.transform);
var card = cardObj.GetComponent<StateMachine.Card>(); var card = cardObj.GetComponent<StateMachine.Card>();
if (card != null) if (card != null)
{ {
card.SetupForAlbumPending(); card.SetupForAlbumPending();
card.AssignToSlot(slot, true); card.AssignToSlot(slot, true);
// Subscribe to both drag events
card.Context.OnDragStarted += OnCardDragStarted; card.Context.OnDragStarted += OnCardDragStarted;
card.Context.OnDragEnded += OnCardDragEnded;
_pendingCornerCards.Add(card); _pendingCornerCards.Add(card);
} }
else else
@@ -707,6 +524,40 @@ namespace UI.CardSystem
} }
} }
/// <summary>
/// Get unique pending cards (one per definition+rarity combo)
/// </summary>
private List<CardData> GetUniquePendingCards()
{
if (CardSystemManager.Instance == null) return new List<CardData>();
var pending = CardSystemManager.Instance.GetPendingRevealCards();
// Group by definition+rarity, take first of each group
var uniqueDict = new Dictionary<string, CardData>();
int duplicateCount = 0;
foreach (var card in pending)
{
string key = $"{card.DefinitionId}_{card.Rarity}";
if (!uniqueDict.ContainsKey(key))
{
uniqueDict[key] = card;
}
else
{
duplicateCount++;
}
}
if (duplicateCount > 0)
{
Logging.Warning($"[AlbumViewPage] Found {duplicateCount} duplicate pending cards (same definition+rarity combo)");
}
return new List<CardData>(uniqueDict.Values);
}
private void CleanupPendingCornerCards() private void CleanupPendingCornerCards()
{ {
foreach (var c in _pendingCornerCards) foreach (var c in _pendingCornerCards)
@@ -716,6 +567,7 @@ namespace UI.CardSystem
if (c.Context != null) if (c.Context != null)
{ {
c.Context.OnDragStarted -= OnCardDragStarted; c.Context.OnDragStarted -= OnCardDragStarted;
c.Context.OnDragEnded -= OnCardDragEnded;
} }
Destroy(c.gameObject); Destroy(c.gameObject);
} }
@@ -732,24 +584,202 @@ namespace UI.CardSystem
if (card == null) return; if (card == null) return;
string stateName = card.GetCurrentStateName(); string stateName = card.GetCurrentStateName();
Logging.Debug($"[AlbumViewPage] OnCardDragStarted - Card state: {stateName}");
if (stateName != "PendingFaceDownState") return; if (stateName != "PendingFaceDownState") return;
// Select smart pending card data // Select smart pending card data
var selected = SelectSmartPendingCard(); var selected = SelectSmartPendingCard();
if (selected == null) if (selected == null)
{ {
Logging.Warning("[AlbumViewPage] No pending card data available!");
return; // no pending data return; // no pending data
} }
Logging.Debug($"[AlbumViewPage] Selected card: {selected.Name} ({selected.DefinitionId}), Zone: {selected.Zone}");
context.SetupCard(selected); context.SetupCard(selected);
// Navigate album to page // Find target page based on card's zone using BookTabButtons
int targetPage = FindPageForCard(selected); int targetPage = FindPageForZone(selected.Zone);
Logging.Debug($"[AlbumViewPage] Target page for zone {selected.Zone}: {targetPage}");
if (targetPage >= 0) if (targetPage >= 0)
{ {
NavigateToAlbumPage(targetPage); // 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)
{
Logging.Warning("[AlbumViewPage] TryPlaceCard - No pending card or card data");
ResetPlacementState();
return;
}
// 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);
}
}
/// <summary>
/// Find the AlbumCardSlot that accepts this card based on DefinitionId
/// </summary>
private AlbumCardSlot FindTargetSlotForCard(CardData cardData)
{
if (cardData == null) return null;
var allSlots = FindObjectsByType<AlbumCardSlot>(FindObjectsSortMode.None);
foreach (var slot in allSlots)
{
if (slot.TargetCardDefinition != null &&
slot.TargetCardDefinition.Id == cardData.DefinitionId)
{
Logging.Debug($"[AlbumViewPage] Found target slot for {cardData.DefinitionId}, slot: {slot}");
return slot;
}
} }
// State will handle transition to FlippingPendingState return null;
}
private void PlaceCardInSlot(StateMachine.Card card, AlbumCardSlot slot)
{
if (card == null || slot == null) return;
Logging.Debug($"[AlbumViewPage] Placing card '{card.CardData?.Name}' in slot - starting tween");
// Reparent to slot immediately, keeping world position (card stays visible where it is)
card.transform.SetParent(slot.transform, true);
// Tween local position and scale simultaneously
float tweenDuration = 0.4f;
// Tween position to center of slot
Tween.LocalPosition(card.transform, Vector3.zero, tweenDuration, 0f, Tween.EaseOutBack);
// 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: () =>
{
// 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();
});
}
private void ResetPlacementState()
{
_pendingPlacementCard = null;
_waitingForPageFlip = false;
_cardDragReleased = false;
} }
private CardData SelectSmartPendingCard() private CardData SelectSmartPendingCard()
@@ -766,6 +796,29 @@ namespace UI.CardSystem
return pending[idx]; return pending[idx];
} }
/// <summary>
/// Find the target page for a card zone using BookTabButtons
/// </summary>
private int FindPageForZone(CardZone zone)
{
if (_zoneTabs == null || _zoneTabs.Length == 0)
{
Logging.Warning("[AlbumViewPage] No zone tabs discovered!");
return -1;
}
foreach (var tab in _zoneTabs)
{
if (tab.Zone == zone)
{
return tab.TargetPage;
}
}
Logging.Warning($"[AlbumViewPage] No BookTabButton found for zone {zone}");
return -1;
}
private List<string> GetDefinitionsOnCurrentPage() private List<string> GetDefinitionsOnCurrentPage()
{ {
var result = new List<string>(); var result = new List<string>();
@@ -811,32 +864,7 @@ namespace UI.CardSystem
return false; return false;
} }
private int FindPageForCard(CardData data) private void NavigateToAlbumPage(int pageIndex, UnityEngine.Events.UnityAction onComplete = null)
{
if (data == null || book == null || book.papers == null) return -1;
// Find all AlbumCardSlot in scene
var allSlots = FindObjectsByType<AlbumCardSlot>(FindObjectsSortMode.None);
foreach (var slot in allSlots)
{
if (slot.TargetCardDefinition != null && slot.TargetCardDefinition.Id == data.DefinitionId)
{
// Found matching slot, now find which page it's on
for (int i = 0; i < book.papers.Length; i++)
{
if (IsSlotOnPage(slot.transform, i))
{
return i;
}
}
}
}
return -1;
}
private void NavigateToAlbumPage(int pageIndex)
{ {
if (book == null || pageIndex < 0) return; if (book == null || pageIndex < 0) return;
@@ -847,9 +875,18 @@ namespace UI.CardSystem
autoFlip = book.gameObject.AddComponent<BookCurlPro.AutoFlip>(); autoFlip = book.gameObject.AddComponent<BookCurlPro.AutoFlip>();
} }
// Start flipping to target page // Start flipping to target page with callback
autoFlip.enabled = true; autoFlip.enabled = true;
autoFlip.StartFlipping(pageIndex); if (onComplete != null)
{
autoFlip.StartFlipping(pageIndex, onComplete);
}
else
{
autoFlip.StartFlipping(pageIndex);
}
} }
#endregion
} }
} }

View File

@@ -8,8 +8,7 @@ namespace UI.CardSystem
{ {
/// <summary> /// <summary>
/// Specialized slot for album pages that only accepts a specific card. /// Specialized slot for album pages that only accepts a specific card.
/// Validates cards based on their CardDefinition. /// Empty by default, auto-spawns owned cards on enable.
/// Self-populates with owned cards when enabled.
/// </summary> /// </summary>
public class AlbumCardSlot : DraggableSlot public class AlbumCardSlot : DraggableSlot
{ {
@@ -17,17 +16,57 @@ namespace UI.CardSystem
[SerializeField] private CardDefinition targetCardDefinition; // Which card this slot accepts [SerializeField] private CardDefinition targetCardDefinition; // Which card this slot accepts
[SerializeField] private GameObject cardPrefab; // Card prefab to spawn when card is owned [SerializeField] private GameObject cardPrefab; // Card prefab to spawn when card is owned
private StateMachine.Card _placedCard; private StateMachine.Card _assignedCard; // The card currently in this slot (if any)
/// <summary>
/// Get the target card definition for this slot
/// </summary>
public CardDefinition TargetCardDefinition => targetCardDefinition;
/// <summary>
/// Check if this slot has a card assigned to it
/// </summary>
public bool HasCardAssigned => _assignedCard != null;
/// <summary>
/// Get the card currently assigned to this slot
/// </summary>
public StateMachine.Card AssignedCard => _assignedCard;
private void OnEnable() private void OnEnable()
{ {
// Check if we should spawn a card for this slot // Check if we should spawn a card for this slot
CheckAndSpawnOwnedCard(); CheckAndSpawnOwnedCard();
} }
/// <summary>
/// Assign a card to this slot (called by AlbumViewPage after placement animation)
/// </summary>
public void AssignCard(StateMachine.Card card)
{
if (card == null)
{
Logging.Warning("[AlbumCardSlot] Attempted to assign null card to slot");
return;
}
if (_assignedCard != null && _assignedCard != card)
{
Logging.Warning($"[AlbumCardSlot] Slot already has a card assigned, replacing with new card");
// Clean up old card
if (_assignedCard.gameObject != null)
{
Destroy(_assignedCard.gameObject);
}
}
_assignedCard = card;
Logging.Debug($"[AlbumCardSlot] Card '{card.CardData?.Name}' assigned to slot for {targetCardDefinition?.name}");
}
/// <summary> /// <summary>
/// Check if player owns the card for this slot and spawn it if so /// Check if player owns the card for this slot and spawn it if so
/// (Called on OnEnable to handle game reload scenarios)
/// </summary> /// </summary>
private void CheckAndSpawnOwnedCard() private void CheckAndSpawnOwnedCard()
{ {
@@ -35,9 +74,12 @@ namespace UI.CardSystem
if (CardSystemManager.Instance == null || targetCardDefinition == null) if (CardSystemManager.Instance == null || targetCardDefinition == null)
return; return;
// Guard: don't spawn if already occupied // Guard: don't spawn if already has a card assigned
if (_placedCard != null) if (_assignedCard != null)
{
Logging.Debug($"[AlbumCardSlot] Slot for {targetCardDefinition.name} already has card assigned, skipping spawn");
return; return;
}
// Guard: need prefab to spawn // Guard: need prefab to spawn
if (cardPrefab == null) if (cardPrefab == null)
@@ -46,31 +88,41 @@ namespace UI.CardSystem
return; return;
} }
// Check if player owns this card at ANY rarity (prioritize highest rarity) // Check if player owns this card in COLLECTION (not pending)
CardData ownedCard = null; CardData ownedCard = FindOwnedCardForSlot();
// Check in order: Legendary > Rare > Normal // Only spawn if owned (not pending)
foreach (CardRarity rarity in new[] { CardRarity.Legendary, CardRarity.Rare, CardRarity.Normal })
{
CardData card = CardSystemManager.Instance.GetCardInventory().GetCard(targetCardDefinition.Id, rarity);
if (card != null)
{
ownedCard = card;
break; // Found highest rarity owned
}
}
// Spawn card if owned
if (ownedCard != null) if (ownedCard != null)
{ {
SpawnCard(ownedCard); Logging.Debug($"[AlbumCardSlot] Found owned card for {targetCardDefinition.name}, spawning");
SpawnOwnedCard(ownedCard);
} }
} }
/// <summary> /// <summary>
/// Spawn a Card in this slot using the PlacedInSlotState /// Find owned card for this slot (checks collection only, not pending)
/// </summary> /// </summary>
private void SpawnCard(CardData cardData) private CardData FindOwnedCardForSlot()
{
var inventory = CardSystemManager.Instance.GetCardInventory();
// Check in order: Legendary > Rare > Normal (prioritize highest rarity)
foreach (CardRarity rarity in new[] { CardRarity.Legendary, CardRarity.Rare, CardRarity.Normal })
{
CardData card = inventory.GetCard(targetCardDefinition.Id, rarity);
if (card != null)
{
return card; // Found highest rarity owned
}
}
return null; // Not owned
}
/// <summary>
/// Spawn a card that the player already owns (for reload scenarios)
/// </summary>
private void SpawnOwnedCard(CardData cardData)
{ {
GameObject cardObj = Instantiate(cardPrefab, transform); GameObject cardObj = Instantiate(cardPrefab, transform);
var card = cardObj.GetComponent<StateMachine.Card>(); var card = cardObj.GetComponent<StateMachine.Card>();
@@ -79,22 +131,21 @@ namespace UI.CardSystem
{ {
// Setup card for album slot (starts in PlacedInSlotState) // Setup card for album slot (starts in PlacedInSlotState)
card.SetupForAlbumSlot(cardData, this); card.SetupForAlbumSlot(cardData, this);
_placedCard = card;
// Resize the card to match the slot size // Resize the card to match the slot size
RectTransform cardRect = card.transform as RectTransform; RectTransform cardRect = card.transform as RectTransform;
RectTransform slotRect = transform as RectTransform; RectTransform slotRect = transform as RectTransform;
if (cardRect != null && slotRect != null) if (cardRect != null && slotRect != null)
{ {
// Set height to match slot height (AspectRatioFitter will handle width)
float targetHeight = slotRect.rect.height; float targetHeight = slotRect.rect.height;
cardRect.sizeDelta = new Vector2(cardRect.sizeDelta.x, targetHeight); cardRect.sizeDelta = new Vector2(cardRect.sizeDelta.x, targetHeight);
// Ensure position and rotation are centered
cardRect.localPosition = Vector3.zero; cardRect.localPosition = Vector3.zero;
cardRect.localRotation = Quaternion.identity; cardRect.localRotation = Quaternion.identity;
} }
// Assign card to this slot
_assignedCard = card;
// Register with AlbumViewPage for enlarge/shrink handling // Register with AlbumViewPage for enlarge/shrink handling
AlbumViewPage albumPage = FindFirstObjectByType<AlbumViewPage>(); AlbumViewPage albumPage = FindFirstObjectByType<AlbumViewPage>();
if (albumPage != null) if (albumPage != null)
@@ -110,16 +161,5 @@ namespace UI.CardSystem
Destroy(cardObj); Destroy(cardObj);
} }
} }
/// <summary>
/// Get the target card definition for this slot
/// </summary>
public CardDefinition TargetCardDefinition => targetCardDefinition;
/// <summary>
/// Get the target card definition for this slot (method version for compatibility)
/// </summary>
public CardDefinition GetTargetCardDefinition() => targetCardDefinition;
} }
} }

View File

@@ -6,12 +6,7 @@ using UnityEngine;
namespace UI.CardSystem.StateMachine namespace UI.CardSystem.StateMachine
{ {
/// <summary> // ...existing code...
/// Main Card controller component.
/// Orchestrates the card state machine, context, and animator.
/// Inherits from DraggableObject to provide drag/drop capabilities for album placement.
/// This is the single entry point for working with cards.
/// </summary>
public class Card : DraggableObject public class Card : DraggableObject
{ {
[Header("Components")] [Header("Components")]
@@ -57,34 +52,45 @@ namespace UI.CardSystem.StateMachine
{ {
base.OnDragStartedHook(); base.OnDragStartedHook();
// Always emit the generic drag started event - consumers can decide what to do // Always emit the generic drag started event for external consumers (AlbumViewPage, etc.)
context?.NotifyDragStarted(); context?.NotifyDragStarted();
string current = GetCurrentStateName(); // Check if current state wants to handle drag behavior
if (stateMachine?.currentState != null)
if (current == "PendingFaceDownState")
{ {
// Let the state handle the flip transition var dragHandler = stateMachine.currentState.GetComponent<ICardStateDragHandler>();
var pendingState = GetStateComponent<States.CardPendingFaceDownState>("PendingFaceDownState"); if (dragHandler != null && dragHandler.OnCardDragStarted(context))
if (pendingState != null)
{ {
pendingState.OnDragStarted(); return; // State handled it, don't do default behavior
} }
} }
else
{ // Default behavior: transition to DraggingState
Logging.Debug($"[Card] Drag started on {CardData?.Name}, transitioning to DraggingState"); Logging.Debug($"[Card] Drag started on {CardData?.Name}, transitioning to DraggingState");
ChangeState("DraggingState"); ChangeState("DraggingState");
}
} }
protected override void OnDragEndedHook() protected override void OnDragEndedHook()
{ {
base.OnDragEndedHook(); base.OnDragEndedHook();
// Always emit the generic drag ended event for external consumers
context?.NotifyDragEnded();
// Check if current state wants to handle drag end
if (stateMachine?.currentState != null)
{
var dragHandler = stateMachine.currentState.GetComponent<ICardStateDragHandler>();
if (dragHandler != null && dragHandler.OnCardDragEnded(context))
{
return; // State handled it
}
}
// Default behavior for states that don't implement custom drag end
string current = GetCurrentStateName(); string current = GetCurrentStateName();
if (current == "DraggingState") if (current == "DraggingState")
{ {
// Existing logic
if (CurrentSlot is AlbumCardSlot albumSlot) if (CurrentSlot is AlbumCardSlot albumSlot)
{ {
Logging.Debug($"[Card] Dropped in album slot, transitioning to PlacedInSlotState"); Logging.Debug($"[Card] Dropped in album slot, transitioning to PlacedInSlotState");
@@ -102,22 +108,6 @@ namespace UI.CardSystem.StateMachine
ChangeState("RevealedState"); ChangeState("RevealedState");
} }
} }
else if (current == "DraggingRevealedState")
{
// Pending revealed drag state end
if (CurrentSlot is AlbumCardSlot albumSlot)
{
var placedState = GetStateComponent<States.CardPlacedInSlotState>("PlacedInSlotState");
if (placedState != null) placedState.SetParentSlot(albumSlot);
ChangeState("PlacedInSlotState");
OnPlacedInAlbumSlot?.Invoke(this, albumSlot);
}
else
{
// Return to corner face-down
ChangeState("PendingFaceDownState");
}
}
} }
#endregion #endregion
@@ -151,16 +141,6 @@ namespace UI.CardSystem.StateMachine
SetDraggingEnabled(false); // Booster cards cannot be dragged SetDraggingEnabled(false); // Booster cards cannot be dragged
} }
/// <summary>
/// Setup for album placement flow (starts at RevealedState, can be dragged)
/// Dragging is ENABLED for album placement cards
/// </summary>
public void SetupForAlbumPlacement(CardData data)
{
SetupCard(data, "RevealedState");
SetDraggingEnabled(true); // Album placement cards can be dragged
}
/// <summary> /// <summary>
/// Setup for album placement (starts at PlacedInSlotState) /// Setup for album placement (starts at PlacedInSlotState)
/// Dragging is DISABLED once placed in slot /// Dragging is DISABLED once placed in slot

View File

@@ -41,6 +41,9 @@ namespace UI.CardSystem.StateMachine
// Generic drag event - fired when drag starts, consumers decide how to handle based on current state // Generic drag event - fired when drag starts, consumers decide how to handle based on current state
public event Action<CardContext> OnDragStarted; public event Action<CardContext> OnDragStarted;
// Generic drag end event - fired when drag ends, consumers decide how to handle based on current state
public event Action<CardContext> OnDragEnded;
private bool _hasCompletedReveal = false; private bool _hasCompletedReveal = false;
public bool HasCompletedReveal => _hasCompletedReveal; public bool HasCompletedReveal => _hasCompletedReveal;
@@ -60,6 +63,12 @@ namespace UI.CardSystem.StateMachine
OnDragStarted?.Invoke(this); OnDragStarted?.Invoke(this);
} }
// Helper method for states/card to signal drag ended
public void NotifyDragEnded()
{
OnDragEnded?.Invoke(this);
}
private void Awake() private void Awake()
{ {
// Auto-find components if not assigned // Auto-find components if not assigned

View 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;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 87ed5616041a4d878f452a8741e1eeab
timeCreated: 1763385180

View File

@@ -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);
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: fc610b791f43409e8231085a70514e2c
timeCreated: 1763374419

View File

@@ -4,35 +4,60 @@ using UnityEngine;
namespace UI.CardSystem.StateMachine.States namespace UI.CardSystem.StateMachine.States
{ {
/// <summary> /// <summary>
/// Dragging revealed state for pending cards after flip; minimal overlay of CardDraggingState but no badges. /// Dragging revealed state for pending cards after flip.
/// Shows card front without badges, handles placement or return to corner.
/// </summary> /// </summary>
public class CardDraggingRevealedState : AppleState public class CardDraggingRevealedState : AppleState, ICardStateDragHandler
{ {
private CardContext context; private CardContext _context;
private Vector3 originalScale; private Vector3 _originalScale;
private void Awake() private void Awake()
{ {
context = GetComponentInParent<CardContext>(); _context = GetComponentInParent<CardContext>();
} }
public override void OnEnterState() public override void OnEnterState()
{ {
if (context == null) return; if (_context == null) return;
if (context.CardDisplay != null) if (_context.CardDisplay != null)
{ {
context.CardDisplay.gameObject.SetActive(true); _context.CardDisplay.gameObject.SetActive(true);
context.CardDisplay.transform.localRotation = Quaternion.Euler(0,0,0); _context.CardDisplay.transform.localRotation = Quaternion.Euler(0,0,0);
} }
originalScale = context.RootTransform.localScale; _originalScale = _context.RootTransform.localScale;
context.RootTransform.localScale = originalScale * 1.15f; _context.RootTransform.localScale = _originalScale * 1.15f;
}
/// <summary>
/// Already in dragging state, nothing to do
/// </summary>
public bool OnCardDragStarted(CardContext ctx)
{
return true; // Prevent default DraggingState transition
}
/// <summary>
/// Handle drag end - just let AlbumViewPage handle placement logic
/// Stay in this state until AlbumViewPage transitions us after tween
/// </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
return true;
} }
private void OnDisable() private void OnDisable()
{ {
if (context?.RootTransform != null) if (_context?.RootTransform != null)
{ {
context.RootTransform.localScale = originalScale; _context.RootTransform.localScale = _originalScale;
} }
} }
} }

View File

@@ -1,13 +1,14 @@
using Core.SaveLoad; using Core;
using Core.SaveLoad;
using UnityEngine; using UnityEngine;
namespace UI.CardSystem.StateMachine.States namespace UI.CardSystem.StateMachine.States
{ {
/// <summary> /// <summary>
/// Card is in pending face-down state in corner, awaiting drag. /// Card is in pending face-down state in corner, awaiting drag.
/// On drag start, triggers data assignment, flip animation, and page navigation. /// On drag start, triggers flip animation and transitions to revealed dragging.
/// </summary> /// </summary>
public class CardPendingFaceDownState : AppleState public class CardPendingFaceDownState : AppleState, ICardStateDragHandler
{ {
[Header("State-Owned Visuals")] [Header("State-Owned Visuals")]
[SerializeField] private GameObject cardBackVisual; [SerializeField] private GameObject cardBackVisual;
@@ -44,15 +45,31 @@ namespace UI.CardSystem.StateMachine.States
} }
/// <summary> /// <summary>
/// Called by Card.OnDragStartedHook when user starts dragging. /// Handle drag start - triggers flip animation and page navigation
/// This triggers the smart selection, page navigation, and flip animation.
/// </summary> /// </summary>
public void OnDragStarted() public bool OnCardDragStarted(CardContext context)
{ {
if (_isFlipping) return; // Already flipping if (_isFlipping) return true; // Already handling
// Start flip animation (data should be assigned by listeners by now) // 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)
{
Logging.Warning("[CardPendingFaceDownState] OnCardDragStarted called but no CardData assigned yet!");
return true; // Don't flip without data
}
// Start flip animation (data is now guaranteed to be assigned)
StartFlipAnimation(); StartFlipAnimation();
return true; // We handled it, prevent default DraggingState transition
}
/// <summary>
/// We don't handle drag end in face-down state
/// </summary>
public bool OnCardDragEnded(CardContext context)
{
return false;
} }
private void StartFlipAnimation() private void StartFlipAnimation()

View File

@@ -38,7 +38,7 @@ MonoBehaviour:
m_ExplicitNullChecks: 0 m_ExplicitNullChecks: 0
m_ExplicitDivideByZeroChecks: 0 m_ExplicitDivideByZeroChecks: 0
m_ExplicitArrayBoundsChecks: 0 m_ExplicitArrayBoundsChecks: 0
m_CompressionType: -1 m_CompressionType: 0
m_InstallInBuildFolder: 0 m_InstallInBuildFolder: 0
m_InsightsSettingsContainer: m_InsightsSettingsContainer:
m_BuildProfileEngineDiagnosticsState: 2 m_BuildProfileEngineDiagnosticsState: 2

42459
card_diff.txt Normal file

File diff suppressed because one or more lines are too long