Slotting cards in album after revealing

This commit is contained in:
Michal Pikulski
2025-11-07 01:51:03 +01:00
parent debe70c9b1
commit 3e607f3857
20 changed files with 2986 additions and 1459 deletions

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AppleHills.Data.CardSystem;
using Bootstrap;
using Core;
@@ -23,6 +24,10 @@ namespace Data.CardSystem
// 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 = new Dictionary<string, CardDefinition>();
@@ -32,6 +37,8 @@ namespace Data.CardSystem
public event Action<CardData> OnCardCollected;
public event Action<CardData> OnCardRarityUpgraded;
public event Action<int> OnBoosterCountChanged;
public event Action<CardData> OnPendingCardAdded;
public event Action<CardData> OnCardPlacedInAlbum;
private void Awake()
{
@@ -175,19 +182,34 @@ namespace Data.CardSystem
/// <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</returns>
/// <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;
return true; // Truly new - not in inventory or pending
}
/// <summary>
@@ -201,28 +223,46 @@ namespace Data.CardSystem
/// <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)
{
// Check if the player already has this card at this rarity
// 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++;
// Note: Upgrades are now handled separately in BoosterOpeningPage
// We don't auto-upgrade here anymore
Logging.Debug($"[CardSystemManager] Added duplicate card '{card.Name}' ({card.Rarity}). Now have {existingCard.CopiesOwned} copies.");
Logging.Debug($"[CardSystemManager] Added duplicate card '{card.Name}' ({card.Rarity}) to INVENTORY. Now have {existingCard.CopiesOwned} copies.");
return;
}
else
// Then check pending reveal queue
CardData pendingCard = _pendingRevealCards.FirstOrDefault(c =>
c.DefinitionId == card.DefinitionId && c.Rarity == card.Rarity);
if (pendingCard != null)
{
// Add new card at this rarity
playerInventory.AddCard(card);
OnCardCollected?.Invoke(card);
// Card already in pending - increment its copy count
pendingCard.CopiesOwned++;
Logging.Debug($"[CardSystemManager] Added new card '{card.Name}' ({card.Rarity}) to collection.");
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>
@@ -286,27 +326,33 @@ namespace Data.CardSystem
}
/// <summary>
/// Returns all cards from the player's collection
/// Returns all cards from the player's collection (both owned and pending)
/// </summary>
public List<CardData> GetAllCollectedCards()
{
return playerInventory.GetAllCards();
List<CardData> allCards = new List<CardData>(playerInventory.GetAllCards());
allCards.AddRange(_pendingRevealCards);
return allCards;
}
/// <summary>
/// Returns cards from a specific zone
/// Returns cards from a specific zone (both owned and pending)
/// </summary>
public List<CardData> GetCardsByZone(CardZone zone)
{
return playerInventory.GetCardsByZone(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
/// Returns cards of a specific rarity (both owned and pending)
/// </summary>
public List<CardData> GetCardsByRarity(CardRarity rarity)
{
return playerInventory.GetCardsByRarity(rarity);
List<CardData> rarityCards = new List<CardData>(playerInventory.GetCardsByRarity(rarity));
rarityCards.AddRange(_pendingRevealCards.Where(c => c.Rarity == rarity));
return rarityCards;
}
/// <summary>
@@ -318,25 +364,38 @@ namespace Data.CardSystem
}
/// <summary>
/// Returns whether a specific card definition has been collected (at any rarity)
/// Returns whether a specific card definition has been collected (at any rarity, in inventory or pending)
/// </summary>
public bool IsCardCollected(string definitionId)
{
// Check if the card exists at any rarity
// 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
/// Gets total unique card count (both owned and pending)
/// </summary>
public int GetUniqueCardCount()
{
return playerInventory.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>
@@ -381,19 +440,23 @@ namespace Data.CardSystem
}
/// <summary>
/// Returns the count of cards by rarity
/// Returns the count of cards by rarity (both owned and pending)
/// </summary>
public int GetCardCountByRarity(CardRarity rarity)
{
return playerInventory.GetCardsByRarity(rarity).Count;
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
/// Returns the count of cards by zone (both owned and pending)
/// </summary>
public int GetCardCountByZone(CardZone zone)
{
return playerInventory.GetCardsByZone(zone).Count;
int inventoryCount = playerInventory.GetCardsByZone(zone).Count;
int pendingCount = _pendingRevealCards.Count(c => c.Zone == zone);
return inventoryCount + pendingCount;
}
/// <summary>
@@ -428,6 +491,121 @@ namespace Data.CardSystem
return (float)collectedOfRarity / totalOfRarity * 100f;
}
#region Album System
/// <summary>
/// Returns all cards waiting to be placed in the album
/// </summary>
public List<CardData> GetPendingRevealCards()
{
return new List<CardData>(_pendingRevealCards);
}
/// <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
/// Moves it from pending reveal to owned inventory
/// </summary>
public void MarkCardAsPlaced(CardData card)
{
if (_pendingRevealCards.Remove(card))
{
// Add to owned inventory
playerInventory.AddCard(card);
// Track as placed
_placedInAlbumCardIds.Add(card.Id);
OnCardPlacedInAlbum?.Invoke(card);
OnCardCollected?.Invoke(card);
Logging.Debug($"[CardSystemManager] Card '{card.Name}' placed in album and added to inventory.");
}
else
{
Logging.Warning($"[CardSystemManager] Attempted to place card '{card.Name}' but it wasn't in pending reveal list.");
}
}
/// <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;
}
#endregion
/// <summary>
/// Export current card collection to a serializable snapshot
/// </summary>
@@ -436,7 +614,9 @@ namespace Data.CardSystem
var state = new CardCollectionState
{
boosterPackCount = playerInventory.BoosterPackCount,
cards = new List<SavedCardEntry>()
cards = new List<SavedCardEntry>(),
pendingRevealCards = new List<SavedCardEntry>(),
placedInAlbumCardIds = new List<string>(_placedInAlbumCardIds)
};
foreach (var card in playerInventory.CollectedCards.Values)
@@ -449,6 +629,18 @@ namespace Data.CardSystem
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;
}
@@ -460,6 +652,9 @@ namespace Data.CardSystem
if (state == null) return;
playerInventory.ClearAllCards();
_pendingRevealCards.Clear();
_placedInAlbumCardIds.Clear();
playerInventory.BoosterPackCount = state.boosterPackCount;
OnBoosterCountChanged?.Invoke(playerInventory.BoosterPackCount);
@@ -479,6 +674,31 @@ namespace Data.CardSystem
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);
}
}
}
#region ISaveParticipant Implementation