using System; using System.Collections.Generic; using System.Linq; using AppleHills.Data.CardSystem; using Bootstrap; using Core; using Core.SaveLoad; 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; public static CardSystemManager Instance => _instance; [Header("Card Collection")] [SerializeField] private List availableCards = new List(); // 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; // Keep a reference to unsubscribe safely private Action _onSaveDataLoadedHandler; private void Awake() { _instance = this; // Register for post-boot initialization BootCompletionService.RegisterInitAction(InitializePostBoot); } private void InitializePostBoot() { // Load card definitions from Addressables LoadCardDefinitionsFromAddressables(); Logging.Debug("[CardSystemManager] Post-boot initialization complete"); } /// /// Loads all card definitions from Addressables using the "BlokkemonCard" label /// private async void LoadCardDefinitionsFromAddressables() { availableCards = new List(); // Load by label instead of group name for better flexibility var handle = UnityEngine.AddressableAssets.Addressables.LoadResourceLocationsAsync(new string[] { "BlokkemonCard" }, UnityEngine.AddressableAssets.Addressables.MergeMode.Union); await handle.Task; if (handle.Status == UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.Succeeded) { var locations = handle.Result; var loadedIds = new HashSet(); foreach (var loc in locations) { var cardHandle = UnityEngine.AddressableAssets.Addressables.LoadAssetAsync(loc); await cardHandle.Task; if (cardHandle.Status == UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.Succeeded) { var cardDef = cardHandle.Result; if (cardDef != null && !string.IsNullOrEmpty(cardDef.Id) && !loadedIds.Contains(cardDef.Id)) { availableCards.Add(cardDef); loadedIds.Add(cardDef.Id); } } } // Build lookup now that cards are loaded BuildDefinitionLookup(); // Apply saved state when save data is available if (SaveLoadManager.Instance != null) { if (SaveLoadManager.Instance.IsSaveDataLoaded) { ApplySavedCardStateIfAvailable(); } else { SaveLoadManager.Instance.OnLoadCompleted += OnSaveDataLoadedHandler; } } } } private void OnDestroy() { if (SaveLoadManager.Instance != null) { SaveLoadManager.Instance.OnLoadCompleted -= OnSaveDataLoadedHandler; } } // Apply saved state if present in the SaveLoadManager private void ApplySavedCardStateIfAvailable() { var data = SaveLoadManager.Instance?.currentSaveData; if (data?.cardCollection != null) { ApplyCardCollectionState(data.cardCollection); Logging.Debug("[CardSystemManager] Applied saved card collection state after loading definitions"); } } // Event handler for when save data load completes private void OnSaveDataLoadedHandler(string slot) { ApplySavedCardStateIfAvailable(); if (SaveLoadManager.Instance != null) { SaveLoadManager.Instance.OnLoadCompleted -= OnSaveDataLoadedHandler; } } /// /// 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; } /// /// Export current card collection to a serializable snapshot /// public CardCollectionState ExportCardCollectionState() { var state = new CardCollectionState { boosterPackCount = playerInventory.BoosterPackCount, cards = new List() }; foreach (var card in playerInventory.CollectedCards.Values) { if (string.IsNullOrEmpty(card.DefinitionId)) continue; state.cards.Add(new SavedCardEntry { definitionId = card.DefinitionId, rarity = card.Rarity, copiesOwned = card.CopiesOwned }); } return state; } /// /// Apply a previously saved snapshot to the runtime inventory /// public void ApplyCardCollectionState(CardCollectionState state) { if (state == null) return; playerInventory.ClearAllCards(); playerInventory.BoosterPackCount = state.boosterPackCount; OnBoosterCountChanged?.Invoke(playerInventory.BoosterPackCount); foreach (var entry in state.cards) { if (string.IsNullOrEmpty(entry.definitionId)) continue; if (_definitionLookup.TryGetValue(entry.definitionId, out var def)) { // Create from definition to ensure links, then overwrite runtime fields var cd = def.CreateCardData(); cd.Rarity = entry.rarity; cd.CopiesOwned = entry.copiesOwned; playerInventory.AddCard(cd); } else { Logging.Warning($"[CardSystemManager] Saved card definition not found: {entry.definitionId}"); } } } } }