diff --git a/Assets/Scripts/Core/Logging.cs b/Assets/Scripts/Core/Logging.cs
new file mode 100644
index 00000000..9f636cd6
--- /dev/null
+++ b/Assets/Scripts/Core/Logging.cs
@@ -0,0 +1,23 @@
+namespace Core
+{
+ public static class Logging
+ {
+ [System.Diagnostics.Conditional("ENABLE_LOG")]
+ public static void Debug(object message)
+ {
+ UnityEngine.Debug.Log(message);
+ }
+
+ [System.Diagnostics.Conditional("ENABLE_LOG")]
+ public static void Warning(object message)
+ {
+ UnityEngine.Debug.LogWarning(message);
+ }
+
+ [System.Diagnostics.Conditional("ENABLE_LOG")]
+ public static void Error(object message)
+ {
+ UnityEngine.Debug.LogError(message);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Scripts/Core/Logging.cs.meta b/Assets/Scripts/Core/Logging.cs.meta
new file mode 100644
index 00000000..f11c0dc4
--- /dev/null
+++ b/Assets/Scripts/Core/Logging.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 99ee122ee6ee4b57b40c03be4479b124
+timeCreated: 1760446431
\ No newline at end of file
diff --git a/Assets/Scripts/Core/QuickAccess.cs b/Assets/Scripts/Core/QuickAccess.cs
index 17fd16f1..954860c7 100644
--- a/Assets/Scripts/Core/QuickAccess.cs
+++ b/Assets/Scripts/Core/QuickAccess.cs
@@ -2,6 +2,7 @@
using AppleHills.Data.CardSystem;
using Cinematics;
using Core;
+using Data.CardSystem;
using Input;
using PuzzleS;
diff --git a/Assets/Scripts/Data/CardSystem/CardInventory.cs b/Assets/Scripts/Data/CardSystem/CardInventory.cs
new file mode 100644
index 00000000..ae1479f8
--- /dev/null
+++ b/Assets/Scripts/Data/CardSystem/CardInventory.cs
@@ -0,0 +1,203 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+
+namespace AppleHills.Data.CardSystem
+{
+ ///
+ /// Manages the player's collection of cards and booster packs
+ ///
+ [Serializable]
+ public class CardInventory
+ {
+ // Dictionary of collected cards indexed by definition ID
+ [SerializeField] private Dictionary collectedCards = new Dictionary();
+
+ // Number of unopened booster packs the player has
+ [SerializeField] private int boosterPackCount;
+
+ // Additional lookup dictionaries (not serialized)
+ [NonSerialized] private Dictionary> cardsByZone = new Dictionary>();
+ [NonSerialized] private Dictionary> cardsByRarity = new Dictionary>();
+
+ // Properties with public getters
+ public Dictionary CollectedCards => collectedCards;
+
+ public int BoosterPackCount
+ {
+ get => boosterPackCount;
+ set => boosterPackCount = value;
+ }
+
+ // Constructor initializes empty dictionaries
+ public CardInventory()
+ {
+ // Initialize dictionaries for all enum values so we never have to check for null
+ foreach (CardZone zone in Enum.GetValues(typeof(CardZone)))
+ {
+ cardsByZone[zone] = new List();
+ }
+
+ foreach (CardRarity rarity in Enum.GetValues(typeof(CardRarity)))
+ {
+ cardsByRarity[rarity] = new List();
+ }
+ }
+
+ ///
+ /// Get all cards in the player's collection as a list
+ ///
+ public List GetAllCards()
+ {
+ return new List(collectedCards.Values);
+ }
+
+ ///
+ /// Get cards filtered by zone
+ ///
+ public List GetCardsByZone(CardZone zone)
+ {
+ return new List(cardsByZone[zone]);
+ }
+
+ ///
+ /// Get cards filtered by rarity
+ ///
+ public List GetCardsByRarity(CardRarity rarity)
+ {
+ return new List(cardsByRarity[rarity]);
+ }
+
+ ///
+ /// Add a card to the inventory (or increase the copies if already owned)
+ ///
+ public void AddCard(CardData card)
+ {
+ if (card == null) return;
+
+ if (collectedCards.TryGetValue(card.DefinitionId, out CardData existingCard))
+ {
+ // Increase copies of existing card
+ existingCard.CopiesOwned++;
+ }
+ else
+ {
+ // Add new card to collection
+ var newCard = new CardData(card);
+ collectedCards[card.DefinitionId] = newCard;
+
+ // Add to lookup dictionaries
+ cardsByZone[newCard.Zone].Add(newCard);
+ cardsByRarity[newCard.Rarity].Add(newCard);
+ }
+ }
+
+ ///
+ /// Update card zone and rarity indexes when a card changes
+ ///
+ public void UpdateCardProperties(CardData card, CardZone oldZone, CardRarity oldRarity)
+ {
+ // Only needed if the card's zone or rarity actually changed
+ if (oldZone != card.Zone)
+ {
+ cardsByZone[oldZone].Remove(card);
+ cardsByZone[card.Zone].Add(card);
+ }
+
+ if (oldRarity != card.Rarity)
+ {
+ cardsByRarity[oldRarity].Remove(card);
+ cardsByRarity[card.Rarity].Add(card);
+ }
+ }
+
+ ///
+ /// Get a specific card from the collection by definition ID
+ ///
+ public CardData GetCard(string definitionId)
+ {
+ return collectedCards.TryGetValue(definitionId, out CardData card) ? card : null;
+ }
+
+ ///
+ /// Check if the player has a specific card
+ ///
+ public bool HasCard(string definitionId)
+ {
+ return collectedCards.ContainsKey(definitionId);
+ }
+
+ ///
+ /// Get total number of unique cards in collection
+ ///
+ public int GetUniqueCardCount()
+ {
+ return collectedCards.Count;
+ }
+
+ ///
+ /// Get total number of cards including copies
+ ///
+ public int GetTotalCardCount()
+ {
+ return collectedCards.Values.Sum(card => card.CopiesOwned);
+ }
+
+ ///
+ /// Get number of cards in a specific zone
+ ///
+ public int GetZoneCardCount(CardZone zone)
+ {
+ return cardsByZone[zone].Count;
+ }
+
+ ///
+ /// Get number of cards of a specific rarity
+ ///
+ public int GetRarityCardCount(CardRarity rarity)
+ {
+ return cardsByRarity[rarity].Count;
+ }
+
+ ///
+ /// Get cards sorted by collection index (for album view)
+ ///
+ public List GetCardsSortedByIndex()
+ {
+ return collectedCards.Values
+ .OrderBy(card => card.CollectionIndex)
+ .ToList();
+ }
+
+ ///
+ /// Check if there's a complete collection for a specific zone
+ ///
+ public bool IsZoneCollectionComplete(CardZone zone, List allAvailableCards)
+ {
+ int availableInZone = allAvailableCards.Count(card => card.Zone == zone);
+ int collectedInZone = cardsByZone[zone].Count;
+
+ return availableInZone > 0 && collectedInZone >= availableInZone;
+ }
+
+ ///
+ /// Adds booster packs to the inventory
+ ///
+ public void AddBoosterPacks(int count)
+ {
+ boosterPackCount += count;
+ }
+
+ ///
+ /// Use a single booster pack (returns true if successful)
+ ///
+ public bool UseBoosterPack()
+ {
+ if (boosterPackCount <= 0) return false;
+
+ boosterPackCount--;
+ return true;
+ }
+ }
+}
diff --git a/Assets/Scripts/Data/CardSystem/CardInventory.cs.meta b/Assets/Scripts/Data/CardSystem/CardInventory.cs.meta
new file mode 100644
index 00000000..fecde4f8
--- /dev/null
+++ b/Assets/Scripts/Data/CardSystem/CardInventory.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: f5b1aa91590d48a1a4c426f3cd4aa103
+timeCreated: 1760445622
\ No newline at end of file
diff --git a/Assets/Scripts/Data/CardSystem/CardSystemManager.cs b/Assets/Scripts/Data/CardSystem/CardSystemManager.cs
index c80ed549..183b4e0d 100644
--- a/Assets/Scripts/Data/CardSystem/CardSystemManager.cs
+++ b/Assets/Scripts/Data/CardSystem/CardSystemManager.cs
@@ -1,8 +1,9 @@
using System;
using System.Collections.Generic;
+using AppleHills.Data.CardSystem;
using UnityEngine;
-namespace AppleHills.Data.CardSystem
+namespace Data.CardSystem
{
///
/// Manages the player's card collection, booster packs, and related operations.
@@ -138,8 +139,9 @@ namespace AppleHills.Data.CardSystem
private void AddCardToInventory(CardData card)
{
// Check if the player already has this card type (definition)
- if (playerInventory.CollectedCards.TryGetValue(card.DefinitionId, out CardData existingCard))
+ if (playerInventory.HasCard(card.DefinitionId))
{
+ CardData existingCard = playerInventory.GetCard(card.DefinitionId);
existingCard.CopiesOwned++;
// Check if the card can be upgraded
@@ -153,7 +155,7 @@ namespace AppleHills.Data.CardSystem
else
{
// Add new card
- playerInventory.CollectedCards.Add(card.DefinitionId, new CardData(card));
+ playerInventory.AddCard(card);
OnCardCollected?.Invoke(card);
Debug.Log($"[CardSystemManager] Added new card '{card.Name}' to collection.");
@@ -227,12 +229,23 @@ namespace AppleHills.Data.CardSystem
///
public List GetAllCollectedCards()
{
- List result = new List();
- foreach (var card in playerInventory.CollectedCards.Values)
- {
- result.Add(card);
- }
- return result;
+ return playerInventory.GetAllCards();
+ }
+
+ ///
+ /// Returns cards from a specific zone
+ ///
+ public List GetCardsByZone(CardZone zone)
+ {
+ return playerInventory.GetCardsByZone(zone);
+ }
+
+ ///
+ /// Returns cards of a specific rarity
+ ///
+ public List GetCardsByRarity(CardRarity rarity)
+ {
+ return playerInventory.GetCardsByRarity(rarity);
}
///
@@ -248,17 +261,30 @@ namespace AppleHills.Data.CardSystem
///
public bool IsCardCollected(string definitionId)
{
- return playerInventory.CollectedCards.ContainsKey(definitionId);
+ return playerInventory.HasCard(definitionId);
+ }
+
+ ///
+ /// Gets total unique card count
+ ///
+ public int GetUniqueCardCount()
+ {
+ return playerInventory.GetUniqueCardCount();
+ }
+
+ ///
+ /// Gets completion percentage for a specific zone (0-100)
+ ///
+ public float GetZoneCompletionPercentage(CardZone zone)
+ {
+ // Count available cards in this zone
+ int totalInZone = availableCards.FindAll(c => c.Zone == zone).Count;
+ if (totalInZone == 0) return 0;
+
+ // Count collected cards in this zone
+ int collectedInZone = playerInventory.GetCardsByZone(zone).Count;
+
+ return (float)collectedInZone / totalInZone * 100f;
}
}
-
- ///
- /// Serializable class to store the player's card inventory
- ///
- [Serializable]
- public class CardInventory
- {
- public Dictionary CollectedCards = new Dictionary();
- public int BoosterPackCount;
- }
}
diff --git a/Assets/Scripts/UI/CardSystem/AlbumViewPage.cs b/Assets/Scripts/UI/CardSystem/AlbumViewPage.cs
index 08bc1341..069cd840 100644
--- a/Assets/Scripts/UI/CardSystem/AlbumViewPage.cs
+++ b/Assets/Scripts/UI/CardSystem/AlbumViewPage.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using AppleHills.Data.CardSystem;
+using Data.CardSystem;
using Pixelplacement;
using UnityEngine;
using UnityEngine.UI;
diff --git a/Assets/Scripts/UI/CardSystem/BoosterOpeningPage.cs b/Assets/Scripts/UI/CardSystem/BoosterOpeningPage.cs
index c2a009f5..56bd7668 100644
--- a/Assets/Scripts/UI/CardSystem/BoosterOpeningPage.cs
+++ b/Assets/Scripts/UI/CardSystem/BoosterOpeningPage.cs
@@ -1,6 +1,7 @@
using System.Collections;
using System.Collections.Generic;
using AppleHills.Data.CardSystem;
+using Data.CardSystem;
using Pixelplacement;
using UnityEngine;
using UnityEngine.UI;
diff --git a/Assets/Scripts/UI/CardSystem/CardAlbumUI.cs b/Assets/Scripts/UI/CardSystem/CardAlbumUI.cs
index 28ef9a36..79d60628 100644
--- a/Assets/Scripts/UI/CardSystem/CardAlbumUI.cs
+++ b/Assets/Scripts/UI/CardSystem/CardAlbumUI.cs
@@ -1,5 +1,6 @@
using System;
using AppleHills.Data.CardSystem;
+using Data.CardSystem;
using UnityEngine;
using UnityEngine.UI;
diff --git a/Assets/Scripts/UI/CardSystem/CardMenuPage.cs b/Assets/Scripts/UI/CardSystem/CardMenuPage.cs
index 83ce4a95..85a7683f 100644
--- a/Assets/Scripts/UI/CardSystem/CardMenuPage.cs
+++ b/Assets/Scripts/UI/CardSystem/CardMenuPage.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using AppleHills.Data.CardSystem;
+using Data.CardSystem;
using Pixelplacement;
using UnityEngine;
using UnityEngine.UI;
diff --git a/docs/card_system_implementation_plan.md b/docs/card_system_implementation_plan.md
new file mode 100644
index 00000000..b60bbe14
--- /dev/null
+++ b/docs/card_system_implementation_plan.md
@@ -0,0 +1,132 @@
+# Card System Implementation Plan
+
+## Current Implementation Analysis
+
+### Data Layer
+
+1. **CardData.cs**:
+ - Represents an instance of a card in the player's collection
+ - Contains unique ID, rarity, and copies owned
+ - Has a reference to its CardDefinition for display information
+ - Includes methods for rarity upgrades when collecting duplicates
+ - Well-structured with proper serialization support
+
+2. **CardDefinition.cs**:
+ - ScriptableObject template for creating card instances
+ - Contains basic info (name, description), visuals, and collection data
+ - Provides helper methods like CreateCardData() and GetBackgroundColor()
+ - Supports different zones (AppleHills, Quarry, Forest, Mountain, Beach)
+
+3. **CardSystemManager.cs**:
+ - Singleton manager for the card system
+ - Manages available card definitions and player inventory
+ - Includes event callbacks for booster packs and card collection
+ - Has partial implementation for adding booster packs
+ - Has a simple CardInventory class stub
+
+4. **CardVisualConfig.cs**:
+ - ScriptableObject for configuring the visual appearance of cards
+ - Maps rarities to colors and zones to colors/shapes
+ - Uses dictionary lookups for efficient access
+
+### UI Layer
+
+1. **CardUIElement.cs**:
+ - Handles displaying a single card in the UI
+ - Updates visuals based on card data (rarity, zone)
+ - References UI elements like card name text, images, frames, etc.
+
+2. **UIPageController.cs**:
+ - Manages UI page transitions with a stack-based navigation system
+ - Provides methods for pushing, popping, and clearing the page stack
+ - Uses events to notify when pages change
+
+3. **UI Page Components**:
+ - Several page-related files (AlbumViewPage.cs, BoosterOpeningPage.cs, CardMenuPage.cs)
+ - CardAlbumUI.cs file for handling the album display
+ - Base UIPage.cs class for common page functionality
+
+## What's Missing
+
+Based on the requirements, here's what still needs to be implemented:
+
+1. **Complete CardInventory Implementation**:
+ - Move from simple stub to full implementation
+ - Add methods to manage the player's card collection
+ - Implement filtering, sorting, and organization features
+
+2. **Booster Pack Generation**:
+ - Complete the logic to generate random booster packs with appropriate rarity distribution
+ - Implement methods for awarding booster packs to the player
+ - Add events for notifying UI when booster packs are obtained
+
+3. **Card Collection UI**:
+ - Complete the album browsing UI with filtering and sorting options
+ - Implement visual indicators for owned/not owned cards
+ - Add UI for tracking collection completion
+
+4. **Booster Pack Opening UI**:
+ - Implement the UI flow for opening booster packs
+ - Add card reveal animations and effects
+ - Create UI feedback for rare card discoveries
+
+5. **Save/Load System**:
+ - Implement persistence for the player's card collection
+ - Add autosave functionality for cards and booster packs
+
+## Implementation Plan
+
+### Step 1: Complete the Data Layer
+
+1. **Implement CardInventory.cs**:
+ - Create a dedicated class file (replacing the stub in CardSystemManager)
+ - Add methods for adding/removing cards, checking duplicates
+ - Implement filtering by zone, rarity, etc.
+ - Add collection statistics methods
+
+2. **Complete CardSystemManager.cs**:
+ - Update references to use the new CardInventory class
+ - Add methods for save/load integration
+ - Implement additional helper methods for card management
+
+### Step 2: Implement UI Flow
+
+1. **Complete UIPage Implementations**:
+ - Implement CardMenuPage.cs as the main card system entry point
+ - Complete AlbumViewPage.cs for browsing the collection
+ - Implement BoosterOpeningPage.cs for the pack opening experience
+
+2. **Card Collection Browsing**:
+ - Complete CardAlbumUI.cs to display the player's collection
+ - Add filtering by zone, rarity, etc.
+ - Implement pagination for large collections
+
+3. **Booster Pack UI**:
+ - Create UI for displaying and opening booster packs
+ - Implement card reveal animations and transitions
+ - Add sound effects and visual feedback
+
+### Step 3: Integration and Polishing
+
+1. **Connect UI to Data Layer**:
+ - Hook up UI components to CardSystemManager events
+ - Ensure data flows correctly between UI and data layers
+
+2. **Test and Balance**:
+ - Test rarity distribution in booster packs
+ - Balance the upgrade system for duplicates
+
+3. **Performance Optimization**:
+ - Implement object pooling for card UI elements
+ - Optimize loading of card data and sprites
+
+### Step 4: Additional Features (Optional)
+
+1. **Card Trading System**:
+ - Allow players to trade cards with NPCs or other players
+
+2. **Special Collection Bonuses**:
+ - Rewards for completing sets of cards
+
+3. **Card Usage Mechanics**:
+ - Ways to use cards in gameplay beyond collection