using System; using System.Collections.Generic; using System.Linq; using AppleHills.Data.CardSystem; using Bootstrap; using Core; using UnityEngine; #if UNITY_EDITOR using UnityEditor; #endif namespace Data.CardSystem { /// /// Manages the player's card collection, booster packs, and related operations. /// Uses a singleton pattern for global access. /// public class CardSystemManager : MonoBehaviour { private static CardSystemManager _instance; private static bool _isQuitting = false; public static CardSystemManager Instance => _instance; [Header("Card Collection")] [SerializeField] private List availableCards = new List(); [Header("Auto-Loading Configuration")] [SerializeField] private bool autoLoadCardDefinitions = true; [SerializeField] private string cardDataPath = "Data/Cards"; // Runtime data - will be serialized for save/load [SerializeField] private CardInventory playerInventory = new CardInventory(); // Dictionary to quickly look up card definitions by ID private Dictionary _definitionLookup = new Dictionary(); // Event callbacks using System.Action public event Action> OnBoosterOpened; public event Action OnCardCollected; public event Action OnCardRarityUpgraded; public event Action OnBoosterCountChanged; private void Awake() { _instance = this; // Auto-load card definitions if enabled if (autoLoadCardDefinitions) { LoadCardDefinitionsFromFolder(); } // Build lookup dictionary BuildDefinitionLookup(); // Register for post-boot initialization BootCompletionService.RegisterInitAction(InitializePostBoot); } private void InitializePostBoot() { // Initialize any dependencies that require other services to be ready Logging.Debug("[CardSystemManager] Post-boot initialization complete"); } private void OnApplicationQuit() { _isQuitting = true; } /// /// Loads all card definitions from the specified folder /// private void LoadCardDefinitionsFromFolder() { // Initialize list if needed if (availableCards == null) { availableCards = new List(); } #if UNITY_EDITOR // In editor we can load assets directly from the project folder string folderPath = "Assets/" + cardDataPath; string[] guids = AssetDatabase.FindAssets("t:CardDefinition", new[] { folderPath }); List loadedDefinitions = new List(); foreach (string guid in guids) { string assetPath = AssetDatabase.GUIDToAssetPath(guid); CardDefinition cardDef = AssetDatabase.LoadAssetAtPath(assetPath); if (cardDef != null && !string.IsNullOrEmpty(cardDef.Id)) { loadedDefinitions.Add(cardDef); } } // Replace the existing list with loaded definitions availableCards = loadedDefinitions; #else // In build, load from Resources folder CardDefinition[] resourceCards = Resources.LoadAll(cardDataPath); if (resourceCards != null && resourceCards.Length > 0) { availableCards = resourceCards.Where(card => card != null && !string.IsNullOrEmpty(card.Id)).ToList(); } #endif Logging.Debug($"[CardSystemManager] Loaded {availableCards.Count} card definitions from {cardDataPath}"); } /// /// Builds a lookup dictionary for quick access to card definitions by ID /// private void BuildDefinitionLookup() { _definitionLookup.Clear(); foreach (var cardDef in availableCards) { if (cardDef != null && !string.IsNullOrEmpty(cardDef.Id)) { _definitionLookup[cardDef.Id] = cardDef; } } // Link existing card data to their definitions foreach (var cardData in playerInventory.CollectedCards.Values) { if (!string.IsNullOrEmpty(cardData.DefinitionId) && _definitionLookup.TryGetValue(cardData.DefinitionId, out CardDefinition def)) { cardData.SetDefinition(def); } } } /// /// Adds a booster pack to the player's inventory /// public void AddBoosterPack(int count = 1) { playerInventory.BoosterPackCount += count; OnBoosterCountChanged?.Invoke(playerInventory.BoosterPackCount); Logging.Debug($"[CardSystemManager] Added {count} booster pack(s). Total: {playerInventory.BoosterPackCount}"); } /// /// Opens a booster pack and returns the newly obtained cards /// public List OpenBoosterPack() { if (playerInventory.BoosterPackCount <= 0) { Logging.Warning("[CardSystemManager] Attempted to open a booster pack, but none are available."); return new List(); } playerInventory.BoosterPackCount--; OnBoosterCountChanged?.Invoke(playerInventory.BoosterPackCount); // Draw 3 cards based on rarity distribution List drawnCards = DrawRandomCards(3); // Add cards to the inventory foreach (var card in drawnCards) { AddCardToInventory(card); } // Notify listeners OnBoosterOpened?.Invoke(drawnCards); Logging.Debug($"[CardSystemManager] Opened a booster pack and obtained {drawnCards.Count} cards. Remaining boosters: {playerInventory.BoosterPackCount}"); return drawnCards; } /// /// Adds a card to the player's inventory, handles duplicates /// private void AddCardToInventory(CardData card) { // Check if the player already has this card type (definition) if (playerInventory.HasCard(card.DefinitionId)) { CardData existingCard = playerInventory.GetCard(card.DefinitionId); existingCard.CopiesOwned++; // Check if the card can be upgraded if (existingCard.TryUpgradeRarity()) { OnCardRarityUpgraded?.Invoke(existingCard); } Logging.Debug($"[CardSystemManager] Added duplicate card '{card.Name}'. Now have {existingCard.CopiesOwned} copies."); } else { // Add new card playerInventory.AddCard(card); OnCardCollected?.Invoke(card); Logging.Debug($"[CardSystemManager] Added new card '{card.Name}' to collection."); } } /// /// Draws random cards based on rarity distribution /// private List DrawRandomCards(int count) { List result = new List(); if (availableCards.Count == 0) { Debug.LogError("[CardSystemManager] No available cards defined!"); return result; } // Simple weighted random selection based on rarity for (int i = 0; i < count; i++) { // Determine card rarity first CardRarity rarity = DetermineRandomRarity(); // Filter cards by the selected rarity List cardsOfRarity = availableCards.FindAll(c => c.Rarity == rarity); if (cardsOfRarity.Count > 0) { // Select a random card of this rarity int randomIndex = UnityEngine.Random.Range(0, cardsOfRarity.Count); CardDefinition selectedDef = cardsOfRarity[randomIndex]; // Create card data from definition CardData newCard = selectedDef.CreateCardData(); result.Add(newCard); } else { // Fallback if no cards of the selected rarity Logging.Warning($"[CardSystemManager] No cards of rarity {rarity} available, selecting a random card instead."); int randomIndex = UnityEngine.Random.Range(0, availableCards.Count); CardDefinition randomDef = availableCards[randomIndex]; CardData newCard = randomDef.CreateCardData(); result.Add(newCard); } } return result; } /// /// Determines a random card rarity with appropriate weighting /// private CardRarity DetermineRandomRarity() { // Simple weighted random - can be adjusted for better distribution float rand = UnityEngine.Random.value; if (rand < 0.6f) return CardRarity.Common; if (rand < 0.85f) return CardRarity.Uncommon; if (rand < 0.95f) return CardRarity.Rare; if (rand < 0.99f) return CardRarity.Epic; return CardRarity.Legendary; } /// /// Returns all cards from the player's collection /// public List GetAllCollectedCards() { 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); } /// /// Returns the number of booster packs the player has /// public int GetBoosterPackCount() { return playerInventory.BoosterPackCount; } /// /// Returns whether a specific card definition has been collected /// public bool IsCardCollected(string 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; } /// /// Returns all available card definitions in the system /// public List GetAllCardDefinitions() { return new List(availableCards); } /// /// Returns direct access to the player's card inventory /// For advanced operations and testing /// public CardInventory GetCardInventory() { return playerInventory; } /// /// Returns cards filtered by both zone and rarity /// public List GetCardsByZoneAndRarity(CardZone zone, CardRarity rarity) { List zoneCards = GetCardsByZone(zone); return zoneCards.FindAll(c => c.Rarity == rarity); } /// /// Returns the count of cards by rarity /// public int GetCardCountByRarity(CardRarity rarity) { return playerInventory.GetCardsByRarity(rarity).Count; } /// /// Returns the count of cards by zone /// public int GetCardCountByZone(CardZone zone) { return playerInventory.GetCardsByZone(zone).Count; } /// /// Gets the total number of card definitions available in the system /// public int GetTotalCardDefinitionsCount() { return availableCards.Count; } /// /// Gets the total collection completion percentage (0-100) /// public float GetTotalCompletionPercentage() { if (availableCards.Count == 0) return 0; return (float)GetUniqueCardCount() / availableCards.Count * 100f; } /// /// Gets total completion percentage for a specific rarity (0-100) /// public float GetRarityCompletionPercentage(CardRarity rarity) { // Count available cards of this rarity int totalOfRarity = availableCards.FindAll(c => c.Rarity == rarity).Count; if (totalOfRarity == 0) return 0; // Count collected cards of this rarity int collectedOfRarity = playerInventory.GetCardsByRarity(rarity).Count; return (float)collectedOfRarity / totalOfRarity * 100f; } } }