Co-authored-by: Michal Pikulski <michal.a.pikulski@gmail.com> Co-authored-by: Michal Pikulski <michal@foolhardyhorizons.com> Reviewed-on: #60
810 lines
32 KiB
C#
810 lines
32 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using AppleHills.Data.CardSystem;
|
|
using Core;
|
|
using Core.Lifecycle;
|
|
using UnityEngine;
|
|
|
|
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();
|
|
|
|
// Album system - cards waiting to be placed in album
|
|
private List<CardData> _pendingRevealCards = new List<CardData>();
|
|
private HashSet<string> _placedInAlbumCardIds = new HashSet<string>();
|
|
|
|
// Dictionary to quickly look up card definitions by ID
|
|
private Dictionary<string, CardDefinition> _definitionLookup;
|
|
private bool _lookupInitialized = false;
|
|
|
|
// Event callbacks using System.Action
|
|
public event Action<List<CardData>> OnBoosterOpened;
|
|
public event Action<CardData> OnCardCollected;
|
|
public event Action<int> OnBoosterCountChanged;
|
|
public event Action<CardData> OnPendingCardAdded;
|
|
public event Action<CardData> OnPendingCardRemoved;
|
|
public event Action<CardData> OnCardPlacedInAlbum;
|
|
|
|
internal override void OnManagedAwake()
|
|
{
|
|
// Set instance immediately (early initialization)
|
|
_instance = this;
|
|
|
|
// Load card definitions from Addressables, then register with save system
|
|
LoadCardDefinitionsFromAddressables();
|
|
}
|
|
|
|
internal override void OnManagedStart()
|
|
{
|
|
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()
|
|
{
|
|
if (_definitionLookup == null)
|
|
{
|
|
_definitionLookup = new Dictionary<string, CardDefinition>();
|
|
}
|
|
|
|
_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);
|
|
}
|
|
}
|
|
|
|
_lookupInitialized = true;
|
|
}
|
|
|
|
/// <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
|
|
/// NOTE: Cards are NOT added to inventory immediately - they're added after the reveal interaction
|
|
/// </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);
|
|
|
|
// NOTE: Cards are NOT added to inventory here anymore
|
|
// They will be added after the player interacts with each revealed card
|
|
// This allows us to show new/repeat status before adding to collection
|
|
|
|
// Notify listeners
|
|
OnBoosterOpened?.Invoke(drawnCards);
|
|
|
|
Logging.Debug($"[CardSystemManager] Opened a booster pack and obtained {drawnCards.Count} cards. Remaining boosters: {playerInventory.BoosterPackCount}");
|
|
return drawnCards;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if a card is new to the player's collection at the specified rarity
|
|
/// Checks both owned inventory and pending reveal queue
|
|
/// </summary>
|
|
/// <param name="cardData">The card to check</param>
|
|
/// <param name="existingCard">Out parameter - the existing card if found, null otherwise</param>
|
|
/// <returns>True if this is a new card at this rarity, false if already owned or pending</returns>
|
|
public bool IsCardNew(CardData cardData, out CardData existingCard)
|
|
{
|
|
// First check inventory (cards already placed in album)
|
|
if (playerInventory.HasCard(cardData.DefinitionId, cardData.Rarity))
|
|
{
|
|
existingCard = playerInventory.GetCard(cardData.DefinitionId, cardData.Rarity);
|
|
return false;
|
|
}
|
|
|
|
// Then check pending reveal queue (cards waiting to be placed)
|
|
CardData pendingCard = _pendingRevealCards.FirstOrDefault(c =>
|
|
c.DefinitionId == cardData.DefinitionId && c.Rarity == cardData.Rarity);
|
|
|
|
if (pendingCard != null)
|
|
{
|
|
// Return the actual pending card with its real CopiesOwned count
|
|
// Pending status is just about placement location, not copy count
|
|
existingCard = pendingCard;
|
|
return false; // Not new - already in pending queue
|
|
}
|
|
|
|
existingCard = null;
|
|
return true; // Truly new - not in inventory or pending
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a card to the player's inventory after reveal (delayed add)
|
|
/// Public wrapper for AddCardToInventory to support delayed inventory updates
|
|
/// </summary>
|
|
public void AddCardToInventoryDelayed(CardData card)
|
|
{
|
|
AddCardToInventory(card);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a card to the player's inventory, handles duplicates
|
|
/// Checks both inventory and pending lists to find existing cards
|
|
/// </summary>
|
|
private void AddCardToInventory(CardData card)
|
|
{
|
|
// Guard: Ensure card has at least 1 copy
|
|
if (card.CopiesOwned <= 0)
|
|
{
|
|
card.CopiesOwned = 1;
|
|
Logging.Warning($"[CardSystemManager] Card '{card.Name}' had {card.CopiesOwned} copies, setting to 1");
|
|
}
|
|
|
|
// First check inventory (cards already placed in album)
|
|
if (playerInventory.HasCard(card.DefinitionId, card.Rarity))
|
|
{
|
|
CardData existingCard = playerInventory.GetCard(card.DefinitionId, card.Rarity);
|
|
existingCard.CopiesOwned++;
|
|
|
|
Logging.Debug($"[CardSystemManager] Added duplicate card '{card.Name}' ({card.Rarity}) to INVENTORY. Now have {existingCard.CopiesOwned} copies.");
|
|
return;
|
|
}
|
|
|
|
// Then check pending reveal queue
|
|
CardData pendingCard = _pendingRevealCards.FirstOrDefault(c =>
|
|
c.DefinitionId == card.DefinitionId && c.Rarity == card.Rarity);
|
|
|
|
if (pendingCard != null)
|
|
{
|
|
// Card already in pending - increment its copy count
|
|
pendingCard.CopiesOwned++;
|
|
|
|
Logging.Debug($"[CardSystemManager] Added duplicate card '{card.Name}' ({card.Rarity}) to PENDING. Now have {pendingCard.CopiesOwned} copies pending.");
|
|
return;
|
|
}
|
|
|
|
// This is a NEW card (never owned at this rarity before)
|
|
// Add to pending reveal list instead of inventory
|
|
_pendingRevealCards.Add(card);
|
|
OnPendingCardAdded?.Invoke(card);
|
|
|
|
Logging.Debug($"[CardSystemManager] Added new card '{card.Name}' ({card.Rarity}) to pending reveal queue.");
|
|
}
|
|
|
|
/// <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()
|
|
{
|
|
// Weighted random for 3 rarities
|
|
float rand = UnityEngine.Random.value;
|
|
|
|
if (rand < 0.70f) return CardRarity.Normal; // 70% chance
|
|
if (rand < 0.95f) return CardRarity.Rare; // 25% chance
|
|
return CardRarity.Legendary; // 5% chance
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns all cards from the player's collection (both owned and pending)
|
|
/// </summary>
|
|
public List<CardData> GetAllCollectedCards()
|
|
{
|
|
List<CardData> allCards = new List<CardData>(playerInventory.GetAllCards());
|
|
allCards.AddRange(_pendingRevealCards);
|
|
return allCards;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns only owned/collected cards (excludes pending reveal cards)
|
|
/// </summary>
|
|
public List<CardData> GetCollectionOnly()
|
|
{
|
|
return new List<CardData>(playerInventory.GetAllCards());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns cards from a specific zone (both owned and pending)
|
|
/// </summary>
|
|
public List<CardData> GetCardsByZone(CardZone zone)
|
|
{
|
|
List<CardData> zoneCards = new List<CardData>(playerInventory.GetCardsByZone(zone));
|
|
zoneCards.AddRange(_pendingRevealCards.Where(c => c.Zone == zone));
|
|
return zoneCards;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns cards of a specific rarity (both owned and pending)
|
|
/// </summary>
|
|
public List<CardData> GetCardsByRarity(CardRarity rarity)
|
|
{
|
|
List<CardData> rarityCards = new List<CardData>(playerInventory.GetCardsByRarity(rarity));
|
|
rarityCards.AddRange(_pendingRevealCards.Where(c => c.Rarity == rarity));
|
|
return rarityCards;
|
|
}
|
|
|
|
/// <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 (at any rarity, in inventory or pending)
|
|
/// </summary>
|
|
public bool IsCardCollected(string definitionId)
|
|
{
|
|
// Check inventory at any rarity
|
|
foreach (CardRarity rarity in System.Enum.GetValues(typeof(CardRarity)))
|
|
{
|
|
if (playerInventory.HasCard(definitionId, rarity))
|
|
return true;
|
|
}
|
|
|
|
// Check pending reveal queue
|
|
if (_pendingRevealCards.Any(c => c.DefinitionId == definitionId))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets total unique card count (both owned and pending)
|
|
/// </summary>
|
|
public int GetUniqueCardCount()
|
|
{
|
|
int inventoryCount = playerInventory.GetUniqueCardCount();
|
|
|
|
// Count unique cards in pending that aren't already in inventory
|
|
int pendingUniqueCount = _pendingRevealCards
|
|
.Select(c => new { c.DefinitionId, c.Rarity })
|
|
.Distinct()
|
|
.Count(pc => !playerInventory.HasCard(pc.DefinitionId, pc.Rarity));
|
|
|
|
return inventoryCount + pendingUniqueCount;
|
|
}
|
|
|
|
/// <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 (both owned and pending)
|
|
/// </summary>
|
|
public int GetCardCountByRarity(CardRarity rarity)
|
|
{
|
|
int inventoryCount = playerInventory.GetCardsByRarity(rarity).Count;
|
|
int pendingCount = _pendingRevealCards.Count(c => c.Rarity == rarity);
|
|
return inventoryCount + pendingCount;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the count of cards by zone (both owned and pending)
|
|
/// </summary>
|
|
public int GetCardCountByZone(CardZone zone)
|
|
{
|
|
int inventoryCount = playerInventory.GetCardsByZone(zone).Count;
|
|
int pendingCount = _pendingRevealCards.Count(c => c.Zone == zone);
|
|
return inventoryCount + pendingCount;
|
|
}
|
|
|
|
/// <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;
|
|
}
|
|
|
|
#region Album System
|
|
|
|
/// <summary>
|
|
/// Returns all pending reveal cards (cards waiting to be placed in album)
|
|
/// </summary>
|
|
public List<CardData> GetPendingRevealCards()
|
|
{
|
|
return _pendingRevealCards;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove a card from the pending reveal list and fire event.
|
|
/// Called when a card starts being dragged to album slot.
|
|
/// </summary>
|
|
public bool RemoveFromPending(CardData card)
|
|
{
|
|
if (card == null) return false;
|
|
|
|
bool removed = _pendingRevealCards.Remove(card);
|
|
if (removed)
|
|
{
|
|
OnPendingCardRemoved?.Invoke(card);
|
|
Logging.Debug($"[CardSystemManager] Removed '{card.Name}' from pending reveal cards");
|
|
}
|
|
return removed;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get a card by definition ID and rarity from either inventory or pending
|
|
/// Returns the actual card reference so changes persist
|
|
/// </summary>
|
|
/// <param name="definitionId">Card definition ID</param>
|
|
/// <param name="rarity">Card rarity</param>
|
|
/// <param name="isFromPending">Out parameter - true if card is from pending, false if from inventory</param>
|
|
/// <returns>The card data if found, null otherwise</returns>
|
|
public CardData GetCard(string definitionId, CardRarity rarity, out bool isFromPending)
|
|
{
|
|
// Check inventory first
|
|
if (playerInventory.HasCard(definitionId, rarity))
|
|
{
|
|
isFromPending = false;
|
|
return playerInventory.GetCard(definitionId, rarity);
|
|
}
|
|
|
|
// Check pending
|
|
CardData pendingCard = _pendingRevealCards.FirstOrDefault(c =>
|
|
c.DefinitionId == definitionId && c.Rarity == rarity);
|
|
|
|
if (pendingCard != null)
|
|
{
|
|
isFromPending = true;
|
|
return pendingCard;
|
|
}
|
|
|
|
isFromPending = false;
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get a card by definition ID and rarity from either inventory or pending (simplified overload)
|
|
/// </summary>
|
|
public CardData GetCard(string definitionId, CardRarity rarity)
|
|
{
|
|
return GetCard(definitionId, rarity, out _);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update a card's data in whichever list it's in (inventory or pending)
|
|
/// Useful for incrementing CopiesOwned, upgrading rarity, etc.
|
|
/// </summary>
|
|
/// <param name="definitionId">Card definition ID</param>
|
|
/// <param name="rarity">Card rarity</param>
|
|
/// <param name="updateAction">Action to perform on the card</param>
|
|
/// <returns>True if card was found and updated, false otherwise</returns>
|
|
public bool UpdateCard(string definitionId, CardRarity rarity, System.Action<CardData> updateAction)
|
|
{
|
|
CardData card = GetCard(definitionId, rarity, out bool isFromPending);
|
|
|
|
if (card != null)
|
|
{
|
|
updateAction?.Invoke(card);
|
|
Logging.Debug($"[CardSystemManager] Updated card '{card.Name}' in {(isFromPending ? "pending" : "inventory")}");
|
|
return true;
|
|
}
|
|
|
|
Logging.Warning($"[CardSystemManager] Could not find card with ID '{definitionId}' and rarity '{rarity}' to update");
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Marks a card as placed in the album
|
|
/// Adds card to owned inventory and tracks as placed
|
|
/// Note: Card may have already been removed from pending list during drag
|
|
/// </summary>
|
|
public void MarkCardAsPlaced(CardData card)
|
|
{
|
|
if (card == null)
|
|
{
|
|
Logging.Warning("[CardSystemManager] Attempted to place null card");
|
|
return;
|
|
}
|
|
|
|
// Try to remove from pending (may already be removed during drag)
|
|
bool wasInPending = _pendingRevealCards.Remove(card);
|
|
|
|
// Add to owned inventory (regardless of whether it was in pending)
|
|
playerInventory.AddCard(card);
|
|
|
|
// Track as placed
|
|
_placedInAlbumCardIds.Add(card.Id);
|
|
|
|
// Fire events
|
|
OnCardPlacedInAlbum?.Invoke(card);
|
|
OnCardCollected?.Invoke(card);
|
|
|
|
if (wasInPending)
|
|
{
|
|
Logging.Debug($"[CardSystemManager] Card '{card.Name}' removed from pending and added to inventory");
|
|
}
|
|
else
|
|
{
|
|
Logging.Debug($"[CardSystemManager] Card '{card.Name}' added to inventory (was already removed from pending)");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if a card has been placed in the album
|
|
/// </summary>
|
|
public bool IsCardPlacedInAlbum(string cardId)
|
|
{
|
|
return _placedInAlbumCardIds.Contains(cardId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets count of cards waiting to be revealed
|
|
/// </summary>
|
|
public int GetPendingRevealCount()
|
|
{
|
|
return _pendingRevealCards.Count;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a random card definition of the specified rarity.
|
|
/// Used by minigames to spawn cards without affecting player's collection.
|
|
/// </summary>
|
|
public CardDefinition GetRandomCardDefinitionByRarity(CardRarity targetRarity)
|
|
{
|
|
// Filter available cards by rarity
|
|
var matchingCards = availableCards.Where(c => c.Rarity == targetRarity).ToList();
|
|
|
|
if (matchingCards.Count == 0)
|
|
{
|
|
Debug.LogWarning($"[CardSystemManager] No card definitions found for rarity {targetRarity}");
|
|
return null;
|
|
}
|
|
|
|
// Return random card from matching rarity
|
|
return matchingCards[UnityEngine.Random.Range(0, matchingCards.Count)];
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Export current card collection to a serializable snapshot
|
|
/// </summary>
|
|
public CardCollectionState ExportCardCollectionState()
|
|
{
|
|
var state = new CardCollectionState
|
|
{
|
|
boosterPackCount = playerInventory.BoosterPackCount,
|
|
cards = new List<SavedCardEntry>(),
|
|
pendingRevealCards = new List<SavedCardEntry>(),
|
|
placedInAlbumCardIds = new List<string>(_placedInAlbumCardIds)
|
|
};
|
|
|
|
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
|
|
});
|
|
}
|
|
|
|
foreach (var card in _pendingRevealCards)
|
|
{
|
|
if (string.IsNullOrEmpty(card.DefinitionId)) continue;
|
|
state.pendingRevealCards.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 async void ApplyCardCollectionState(CardCollectionState state)
|
|
{
|
|
if (state == null) return;
|
|
|
|
// Wait for lookup to be initialized before loading
|
|
while (!_lookupInitialized)
|
|
{
|
|
await System.Threading.Tasks.Task.Yield();
|
|
}
|
|
|
|
playerInventory.ClearAllCards();
|
|
_pendingRevealCards.Clear();
|
|
_placedInAlbumCardIds.Clear();
|
|
|
|
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}");
|
|
}
|
|
}
|
|
|
|
// Restore pending reveal cards
|
|
if (state.pendingRevealCards != null)
|
|
{
|
|
foreach (var entry in state.pendingRevealCards)
|
|
{
|
|
if (string.IsNullOrEmpty(entry.definitionId)) continue;
|
|
if (_definitionLookup.TryGetValue(entry.definitionId, out var def))
|
|
{
|
|
var cd = def.CreateCardData();
|
|
cd.Rarity = entry.rarity;
|
|
cd.CopiesOwned = entry.copiesOwned;
|
|
_pendingRevealCards.Add(cd);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Restore placed in album tracking
|
|
if (state.placedInAlbumCardIds != null)
|
|
{
|
|
foreach (var cardId in state.placedInAlbumCardIds)
|
|
{
|
|
_placedInAlbumCardIds.Add(cardId);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears all card collection data - inventory, pending cards, boosters, and placement tracking
|
|
/// Used for dev reset functionality
|
|
/// </summary>
|
|
public void ClearAllCollectionData()
|
|
{
|
|
playerInventory.ClearAllCards();
|
|
playerInventory.BoosterPackCount = 0;
|
|
_pendingRevealCards.Clear();
|
|
_placedInAlbumCardIds.Clear();
|
|
|
|
OnBoosterCountChanged?.Invoke(0);
|
|
|
|
Logging.Debug("[CardSystemManager] Cleared all collection data (inventory, boosters, pending, placement tracking)");
|
|
}
|
|
|
|
#region Save/Load Lifecycle Hooks
|
|
|
|
internal override string OnGlobalSaveRequested()
|
|
{
|
|
var state = ExportCardCollectionState();
|
|
return JsonUtility.ToJson(state);
|
|
}
|
|
|
|
internal 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
|
|
}
|
|
}
|