Refactor, cleanup code and add documentaiton
This commit is contained in:
28
Assets/Scripts/CardSystem/Data/CardCollectionState.cs
Normal file
28
Assets/Scripts/CardSystem/Data/CardCollectionState.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System.Collections.Generic;
|
||||
using AppleHills.Data.CardSystem;
|
||||
|
||||
namespace Data.CardSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Serializable snapshot of the card collection state for save/load operations.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class CardCollectionState
|
||||
{
|
||||
public int boosterPackCount;
|
||||
public List<SavedCardEntry> cards = new List<SavedCardEntry>();
|
||||
public List<SavedCardEntry> pendingRevealCards = new List<SavedCardEntry>();
|
||||
public List<string> placedInAlbumCardIds = new List<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializable representation of a single card's saved data.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class SavedCardEntry
|
||||
{
|
||||
public string definitionId;
|
||||
public CardRarity rarity;
|
||||
public int copiesOwned;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e552abbd5bd74192840939e499372ff2
|
||||
timeCreated: 1761830599
|
||||
106
Assets/Scripts/CardSystem/Data/CardData.cs
Normal file
106
Assets/Scripts/CardSystem/Data/CardData.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AppleHills.Data.CardSystem
|
||||
{
|
||||
[Serializable]
|
||||
public class CardData
|
||||
{
|
||||
// Core data (serialized)
|
||||
public string Id; // Auto-generated unique ID (GUID)
|
||||
public string DefinitionId; // ID of the card definition this instance was created from
|
||||
public CardRarity Rarity; // Current rarity (may be upgraded from original)
|
||||
public int CopiesOwned; // Number of copies the player has (for stacking)
|
||||
|
||||
// Reference back to the definition (not serialized)
|
||||
[NonSerialized]
|
||||
private CardDefinition _definition;
|
||||
|
||||
// Properties that reference definition data
|
||||
public string Name => _definition?.Name;
|
||||
public string Description => _definition?.Description;
|
||||
public CardZone Zone => _definition?.Zone ?? CardZone.AppleHills;
|
||||
public int CollectionIndex => _definition?.CollectionIndex ?? 0;
|
||||
public Sprite CardImage => _definition?.CardImage;
|
||||
|
||||
// Default constructor
|
||||
public CardData()
|
||||
{
|
||||
Id = Guid.NewGuid().ToString();
|
||||
CopiesOwned = 0;
|
||||
}
|
||||
|
||||
// Constructor from definition
|
||||
public CardData(CardDefinition definition)
|
||||
{
|
||||
Id = Guid.NewGuid().ToString();
|
||||
DefinitionId = definition.Id;
|
||||
Rarity = definition.Rarity;
|
||||
CopiesOwned = 1;
|
||||
_definition = definition;
|
||||
}
|
||||
|
||||
// Copy constructor
|
||||
public CardData(CardData other)
|
||||
{
|
||||
Id = other.Id;
|
||||
DefinitionId = other.DefinitionId;
|
||||
Rarity = other.Rarity;
|
||||
CopiesOwned = other.CopiesOwned;
|
||||
_definition = other._definition;
|
||||
}
|
||||
|
||||
// Method to link this card data to its definition
|
||||
public void SetDefinition(CardDefinition definition)
|
||||
{
|
||||
if (definition != null)
|
||||
{
|
||||
_definition = definition;
|
||||
DefinitionId = definition.Id;
|
||||
}
|
||||
}
|
||||
|
||||
// Method to upgrade rarity when enough copies are collected
|
||||
public bool TryUpgradeRarity()
|
||||
{
|
||||
// Simple implementation - each rarity needs twice as many copies to upgrade
|
||||
int requiredCopies = (int)Rarity * 2 + 1;
|
||||
|
||||
if (CopiesOwned >= requiredCopies && Rarity < CardRarity.Legendary)
|
||||
{
|
||||
Rarity += 1;
|
||||
CopiesOwned -= requiredCopies;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// ToString method for debugging
|
||||
public override string ToString()
|
||||
{
|
||||
return $"CardData [ID: {Id}, Name: {Name}, Rarity: {Rarity}, Zone: {Zone}, " +
|
||||
$"DefinitionID: {DefinitionId}, Copies: {CopiesOwned}, " +
|
||||
$"Has Definition: {_definition != null}, Has Image: {CardImage != null}]";
|
||||
}
|
||||
}
|
||||
|
||||
// Enums for card attributes
|
||||
public enum CardRarity
|
||||
{
|
||||
Normal = 0,
|
||||
Rare = 1,
|
||||
Legendary = 2
|
||||
}
|
||||
|
||||
public enum CardZone
|
||||
{
|
||||
NotApplicable,
|
||||
AppleHills,
|
||||
Quarry,
|
||||
CementFactory,
|
||||
CentralStreet,
|
||||
Valentine,
|
||||
Dump
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/CardSystem/Data/CardData.cs.meta
Normal file
3
Assets/Scripts/CardSystem/Data/CardData.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 812f681e555841c584d5791cb66278de
|
||||
timeCreated: 1759923654
|
||||
59
Assets/Scripts/CardSystem/Data/CardDefinition.cs
Normal file
59
Assets/Scripts/CardSystem/Data/CardDefinition.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AppleHills.Data.CardSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Scriptable object defining a collectible card's properties.
|
||||
/// Used as a template for generating CardData instances.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "New Card", menuName = "AppleHills/Card System/Card Definition")]
|
||||
public class CardDefinition : ScriptableObject
|
||||
{
|
||||
[Header("Identification")]
|
||||
[Tooltip("Unique identifier for this card definition")]
|
||||
public string Id = Guid.NewGuid().ToString();
|
||||
|
||||
[Header("Basic Info")]
|
||||
public string Name;
|
||||
|
||||
[Tooltip("Use a custom file name instead of the card name")]
|
||||
public bool UseCustomFileName = false;
|
||||
|
||||
[Tooltip("Custom file name (only used if UseCustomFileName is true)")]
|
||||
public string CustomFileName = "";
|
||||
|
||||
[TextArea(3, 5)]
|
||||
public string Description;
|
||||
public CardRarity Rarity;
|
||||
public CardZone Zone;
|
||||
|
||||
[Header("Visual Elements")]
|
||||
public Sprite CardImage; // The actual card image
|
||||
|
||||
[Header("Collection Info")]
|
||||
public int CollectionIndex; // Position in the album
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new CardData instance from this definition
|
||||
/// </summary>
|
||||
public CardData CreateCardData()
|
||||
{
|
||||
return new CardData(this);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is CardDefinition other)
|
||||
{
|
||||
return string.Equals(Id, other.Id, StringComparison.Ordinal);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Id != null ? Id.GetHashCode() : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/CardSystem/Data/CardDefinition.cs.meta
Normal file
3
Assets/Scripts/CardSystem/Data/CardDefinition.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2a80cc88c9884512b8b633110d838780
|
||||
timeCreated: 1759923702
|
||||
235
Assets/Scripts/CardSystem/Data/CardInventory.cs
Normal file
235
Assets/Scripts/CardSystem/Data/CardInventory.cs
Normal file
@@ -0,0 +1,235 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AppleHills.Data.CardSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages the player's collection of cards and booster packs
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class CardInventory
|
||||
{
|
||||
// Dictionary of collected cards indexed by definition ID + rarity (e.g., "Pikachu_Normal", "Pikachu_Rare")
|
||||
[SerializeField] private Dictionary<string, CardData> collectedCards = new Dictionary<string, CardData>();
|
||||
|
||||
/// <summary>
|
||||
/// Generate a unique key for a card based on definition ID and rarity
|
||||
/// </summary>
|
||||
private string GetCardKey(string definitionId, CardRarity rarity)
|
||||
{
|
||||
return $"{definitionId}_{rarity}";
|
||||
}
|
||||
|
||||
// Number of unopened booster packs the player has
|
||||
[SerializeField] private int boosterPackCount;
|
||||
|
||||
// Additional lookup dictionaries (not serialized)
|
||||
[NonSerialized] private Dictionary<CardZone, List<CardData>> cardsByZone = new Dictionary<CardZone, List<CardData>>();
|
||||
[NonSerialized] private Dictionary<CardRarity, List<CardData>> cardsByRarity = new Dictionary<CardRarity, List<CardData>>();
|
||||
|
||||
// Properties with public getters
|
||||
public Dictionary<string, CardData> CollectedCards => collectedCards;
|
||||
|
||||
public int BoosterPackCount
|
||||
{
|
||||
get => boosterPackCount;
|
||||
set => boosterPackCount = value;
|
||||
}
|
||||
|
||||
// Constructor initializes empty dictionaries
|
||||
public CardInventory()
|
||||
{
|
||||
// Initialize dictionaries for all enum values so we never have to check for null
|
||||
foreach (CardZone zone in Enum.GetValues(typeof(CardZone)))
|
||||
{
|
||||
cardsByZone[zone] = new List<CardData>();
|
||||
}
|
||||
|
||||
foreach (CardRarity rarity in Enum.GetValues(typeof(CardRarity)))
|
||||
{
|
||||
cardsByRarity[rarity] = new List<CardData>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all cards in the player's collection as a list
|
||||
/// </summary>
|
||||
public List<CardData> GetAllCards()
|
||||
{
|
||||
return collectedCards.Values.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all cards from the inventory
|
||||
/// Primarily used for testing
|
||||
/// </summary>
|
||||
public void ClearAllCards()
|
||||
{
|
||||
collectedCards.Clear();
|
||||
|
||||
// Clear lookup dictionaries
|
||||
foreach (var zone in cardsByZone.Keys)
|
||||
{
|
||||
cardsByZone[zone].Clear();
|
||||
}
|
||||
|
||||
foreach (var rarity in cardsByRarity.Keys)
|
||||
{
|
||||
cardsByRarity[rarity].Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get cards filtered by zone
|
||||
/// </summary>
|
||||
public List<CardData> GetCardsByZone(CardZone zone)
|
||||
{
|
||||
return new List<CardData>(cardsByZone[zone]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get cards filtered by rarity
|
||||
/// </summary>
|
||||
public List<CardData> GetCardsByRarity(CardRarity rarity)
|
||||
{
|
||||
return new List<CardData>(cardsByRarity[rarity]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a card to the inventory (or increase the copies if already owned)
|
||||
/// </summary>
|
||||
public void AddCard(CardData card)
|
||||
{
|
||||
if (card == null) return;
|
||||
|
||||
string key = GetCardKey(card.DefinitionId, card.Rarity);
|
||||
|
||||
if (collectedCards.TryGetValue(key, out CardData existingCard))
|
||||
{
|
||||
// Increase copies of existing card
|
||||
existingCard.CopiesOwned++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add new card to collection
|
||||
var newCard = new CardData(card);
|
||||
collectedCards[key] = newCard;
|
||||
|
||||
// Add to lookup dictionaries
|
||||
cardsByZone[newCard.Zone].Add(newCard);
|
||||
cardsByRarity[newCard.Rarity].Add(newCard);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update card zone and rarity indexes when a card changes
|
||||
/// </summary>
|
||||
public void UpdateCardProperties(CardData card, CardZone oldZone, CardRarity oldRarity)
|
||||
{
|
||||
// Only needed if the card's zone or rarity actually changed
|
||||
if (oldZone != card.Zone)
|
||||
{
|
||||
cardsByZone[oldZone].Remove(card);
|
||||
cardsByZone[card.Zone].Add(card);
|
||||
}
|
||||
|
||||
if (oldRarity != card.Rarity)
|
||||
{
|
||||
cardsByRarity[oldRarity].Remove(card);
|
||||
cardsByRarity[card.Rarity].Add(card);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a specific card from the collection by definition ID and rarity
|
||||
/// </summary>
|
||||
public CardData GetCard(string definitionId, CardRarity rarity)
|
||||
{
|
||||
string key = GetCardKey(definitionId, rarity);
|
||||
return collectedCards.TryGetValue(key, out CardData card) ? card : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the player has a specific card at a specific rarity
|
||||
/// </summary>
|
||||
public bool HasCard(string definitionId, CardRarity rarity)
|
||||
{
|
||||
string key = GetCardKey(definitionId, rarity);
|
||||
return collectedCards.ContainsKey(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get total number of unique cards in collection
|
||||
/// </summary>
|
||||
public int GetUniqueCardCount()
|
||||
{
|
||||
return collectedCards.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get total number of cards including copies
|
||||
/// </summary>
|
||||
public int GetTotalCardCount()
|
||||
{
|
||||
return collectedCards.Values.Sum(card => card.CopiesOwned);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get number of cards in a specific zone
|
||||
/// </summary>
|
||||
public int GetZoneCardCount(CardZone zone)
|
||||
{
|
||||
return cardsByZone[zone].Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get number of cards of a specific rarity
|
||||
/// </summary>
|
||||
public int GetRarityCardCount(CardRarity rarity)
|
||||
{
|
||||
return cardsByRarity[rarity].Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get cards sorted by collection index (for album view)
|
||||
/// </summary>
|
||||
public List<CardData> GetCardsSortedByIndex()
|
||||
{
|
||||
return collectedCards.Values
|
||||
.OrderBy(card => card.CollectionIndex)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if there's a complete collection for a specific zone
|
||||
/// </summary>
|
||||
public bool IsZoneCollectionComplete(CardZone zone, List<CardDefinition> allAvailableCards)
|
||||
{
|
||||
int availableInZone = allAvailableCards.Count(card => card.Zone == zone);
|
||||
int collectedInZone = cardsByZone[zone].Count;
|
||||
|
||||
return availableInZone > 0 && collectedInZone >= availableInZone;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds booster packs to the inventory
|
||||
/// </summary>
|
||||
public void AddBoosterPacks(int count)
|
||||
{
|
||||
boosterPackCount += count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use a single booster pack (returns true if successful)
|
||||
/// </summary>
|
||||
public bool UseBoosterPack()
|
||||
{
|
||||
if (boosterPackCount <= 0) return false;
|
||||
|
||||
boosterPackCount--;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/CardSystem/Data/CardInventory.cs.meta
Normal file
3
Assets/Scripts/CardSystem/Data/CardInventory.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f5b1aa91590d48a1a4c426f3cd4aa103
|
||||
timeCreated: 1760445622
|
||||
790
Assets/Scripts/CardSystem/Data/CardSystemManager.cs
Normal file
790
Assets/Scripts/CardSystem/Data/CardSystemManager.cs
Normal file
@@ -0,0 +1,790 @@
|
||||
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;
|
||||
}
|
||||
|
||||
#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
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/CardSystem/Data/CardSystemManager.cs.meta
Normal file
3
Assets/Scripts/CardSystem/Data/CardSystemManager.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8d80347e4bd04c87be23a9399860783d
|
||||
timeCreated: 1759923691
|
||||
206
Assets/Scripts/CardSystem/Data/CardVisualConfig.cs
Normal file
206
Assets/Scripts/CardSystem/Data/CardVisualConfig.cs
Normal file
@@ -0,0 +1,206 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Core;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AppleHills.Data.CardSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// ScriptableObject containing visual configuration for card display
|
||||
/// Maps card rarities to frames/overlays and zones to backgrounds/shapes
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "CardVisualConfig", menuName = "AppleHills/Card System/Visual Config")]
|
||||
public class CardVisualConfig : ScriptableObject
|
||||
{
|
||||
[Serializable]
|
||||
public class RarityVisualMapping
|
||||
{
|
||||
public CardRarity rarity;
|
||||
[Tooltip("Frame image for this rarity")]
|
||||
public Sprite frame;
|
||||
[Tooltip("Overlay image for this rarity (can be null)")]
|
||||
public Sprite overlay;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class ZoneVisualMapping
|
||||
{
|
||||
public CardZone zone;
|
||||
[Tooltip("Background image for this zone")]
|
||||
public Sprite background;
|
||||
[Tooltip("Shape sprite for Normal rarity cards in this zone")]
|
||||
public Sprite shapeNormal;
|
||||
[Tooltip("Shape sprite for Rare rarity cards in this zone")]
|
||||
public Sprite shapeRare;
|
||||
}
|
||||
|
||||
[Header("Rarity Configuration")]
|
||||
[Tooltip("Visual mappings for different card rarities (frames and overlays)")]
|
||||
[SerializeField] private List<RarityVisualMapping> rarityVisuals = new List<RarityVisualMapping>();
|
||||
|
||||
[Header("Zone Configuration")]
|
||||
[Tooltip("Visual mappings for different card zones (backgrounds and shapes)")]
|
||||
[SerializeField] private List<ZoneVisualMapping> zoneVisuals = new List<ZoneVisualMapping>();
|
||||
|
||||
[Header("Legendary Override")]
|
||||
[Tooltip("Background used for all Legendary cards, regardless of zone")]
|
||||
[SerializeField] private Sprite legendaryBackground;
|
||||
|
||||
private Dictionary<CardRarity, RarityVisualMapping> _rarityLookup;
|
||||
private Dictionary<CardZone, ZoneVisualMapping> _zoneLookup;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the lookup dictionaries when the asset is loaded
|
||||
/// </summary>
|
||||
private void OnEnable()
|
||||
{
|
||||
InitializeLookups();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the lookup dictionaries from the serialized lists
|
||||
/// </summary>
|
||||
private void InitializeLookups()
|
||||
{
|
||||
// Build rarity visual lookup
|
||||
_rarityLookup = new Dictionary<CardRarity, RarityVisualMapping>();
|
||||
foreach (var mapping in rarityVisuals)
|
||||
{
|
||||
_rarityLookup[mapping.rarity] = mapping;
|
||||
}
|
||||
|
||||
// Build zone visual lookup
|
||||
_zoneLookup = new Dictionary<CardZone, ZoneVisualMapping>();
|
||||
foreach (var mapping in zoneVisuals)
|
||||
{
|
||||
_zoneLookup[mapping.zone] = mapping;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the frame sprite for a specific card rarity
|
||||
/// </summary>
|
||||
public Sprite GetRarityFrame(CardRarity rarity)
|
||||
{
|
||||
if (_rarityLookup == null) InitializeLookups();
|
||||
|
||||
if (_rarityLookup.TryGetValue(rarity, out RarityVisualMapping mapping))
|
||||
{
|
||||
return mapping.frame;
|
||||
}
|
||||
|
||||
Logging.Warning($"[CardVisualConfig] No frame mapping found for rarity {rarity}");
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the overlay sprite for a specific card rarity (can be null)
|
||||
/// </summary>
|
||||
public Sprite GetRarityOverlay(CardRarity rarity)
|
||||
{
|
||||
if (_rarityLookup == null) InitializeLookups();
|
||||
|
||||
if (_rarityLookup.TryGetValue(rarity, out RarityVisualMapping mapping))
|
||||
{
|
||||
return mapping.overlay;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the background sprite for a card based on zone and rarity
|
||||
/// Legendary cards always use the legendary background override
|
||||
/// </summary>
|
||||
public Sprite GetBackground(CardZone zone, CardRarity rarity)
|
||||
{
|
||||
if (_zoneLookup == null) InitializeLookups();
|
||||
|
||||
// Legendary cards use special background
|
||||
if (rarity == CardRarity.Legendary)
|
||||
{
|
||||
return legendaryBackground;
|
||||
}
|
||||
|
||||
// Normal and Rare cards use zone background
|
||||
if (_zoneLookup.TryGetValue(zone, out ZoneVisualMapping mapping))
|
||||
{
|
||||
return mapping.background;
|
||||
}
|
||||
|
||||
Logging.Warning($"[CardVisualConfig] No background mapping found for zone {zone}");
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the shape sprite for a card based on zone and rarity
|
||||
/// Legendary cards don't display shapes (returns null)
|
||||
/// </summary>
|
||||
public Sprite GetZoneShape(CardZone zone, CardRarity rarity)
|
||||
{
|
||||
if (_zoneLookup == null) InitializeLookups();
|
||||
|
||||
// Legendary cards don't have shapes
|
||||
if (rarity == CardRarity.Legendary)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_zoneLookup.TryGetValue(zone, out ZoneVisualMapping mapping))
|
||||
{
|
||||
// Return shape based on rarity
|
||||
return rarity == CardRarity.Rare ? mapping.shapeRare : mapping.shapeNormal;
|
||||
}
|
||||
|
||||
Logging.Warning($"[CardVisualConfig] No shape mapping found for zone {zone}");
|
||||
return null;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// Editor-only utility to initialize the config with default structure
|
||||
/// </summary>
|
||||
public void InitializeDefaults()
|
||||
{
|
||||
// Clear existing mappings
|
||||
rarityVisuals.Clear();
|
||||
zoneVisuals.Clear();
|
||||
|
||||
// Add entries for all rarities
|
||||
foreach (CardRarity rarity in Enum.GetValues(typeof(CardRarity)))
|
||||
{
|
||||
rarityVisuals.Add(new RarityVisualMapping { rarity = rarity });
|
||||
}
|
||||
|
||||
// Add entries for all zones
|
||||
foreach (CardZone zone in Enum.GetValues(typeof(CardZone)))
|
||||
{
|
||||
zoneVisuals.Add(new ZoneVisualMapping { zone = zone });
|
||||
}
|
||||
|
||||
// Initialize the lookups
|
||||
InitializeLookups();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[UnityEditor.CustomEditor(typeof(CardVisualConfig))]
|
||||
public class CardVisualConfigEditor : UnityEditor.Editor
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
DrawDefaultInspector();
|
||||
|
||||
CardVisualConfig config = (CardVisualConfig)target;
|
||||
|
||||
UnityEditor.EditorGUILayout.Space();
|
||||
if (GUILayout.Button("Initialize Default Structure"))
|
||||
{
|
||||
config.InitializeDefaults();
|
||||
UnityEditor.EditorUtility.SetDirty(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
3
Assets/Scripts/CardSystem/Data/CardVisualConfig.cs.meta
Normal file
3
Assets/Scripts/CardSystem/Data/CardVisualConfig.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a82f88f485b4410e9eb7c383b44557cf
|
||||
timeCreated: 1759931508
|
||||
Reference in New Issue
Block a user