Files
AppleHillsProduction/Assets/Scripts/Data/CardSystem/CardSystemManager.cs
2025-11-05 20:37:17 +01:00

488 lines
18 KiB
C#
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Linq;
using AppleHills.Data.CardSystem;
using Core;
using Core.Lifecycle;
using Core.SaveLoad;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Data.CardSystem
{
/// <summary>
/// Manages the player's card collection, booster packs, and related operations.
/// Manages the card collection system for the game.
/// Handles unlocking cards, tracking collections, and integrating with the save/load system.
/// </summary>
public class CardSystemManager : ManagedBehaviour
{
private static CardSystemManager _instance;
public static CardSystemManager Instance => _instance;
// Save system configuration
public override bool AutoRegisterForSave => true;
public override string SaveId => "CardSystemManager";
[Header("Card Collection")]
[SerializeField] private List<CardDefinition> availableCards = new List<CardDefinition>();
// 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<string, CardDefinition> _definitionLookup = new Dictionary<string, CardDefinition>();
// Event callbacks using System.Action
public event Action<List<CardData>> OnBoosterOpened;
public event Action<CardData> OnCardCollected;
public event Action<CardData> OnCardRarityUpgraded;
public event Action<int> OnBoosterCountChanged;
public override int ManagedAwakePriority => 60; // Data systems
private new void Awake()
{
base.Awake(); // CRITICAL: Register with LifecycleManager!
// Set instance immediately so it's available before OnManagedAwake() is called
_instance = this;
}
protected override void OnManagedAwake()
{
// Load card definitions from Addressables, then register with save system
LoadCardDefinitionsFromAddressables();
Logging.Debug("[CardSystemManager] Initialized");
}
/// <summary>
/// Loads all card definitions from Addressables using the "BlokkemonCard" label
/// </summary>
private async void LoadCardDefinitionsFromAddressables()
{
availableCards = new List<CardDefinition>();
// 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<string>();
foreach (var loc in locations)
{
var cardHandle = UnityEngine.AddressableAssets.Addressables.LoadAssetAsync<CardDefinition>(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();
Logging.Debug($"[CardSystemManager] Loaded {availableCards.Count} card definitions from Addressables");
}
else
{
Logging.Warning("[CardSystemManager] Failed to load card definitions from Addressables");
}
}
/// <summary>
/// Builds a lookup dictionary for quick access to card definitions by ID
/// </summary>
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);
}
}
}
/// <summary>
/// Adds a booster pack to the player's inventory
/// </summary>
public void AddBoosterPack(int count = 1)
{
playerInventory.BoosterPackCount += count;
OnBoosterCountChanged?.Invoke(playerInventory.BoosterPackCount);
Logging.Debug($"[CardSystemManager] Added {count} booster pack(s). Total: {playerInventory.BoosterPackCount}");
}
/// <summary>
/// Opens a booster pack and returns the newly obtained cards
/// </summary>
public List<CardData> OpenBoosterPack()
{
if (playerInventory.BoosterPackCount <= 0)
{
Logging.Warning("[CardSystemManager] Attempted to open a booster pack, but none are available.");
return new List<CardData>();
}
playerInventory.BoosterPackCount--;
OnBoosterCountChanged?.Invoke(playerInventory.BoosterPackCount);
// Draw 3 cards based on rarity distribution
List<CardData> 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;
}
/// <summary>
/// Adds a card to the player's inventory, handles duplicates
/// </summary>
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.");
}
}
/// <summary>
/// Draws random cards based on rarity distribution
/// </summary>
private List<CardData> DrawRandomCards(int count)
{
List<CardData> result = new List<CardData>();
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<CardDefinition> 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;
}
/// <summary>
/// Determines a random card rarity with appropriate weighting
/// </summary>
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;
}
/// <summary>
/// Returns all cards from the player's collection
/// </summary>
public List<CardData> GetAllCollectedCards()
{
return playerInventory.GetAllCards();
}
/// <summary>
/// Returns cards from a specific zone
/// </summary>
public List<CardData> GetCardsByZone(CardZone zone)
{
return playerInventory.GetCardsByZone(zone);
}
/// <summary>
/// Returns cards of a specific rarity
/// </summary>
public List<CardData> GetCardsByRarity(CardRarity rarity)
{
return playerInventory.GetCardsByRarity(rarity);
}
/// <summary>
/// Returns the number of booster packs the player has
/// </summary>
public int GetBoosterPackCount()
{
return playerInventory.BoosterPackCount;
}
/// <summary>
/// Returns whether a specific card definition has been collected
/// </summary>
public bool IsCardCollected(string definitionId)
{
return playerInventory.HasCard(definitionId);
}
/// <summary>
/// Gets total unique card count
/// </summary>
public int GetUniqueCardCount()
{
return playerInventory.GetUniqueCardCount();
}
/// <summary>
/// Gets completion percentage for a specific zone (0-100)
/// </summary>
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;
}
/// <summary>
/// Returns all available card definitions in the system
/// </summary>
public List<CardDefinition> GetAllCardDefinitions()
{
return new List<CardDefinition>(availableCards);
}
/// <summary>
/// Returns direct access to the player's card inventory
/// For advanced operations and testing
/// </summary>
public CardInventory GetCardInventory()
{
return playerInventory;
}
/// <summary>
/// Returns cards filtered by both zone and rarity
/// </summary>
public List<CardData> GetCardsByZoneAndRarity(CardZone zone, CardRarity rarity)
{
List<CardData> zoneCards = GetCardsByZone(zone);
return zoneCards.FindAll(c => c.Rarity == rarity);
}
/// <summary>
/// Returns the count of cards by rarity
/// </summary>
public int GetCardCountByRarity(CardRarity rarity)
{
return playerInventory.GetCardsByRarity(rarity).Count;
}
/// <summary>
/// Returns the count of cards by zone
/// </summary>
public int GetCardCountByZone(CardZone zone)
{
return playerInventory.GetCardsByZone(zone).Count;
}
/// <summary>
/// Gets the total number of card definitions available in the system
/// </summary>
public int GetTotalCardDefinitionsCount()
{
return availableCards.Count;
}
/// <summary>
/// Gets the total collection completion percentage (0-100)
/// </summary>
public float GetTotalCompletionPercentage()
{
if (availableCards.Count == 0) return 0;
return (float)GetUniqueCardCount() / availableCards.Count * 100f;
}
/// <summary>
/// Gets total completion percentage for a specific rarity (0-100)
/// </summary>
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;
}
/// <summary>
/// Export current card collection to a serializable snapshot
/// </summary>
public CardCollectionState ExportCardCollectionState()
{
var state = new CardCollectionState
{
boosterPackCount = playerInventory.BoosterPackCount,
cards = new List<SavedCardEntry>()
};
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;
}
/// <summary>
/// Apply a previously saved snapshot to the runtime inventory
/// </summary>
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}");
}
}
}
#region Save/Load Lifecycle Hooks
protected override string OnGlobalSaveRequested()
{
var state = ExportCardCollectionState();
return JsonUtility.ToJson(state);
}
protected override void OnGlobalRestoreRequested(string serializedData)
{
if (string.IsNullOrEmpty(serializedData))
{
Logging.Debug("[CardSystemManager] No saved state to restore, using defaults");
return;
}
try
{
var state = JsonUtility.FromJson<CardCollectionState>(serializedData);
if (state != null)
{
ApplyCardCollectionState(state);
Logging.Debug("[CardSystemManager] Successfully restored card collection state");
}
else
{
Logging.Warning("[CardSystemManager] Failed to deserialize card collection state");
}
}
catch (Exception ex)
{
Logging.Warning($"[CardSystemManager] Exception while restoring card collection state: {ex}");
}
}
#endregion
}
}