2025-11-07 17:49:43 +01:00
using System ;
2025-10-10 14:31:51 +02:00
using System.Collections.Generic ;
2025-11-07 01:51:03 +01:00
using System.Linq ;
2025-10-14 14:57:50 +02:00
using AppleHills.Data.CardSystem ;
2025-10-14 15:53:58 +02:00
using Core ;
2025-11-07 15:38:31 +00:00
using Core.Lifecycle ;
2025-10-10 14:31:51 +02:00
using UnityEngine ;
2025-10-14 14:57:50 +02:00
namespace Data.CardSystem
2025-10-10 14:31:51 +02:00
{
/// <summary>
/// Manages the player's card collection, booster packs, and related operations.
2025-11-07 15:38:31 +00:00
/// Manages the card collection system for the game.
/// Handles unlocking cards, tracking collections, and integrating with the save/load system.
2025-10-10 14:31:51 +02:00
/// </summary>
2025-11-07 15:38:31 +00:00
public class CardSystemManager : ManagedBehaviour
2025-10-10 14:31:51 +02:00
{
private static CardSystemManager _instance ;
2025-10-20 12:04:55 +02:00
public static CardSystemManager Instance = > _instance ;
2025-10-10 14:31:51 +02:00
2025-11-07 15:38:31 +00:00
// Save system configuration
public override bool AutoRegisterForSave = > true ;
public override string SaveId = > "CardSystemManager" ;
2025-10-10 14:31:51 +02:00
[Header("Card Collection")]
[SerializeField] private List < CardDefinition > availableCards = new List < CardDefinition > ( ) ;
2025-10-21 10:05:49 +02:00
2025-10-10 14:31:51 +02:00
// Runtime data - will be serialized for save/load
[SerializeField] private CardInventory playerInventory = new CardInventory ( ) ;
2025-11-07 01:51:03 +01:00
// Album system - cards waiting to be placed in album
private List < CardData > _pendingRevealCards = new List < CardData > ( ) ;
private HashSet < string > _placedInAlbumCardIds = new HashSet < string > ( ) ;
2025-10-21 10:05:49 +02:00
2025-10-10 14:31:51 +02:00
// Dictionary to quickly look up card definitions by ID
2025-11-07 15:38:31 +00:00
private Dictionary < string , CardDefinition > _definitionLookup ;
private bool _lookupInitialized = false ;
2025-10-21 10:05:49 +02:00
2025-10-10 14:31:51 +02:00
// Event callbacks using System.Action
public event Action < List < CardData > > OnBoosterOpened ;
public event Action < CardData > OnCardCollected ;
public event Action < int > OnBoosterCountChanged ;
2025-11-07 01:51:03 +01:00
public event Action < CardData > OnPendingCardAdded ;
2025-11-18 08:40:59 +00:00
public event Action < CardData > OnPendingCardRemoved ;
2025-11-07 01:51:03 +01:00
public event Action < CardData > OnCardPlacedInAlbum ;
2025-11-07 15:38:31 +00:00
2025-11-11 08:48:29 +00:00
internal override void OnManagedAwake ( )
2025-10-10 14:31:51 +02:00
{
2025-11-11 08:48:29 +00:00
// Set instance immediately (early initialization)
2025-10-10 14:31:51 +02:00
_instance = this ;
2025-11-07 15:38:31 +00:00
Refactoring of the interaction system and preliminary integration of save/load functionality across the game. (#44)
### Interactables Architecture Refactor
- Converted composition to inheritance, moved from component-based to class-based interactables. No more requirement for chain of "Interactable -> Item" etc.
- Created `InteractableBase` abstract base class with common functionality that replaces the old component
- Specialized child classes: `Pickup`, `ItemSlot`, `LevelSwitch`, `MinigameSwitch`, `CombinationItem`, `OneClickInteraction` are now children classes
- Light updates to the interactable inspector, moved some things arround, added collapsible inspector sections in the UI for better editor experience
### State Machine Integration
- Custom `AppleMachine` inheritong from Pixelplacement's StateMachine which implements our own interface for saving, easy place for future improvements
- Replaced all previous StateMachines by `AppleMachine`
- Custom `AppleState` extends from default `State`. Added serialization, split state logic into "EnterState", "RestoreState", "ExitState" allowing for separate logic when triggering in-game vs loading game
- Restores directly to target state without triggering transitional logic
- Migration tool converts existing instances
### Prefab Organization
- Saved changes from scenes into prefabs
- Cleaned up duplicated components, confusing prefabs hierarchies
- Created prefab variants where possible
- Consolidated Environment prefabs and moved them out of Placeholders subfolder into main Environment folder
- Organized item prefabs from PrefabsPLACEHOLDER into proper Items folder
- Updated prefab references - All scene references updated to new locations
- Removed placeholder files from Characters, Levels, UI, and Minigames folders
### Scene Updates
- Quarry scene with major updates
- Saved multiple working versions (Quarry, Quarry_Fixed, Quarry_OLD)
- Added proper lighting data
- Updated all interactable components to new architecture
### Minor editor tools
- New tool for testing cards from an editor window (no in-scene object required)
- Updated Interactable Inspector
- New debug option to opt in-and-out of the save/load system
- Tooling for easier migration
Co-authored-by: Michal Pikulski <michal.a.pikulski@gmail.com>
Reviewed-on: https://homelab.tailf7f81b.ts.net/tschesky/AppleHillsProduction/pulls/44
2025-11-03 10:12:51 +00:00
// Load card definitions from Addressables, then register with save system
2025-10-21 10:05:49 +02:00
LoadCardDefinitionsFromAddressables ( ) ;
2025-11-07 15:38:31 +00:00
}
2025-11-11 08:48:29 +00:00
internal override void OnManagedStart ( )
2025-11-07 15:38:31 +00:00
{
Logging . Debug ( "[CardSystemManager] Initialized" ) ;
2025-10-10 14:31:51 +02:00
}
2025-10-20 16:33:18 +02:00
/// <summary>
2025-10-21 10:05:49 +02:00
/// Loads all card definitions from Addressables using the "BlokkemonCard" label
2025-10-20 16:33:18 +02:00
/// </summary>
2025-10-21 10:05:49 +02:00
private async void LoadCardDefinitionsFromAddressables ( )
2025-10-20 16:33:18 +02:00
{
2025-10-21 10:05:49 +02:00
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 )
2025-10-20 16:33:18 +02:00
{
2025-10-21 10:05:49 +02:00
var locations = handle . Result ;
var loadedIds = new HashSet < string > ( ) ;
foreach ( var loc in locations )
2025-10-20 16:33:18 +02:00
{
2025-10-21 10:05:49 +02:00
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 ) ;
}
}
2025-10-20 16:33:18 +02:00
}
2025-10-27 14:00:37 +01:00
// Build lookup now that cards are loaded
BuildDefinitionLookup ( ) ;
Refactoring of the interaction system and preliminary integration of save/load functionality across the game. (#44)
### Interactables Architecture Refactor
- Converted composition to inheritance, moved from component-based to class-based interactables. No more requirement for chain of "Interactable -> Item" etc.
- Created `InteractableBase` abstract base class with common functionality that replaces the old component
- Specialized child classes: `Pickup`, `ItemSlot`, `LevelSwitch`, `MinigameSwitch`, `CombinationItem`, `OneClickInteraction` are now children classes
- Light updates to the interactable inspector, moved some things arround, added collapsible inspector sections in the UI for better editor experience
### State Machine Integration
- Custom `AppleMachine` inheritong from Pixelplacement's StateMachine which implements our own interface for saving, easy place for future improvements
- Replaced all previous StateMachines by `AppleMachine`
- Custom `AppleState` extends from default `State`. Added serialization, split state logic into "EnterState", "RestoreState", "ExitState" allowing for separate logic when triggering in-game vs loading game
- Restores directly to target state without triggering transitional logic
- Migration tool converts existing instances
### Prefab Organization
- Saved changes from scenes into prefabs
- Cleaned up duplicated components, confusing prefabs hierarchies
- Created prefab variants where possible
- Consolidated Environment prefabs and moved them out of Placeholders subfolder into main Environment folder
- Organized item prefabs from PrefabsPLACEHOLDER into proper Items folder
- Updated prefab references - All scene references updated to new locations
- Removed placeholder files from Characters, Levels, UI, and Minigames folders
### Scene Updates
- Quarry scene with major updates
- Saved multiple working versions (Quarry, Quarry_Fixed, Quarry_OLD)
- Added proper lighting data
- Updated all interactable components to new architecture
### Minor editor tools
- New tool for testing cards from an editor window (no in-scene object required)
- Updated Interactable Inspector
- New debug option to opt in-and-out of the save/load system
- Tooling for easier migration
Co-authored-by: Michal Pikulski <michal.a.pikulski@gmail.com>
Reviewed-on: https://homelab.tailf7f81b.ts.net/tschesky/AppleHillsProduction/pulls/44
2025-11-03 10:12:51 +00:00
Logging . Debug ( $"[CardSystemManager] Loaded {availableCards.Count} card definitions from Addressables" ) ;
2025-10-20 16:33:18 +02:00
}
Refactoring of the interaction system and preliminary integration of save/load functionality across the game. (#44)
### Interactables Architecture Refactor
- Converted composition to inheritance, moved from component-based to class-based interactables. No more requirement for chain of "Interactable -> Item" etc.
- Created `InteractableBase` abstract base class with common functionality that replaces the old component
- Specialized child classes: `Pickup`, `ItemSlot`, `LevelSwitch`, `MinigameSwitch`, `CombinationItem`, `OneClickInteraction` are now children classes
- Light updates to the interactable inspector, moved some things arround, added collapsible inspector sections in the UI for better editor experience
### State Machine Integration
- Custom `AppleMachine` inheritong from Pixelplacement's StateMachine which implements our own interface for saving, easy place for future improvements
- Replaced all previous StateMachines by `AppleMachine`
- Custom `AppleState` extends from default `State`. Added serialization, split state logic into "EnterState", "RestoreState", "ExitState" allowing for separate logic when triggering in-game vs loading game
- Restores directly to target state without triggering transitional logic
- Migration tool converts existing instances
### Prefab Organization
- Saved changes from scenes into prefabs
- Cleaned up duplicated components, confusing prefabs hierarchies
- Created prefab variants where possible
- Consolidated Environment prefabs and moved them out of Placeholders subfolder into main Environment folder
- Organized item prefabs from PrefabsPLACEHOLDER into proper Items folder
- Updated prefab references - All scene references updated to new locations
- Removed placeholder files from Characters, Levels, UI, and Minigames folders
### Scene Updates
- Quarry scene with major updates
- Saved multiple working versions (Quarry, Quarry_Fixed, Quarry_OLD)
- Added proper lighting data
- Updated all interactable components to new architecture
### Minor editor tools
- New tool for testing cards from an editor window (no in-scene object required)
- Updated Interactable Inspector
- New debug option to opt in-and-out of the save/load system
- Tooling for easier migration
Co-authored-by: Michal Pikulski <michal.a.pikulski@gmail.com>
Reviewed-on: https://homelab.tailf7f81b.ts.net/tschesky/AppleHillsProduction/pulls/44
2025-11-03 10:12:51 +00:00
else
2025-10-27 14:00:37 +01:00
{
Refactoring of the interaction system and preliminary integration of save/load functionality across the game. (#44)
### Interactables Architecture Refactor
- Converted composition to inheritance, moved from component-based to class-based interactables. No more requirement for chain of "Interactable -> Item" etc.
- Created `InteractableBase` abstract base class with common functionality that replaces the old component
- Specialized child classes: `Pickup`, `ItemSlot`, `LevelSwitch`, `MinigameSwitch`, `CombinationItem`, `OneClickInteraction` are now children classes
- Light updates to the interactable inspector, moved some things arround, added collapsible inspector sections in the UI for better editor experience
### State Machine Integration
- Custom `AppleMachine` inheritong from Pixelplacement's StateMachine which implements our own interface for saving, easy place for future improvements
- Replaced all previous StateMachines by `AppleMachine`
- Custom `AppleState` extends from default `State`. Added serialization, split state logic into "EnterState", "RestoreState", "ExitState" allowing for separate logic when triggering in-game vs loading game
- Restores directly to target state without triggering transitional logic
- Migration tool converts existing instances
### Prefab Organization
- Saved changes from scenes into prefabs
- Cleaned up duplicated components, confusing prefabs hierarchies
- Created prefab variants where possible
- Consolidated Environment prefabs and moved them out of Placeholders subfolder into main Environment folder
- Organized item prefabs from PrefabsPLACEHOLDER into proper Items folder
- Updated prefab references - All scene references updated to new locations
- Removed placeholder files from Characters, Levels, UI, and Minigames folders
### Scene Updates
- Quarry scene with major updates
- Saved multiple working versions (Quarry, Quarry_Fixed, Quarry_OLD)
- Added proper lighting data
- Updated all interactable components to new architecture
### Minor editor tools
- New tool for testing cards from an editor window (no in-scene object required)
- Updated Interactable Inspector
- New debug option to opt in-and-out of the save/load system
- Tooling for easier migration
Co-authored-by: Michal Pikulski <michal.a.pikulski@gmail.com>
Reviewed-on: https://homelab.tailf7f81b.ts.net/tschesky/AppleHillsProduction/pulls/44
2025-11-03 10:12:51 +00:00
Logging . Warning ( "[CardSystemManager] Failed to load card definitions from Addressables" ) ;
2025-10-27 14:00:37 +01:00
}
}
2025-10-10 14:31:51 +02:00
/// <summary>
/// Builds a lookup dictionary for quick access to card definitions by ID
/// </summary>
private void BuildDefinitionLookup ( )
{
2025-11-07 15:38:31 +00:00
if ( _definitionLookup = = null )
{
_definitionLookup = new Dictionary < string , CardDefinition > ( ) ;
}
2025-10-10 14:31:51 +02:00
_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 ) ;
}
}
2025-11-07 15:38:31 +00:00
_lookupInitialized = true ;
2025-10-10 14:31:51 +02:00
}
/// <summary>
/// Adds a booster pack to the player's inventory
/// </summary>
public void AddBoosterPack ( int count = 1 )
{
playerInventory . BoosterPackCount + = count ;
OnBoosterCountChanged ? . Invoke ( playerInventory . BoosterPackCount ) ;
2025-10-14 15:53:58 +02:00
Logging . Debug ( $"[CardSystemManager] Added {count} booster pack(s). Total: {playerInventory.BoosterPackCount}" ) ;
2025-10-10 14:31:51 +02:00
}
/// <summary>
/// Opens a booster pack and returns the newly obtained cards
2025-11-06 23:06:41 +01:00
/// NOTE: Cards are NOT added to inventory immediately - they're added after the reveal interaction
2025-10-10 14:31:51 +02:00
/// </summary>
public List < CardData > OpenBoosterPack ( )
{
if ( playerInventory . BoosterPackCount < = 0 )
{
2025-10-14 15:53:58 +02:00
Logging . Warning ( "[CardSystemManager] Attempted to open a booster pack, but none are available." ) ;
2025-10-10 14:31:51 +02:00
return new List < CardData > ( ) ;
}
playerInventory . BoosterPackCount - - ;
OnBoosterCountChanged ? . Invoke ( playerInventory . BoosterPackCount ) ;
// Draw 3 cards based on rarity distribution
List < CardData > drawnCards = DrawRandomCards ( 3 ) ;
2025-11-06 23:06:41 +01:00
// 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
2025-10-10 14:31:51 +02:00
// Notify listeners
OnBoosterOpened ? . Invoke ( drawnCards ) ;
2025-10-14 15:53:58 +02:00
Logging . Debug ( $"[CardSystemManager] Opened a booster pack and obtained {drawnCards.Count} cards. Remaining boosters: {playerInventory.BoosterPackCount}" ) ;
2025-10-10 14:31:51 +02:00
return drawnCards ;
}
2025-11-06 23:06:41 +01:00
/// <summary>
/// Check if a card is new to the player's collection at the specified rarity
2025-11-07 01:51:03 +01:00
/// Checks both owned inventory and pending reveal queue
2025-11-06 23:06:41 +01:00
/// </summary>
/// <param name="cardData">The card to check</param>
/// <param name="existingCard">Out parameter - the existing card if found, null otherwise</param>
2025-11-07 01:51:03 +01:00
/// <returns>True if this is a new card at this rarity, false if already owned or pending</returns>
2025-11-06 23:06:41 +01:00
public bool IsCardNew ( CardData cardData , out CardData existingCard )
{
2025-11-07 01:51:03 +01:00
// First check inventory (cards already placed in album)
2025-11-06 23:06:41 +01:00
if ( playerInventory . HasCard ( cardData . DefinitionId , cardData . Rarity ) )
{
existingCard = playerInventory . GetCard ( cardData . DefinitionId , cardData . Rarity ) ;
return false ;
}
2025-11-07 01:51:03 +01:00
// 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
}
2025-11-06 23:06:41 +01:00
existingCard = null ;
2025-11-07 01:51:03 +01:00
return true ; // Truly new - not in inventory or pending
2025-11-06 23:06:41 +01:00
}
/// <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 ) ;
}
2025-10-10 14:31:51 +02:00
/// <summary>
/// Adds a card to the player's inventory, handles duplicates
2025-11-07 01:51:03 +01:00
/// Checks both inventory and pending lists to find existing cards
2025-10-10 14:31:51 +02:00
/// </summary>
private void AddCardToInventory ( CardData card )
{
2025-11-07 01:51:03 +01:00
// 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)
2025-11-06 23:06:41 +01:00
if ( playerInventory . HasCard ( card . DefinitionId , card . Rarity ) )
2025-10-10 14:31:51 +02:00
{
2025-11-06 23:06:41 +01:00
CardData existingCard = playerInventory . GetCard ( card . DefinitionId , card . Rarity ) ;
2025-10-10 14:31:51 +02:00
existingCard . CopiesOwned + + ;
2025-11-07 01:51:03 +01:00
Logging . Debug ( $"[CardSystemManager] Added duplicate card '{card.Name}' ({card.Rarity}) to INVENTORY. Now have {existingCard.CopiesOwned} copies." ) ;
return ;
2025-10-10 14:31:51 +02:00
}
2025-11-07 01:51:03 +01:00
// Then check pending reveal queue
CardData pendingCard = _pendingRevealCards . FirstOrDefault ( c = >
c . DefinitionId = = card . DefinitionId & & c . Rarity = = card . Rarity ) ;
if ( pendingCard ! = null )
2025-10-10 14:31:51 +02:00
{
2025-11-07 01:51:03 +01:00
// Card already in pending - increment its copy count
pendingCard . CopiesOwned + + ;
2025-10-10 14:31:51 +02:00
2025-11-07 01:51:03 +01:00
Logging . Debug ( $"[CardSystemManager] Added duplicate card '{card.Name}' ({card.Rarity}) to PENDING. Now have {pendingCard.CopiesOwned} copies pending." ) ;
return ;
2025-10-10 14:31:51 +02:00
}
2025-11-07 01:51:03 +01:00
// 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." ) ;
2025-10-10 14:31:51 +02:00
}
/// <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
2025-10-14 15:53:58 +02:00
Logging . Warning ( $"[CardSystemManager] No cards of rarity {rarity} available, selecting a random card instead." ) ;
2025-10-10 14:31:51 +02:00
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 ( )
{
2025-11-05 23:50:15 +01:00
// Weighted random for 3 rarities
2025-10-10 14:31:51 +02:00
float rand = UnityEngine . Random . value ;
2025-11-05 23:50:15 +01:00
if ( rand < 0.70f ) return CardRarity . Normal ; // 70% chance
if ( rand < 0.95f ) return CardRarity . Rare ; // 25% chance
return CardRarity . Legendary ; // 5% chance
2025-10-10 14:31:51 +02:00
}
/// <summary>
2025-11-07 01:51:03 +01:00
/// Returns all cards from the player's collection (both owned and pending)
2025-10-10 14:31:51 +02:00
/// </summary>
public List < CardData > GetAllCollectedCards ( )
{
2025-11-07 01:51:03 +01:00
List < CardData > allCards = new List < CardData > ( playerInventory . GetAllCards ( ) ) ;
allCards . AddRange ( _pendingRevealCards ) ;
return allCards ;
2025-10-14 14:57:50 +02:00
}
2025-11-18 08:40:59 +00:00
/// <summary>
/// Returns only owned/collected cards (excludes pending reveal cards)
/// </summary>
public List < CardData > GetCollectionOnly ( )
{
return new List < CardData > ( playerInventory . GetAllCards ( ) ) ;
}
2025-10-14 14:57:50 +02:00
/// <summary>
2025-11-07 01:51:03 +01:00
/// Returns cards from a specific zone (both owned and pending)
2025-10-14 14:57:50 +02:00
/// </summary>
public List < CardData > GetCardsByZone ( CardZone zone )
{
2025-11-07 01:51:03 +01:00
List < CardData > zoneCards = new List < CardData > ( playerInventory . GetCardsByZone ( zone ) ) ;
zoneCards . AddRange ( _pendingRevealCards . Where ( c = > c . Zone = = zone ) ) ;
return zoneCards ;
2025-10-14 14:57:50 +02:00
}
/// <summary>
2025-11-07 01:51:03 +01:00
/// Returns cards of a specific rarity (both owned and pending)
2025-10-14 14:57:50 +02:00
/// </summary>
public List < CardData > GetCardsByRarity ( CardRarity rarity )
{
2025-11-07 01:51:03 +01:00
List < CardData > rarityCards = new List < CardData > ( playerInventory . GetCardsByRarity ( rarity ) ) ;
rarityCards . AddRange ( _pendingRevealCards . Where ( c = > c . Rarity = = rarity ) ) ;
return rarityCards ;
2025-10-10 14:31:51 +02:00
}
/// <summary>
/// Returns the number of booster packs the player has
/// </summary>
public int GetBoosterPackCount ( )
{
return playerInventory . BoosterPackCount ;
}
/// <summary>
2025-11-07 01:51:03 +01:00
/// Returns whether a specific card definition has been collected (at any rarity, in inventory or pending)
2025-10-10 14:31:51 +02:00
/// </summary>
public bool IsCardCollected ( string definitionId )
{
2025-11-07 01:51:03 +01:00
// Check inventory at any rarity
2025-11-06 23:06:41 +01:00
foreach ( CardRarity rarity in System . Enum . GetValues ( typeof ( CardRarity ) ) )
{
if ( playerInventory . HasCard ( definitionId , rarity ) )
return true ;
}
2025-11-07 01:51:03 +01:00
// Check pending reveal queue
if ( _pendingRevealCards . Any ( c = > c . DefinitionId = = definitionId ) )
return true ;
2025-11-06 23:06:41 +01:00
return false ;
2025-10-14 14:57:50 +02:00
}
/// <summary>
2025-11-07 01:51:03 +01:00
/// Gets total unique card count (both owned and pending)
2025-10-14 14:57:50 +02:00
/// </summary>
public int GetUniqueCardCount ( )
{
2025-11-07 01:51:03 +01:00
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 ;
2025-10-14 14:57:50 +02:00
}
/// <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 ;
2025-10-10 14:31:51 +02:00
}
2025-10-20 13:45:56 +02:00
/// <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>
2025-11-07 01:51:03 +01:00
/// Returns the count of cards by rarity (both owned and pending)
2025-10-20 13:45:56 +02:00
/// </summary>
public int GetCardCountByRarity ( CardRarity rarity )
{
2025-11-07 01:51:03 +01:00
int inventoryCount = playerInventory . GetCardsByRarity ( rarity ) . Count ;
int pendingCount = _pendingRevealCards . Count ( c = > c . Rarity = = rarity ) ;
return inventoryCount + pendingCount ;
2025-10-20 13:45:56 +02:00
}
/// <summary>
2025-11-07 01:51:03 +01:00
/// Returns the count of cards by zone (both owned and pending)
2025-10-20 13:45:56 +02:00
/// </summary>
public int GetCardCountByZone ( CardZone zone )
{
2025-11-07 01:51:03 +01:00
int inventoryCount = playerInventory . GetCardsByZone ( zone ) . Count ;
int pendingCount = _pendingRevealCards . Count ( c = > c . Zone = = zone ) ;
return inventoryCount + pendingCount ;
2025-10-20 13:45:56 +02:00
}
/// <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 ;
}
2025-10-27 14:00:37 +01:00
2025-11-07 01:51:03 +01:00
#region Album System
/// <summary>
2025-11-18 08:40:59 +00:00
/// Returns all pending reveal cards (cards waiting to be placed in album)
2025-11-07 01:51:03 +01:00
/// </summary>
public List < CardData > GetPendingRevealCards ( )
{
2025-11-18 08:40:59 +00:00
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 ;
2025-11-07 01:51:03 +01:00
}
/// <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
2025-11-18 08:40:59 +00:00
/// Adds card to owned inventory and tracks as placed
/// Note: Card may have already been removed from pending list during drag
2025-11-07 01:51:03 +01:00
/// </summary>
public void MarkCardAsPlaced ( CardData card )
{
2025-11-18 08:40:59 +00:00
if ( card = = null )
2025-11-07 01:51:03 +01:00
{
2025-11-18 08:40:59 +00:00
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" ) ;
2025-11-07 01:51:03 +01:00
}
else
{
2025-11-18 08:40:59 +00:00
Logging . Debug ( $"[CardSystemManager] Card '{card.Name}' added to inventory (was already removed from pending)" ) ;
2025-11-07 01:51:03 +01:00
}
}
/// <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
2025-10-27 14:00:37 +01:00
/// <summary>
/// Export current card collection to a serializable snapshot
/// </summary>
public CardCollectionState ExportCardCollectionState ( )
{
var state = new CardCollectionState
{
boosterPackCount = playerInventory . BoosterPackCount ,
2025-11-07 01:51:03 +01:00
cards = new List < SavedCardEntry > ( ) ,
pendingRevealCards = new List < SavedCardEntry > ( ) ,
placedInAlbumCardIds = new List < string > ( _placedInAlbumCardIds )
2025-10-27 14:00:37 +01:00
} ;
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
} ) ;
}
2025-11-07 01:51:03 +01:00
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
} ) ;
}
2025-10-27 14:00:37 +01:00
return state ;
}
/// <summary>
/// Apply a previously saved snapshot to the runtime inventory
/// </summary>
2025-11-07 15:38:31 +00:00
public async void ApplyCardCollectionState ( CardCollectionState state )
2025-10-27 14:00:37 +01:00
{
if ( state = = null ) return ;
2025-11-07 15:38:31 +00:00
// Wait for lookup to be initialized before loading
while ( ! _lookupInitialized )
{
await System . Threading . Tasks . Task . Yield ( ) ;
}
2025-10-27 14:00:37 +01:00
playerInventory . ClearAllCards ( ) ;
2025-11-07 01:51:03 +01:00
_pendingRevealCards . Clear ( ) ;
_placedInAlbumCardIds . Clear ( ) ;
2025-10-27 14:00:37 +01:00
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}" ) ;
}
2025-11-07 01:51:03 +01:00
}
// 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 ) ;
}
2025-10-27 14:00:37 +01:00
}
}
Refactoring of the interaction system and preliminary integration of save/load functionality across the game. (#44)
### Interactables Architecture Refactor
- Converted composition to inheritance, moved from component-based to class-based interactables. No more requirement for chain of "Interactable -> Item" etc.
- Created `InteractableBase` abstract base class with common functionality that replaces the old component
- Specialized child classes: `Pickup`, `ItemSlot`, `LevelSwitch`, `MinigameSwitch`, `CombinationItem`, `OneClickInteraction` are now children classes
- Light updates to the interactable inspector, moved some things arround, added collapsible inspector sections in the UI for better editor experience
### State Machine Integration
- Custom `AppleMachine` inheritong from Pixelplacement's StateMachine which implements our own interface for saving, easy place for future improvements
- Replaced all previous StateMachines by `AppleMachine`
- Custom `AppleState` extends from default `State`. Added serialization, split state logic into "EnterState", "RestoreState", "ExitState" allowing for separate logic when triggering in-game vs loading game
- Restores directly to target state without triggering transitional logic
- Migration tool converts existing instances
### Prefab Organization
- Saved changes from scenes into prefabs
- Cleaned up duplicated components, confusing prefabs hierarchies
- Created prefab variants where possible
- Consolidated Environment prefabs and moved them out of Placeholders subfolder into main Environment folder
- Organized item prefabs from PrefabsPLACEHOLDER into proper Items folder
- Updated prefab references - All scene references updated to new locations
- Removed placeholder files from Characters, Levels, UI, and Minigames folders
### Scene Updates
- Quarry scene with major updates
- Saved multiple working versions (Quarry, Quarry_Fixed, Quarry_OLD)
- Added proper lighting data
- Updated all interactable components to new architecture
### Minor editor tools
- New tool for testing cards from an editor window (no in-scene object required)
- Updated Interactable Inspector
- New debug option to opt in-and-out of the save/load system
- Tooling for easier migration
Co-authored-by: Michal Pikulski <michal.a.pikulski@gmail.com>
Reviewed-on: https://homelab.tailf7f81b.ts.net/tschesky/AppleHillsProduction/pulls/44
2025-11-03 10:12:51 +00:00
2025-11-07 17:49:43 +01:00
/// <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)" ) ;
}
2025-11-07 15:38:31 +00:00
#region Save / Load Lifecycle Hooks
Refactoring of the interaction system and preliminary integration of save/load functionality across the game. (#44)
### Interactables Architecture Refactor
- Converted composition to inheritance, moved from component-based to class-based interactables. No more requirement for chain of "Interactable -> Item" etc.
- Created `InteractableBase` abstract base class with common functionality that replaces the old component
- Specialized child classes: `Pickup`, `ItemSlot`, `LevelSwitch`, `MinigameSwitch`, `CombinationItem`, `OneClickInteraction` are now children classes
- Light updates to the interactable inspector, moved some things arround, added collapsible inspector sections in the UI for better editor experience
### State Machine Integration
- Custom `AppleMachine` inheritong from Pixelplacement's StateMachine which implements our own interface for saving, easy place for future improvements
- Replaced all previous StateMachines by `AppleMachine`
- Custom `AppleState` extends from default `State`. Added serialization, split state logic into "EnterState", "RestoreState", "ExitState" allowing for separate logic when triggering in-game vs loading game
- Restores directly to target state without triggering transitional logic
- Migration tool converts existing instances
### Prefab Organization
- Saved changes from scenes into prefabs
- Cleaned up duplicated components, confusing prefabs hierarchies
- Created prefab variants where possible
- Consolidated Environment prefabs and moved them out of Placeholders subfolder into main Environment folder
- Organized item prefabs from PrefabsPLACEHOLDER into proper Items folder
- Updated prefab references - All scene references updated to new locations
- Removed placeholder files from Characters, Levels, UI, and Minigames folders
### Scene Updates
- Quarry scene with major updates
- Saved multiple working versions (Quarry, Quarry_Fixed, Quarry_OLD)
- Added proper lighting data
- Updated all interactable components to new architecture
### Minor editor tools
- New tool for testing cards from an editor window (no in-scene object required)
- Updated Interactable Inspector
- New debug option to opt in-and-out of the save/load system
- Tooling for easier migration
Co-authored-by: Michal Pikulski <michal.a.pikulski@gmail.com>
Reviewed-on: https://homelab.tailf7f81b.ts.net/tschesky/AppleHillsProduction/pulls/44
2025-11-03 10:12:51 +00:00
2025-11-11 08:48:29 +00:00
internal override string OnGlobalSaveRequested ( )
Refactoring of the interaction system and preliminary integration of save/load functionality across the game. (#44)
### Interactables Architecture Refactor
- Converted composition to inheritance, moved from component-based to class-based interactables. No more requirement for chain of "Interactable -> Item" etc.
- Created `InteractableBase` abstract base class with common functionality that replaces the old component
- Specialized child classes: `Pickup`, `ItemSlot`, `LevelSwitch`, `MinigameSwitch`, `CombinationItem`, `OneClickInteraction` are now children classes
- Light updates to the interactable inspector, moved some things arround, added collapsible inspector sections in the UI for better editor experience
### State Machine Integration
- Custom `AppleMachine` inheritong from Pixelplacement's StateMachine which implements our own interface for saving, easy place for future improvements
- Replaced all previous StateMachines by `AppleMachine`
- Custom `AppleState` extends from default `State`. Added serialization, split state logic into "EnterState", "RestoreState", "ExitState" allowing for separate logic when triggering in-game vs loading game
- Restores directly to target state without triggering transitional logic
- Migration tool converts existing instances
### Prefab Organization
- Saved changes from scenes into prefabs
- Cleaned up duplicated components, confusing prefabs hierarchies
- Created prefab variants where possible
- Consolidated Environment prefabs and moved them out of Placeholders subfolder into main Environment folder
- Organized item prefabs from PrefabsPLACEHOLDER into proper Items folder
- Updated prefab references - All scene references updated to new locations
- Removed placeholder files from Characters, Levels, UI, and Minigames folders
### Scene Updates
- Quarry scene with major updates
- Saved multiple working versions (Quarry, Quarry_Fixed, Quarry_OLD)
- Added proper lighting data
- Updated all interactable components to new architecture
### Minor editor tools
- New tool for testing cards from an editor window (no in-scene object required)
- Updated Interactable Inspector
- New debug option to opt in-and-out of the save/load system
- Tooling for easier migration
Co-authored-by: Michal Pikulski <michal.a.pikulski@gmail.com>
Reviewed-on: https://homelab.tailf7f81b.ts.net/tschesky/AppleHillsProduction/pulls/44
2025-11-03 10:12:51 +00:00
{
var state = ExportCardCollectionState ( ) ;
return JsonUtility . ToJson ( state ) ;
}
2025-11-11 08:48:29 +00:00
internal override void OnGlobalRestoreRequested ( string serializedData )
Refactoring of the interaction system and preliminary integration of save/load functionality across the game. (#44)
### Interactables Architecture Refactor
- Converted composition to inheritance, moved from component-based to class-based interactables. No more requirement for chain of "Interactable -> Item" etc.
- Created `InteractableBase` abstract base class with common functionality that replaces the old component
- Specialized child classes: `Pickup`, `ItemSlot`, `LevelSwitch`, `MinigameSwitch`, `CombinationItem`, `OneClickInteraction` are now children classes
- Light updates to the interactable inspector, moved some things arround, added collapsible inspector sections in the UI for better editor experience
### State Machine Integration
- Custom `AppleMachine` inheritong from Pixelplacement's StateMachine which implements our own interface for saving, easy place for future improvements
- Replaced all previous StateMachines by `AppleMachine`
- Custom `AppleState` extends from default `State`. Added serialization, split state logic into "EnterState", "RestoreState", "ExitState" allowing for separate logic when triggering in-game vs loading game
- Restores directly to target state without triggering transitional logic
- Migration tool converts existing instances
### Prefab Organization
- Saved changes from scenes into prefabs
- Cleaned up duplicated components, confusing prefabs hierarchies
- Created prefab variants where possible
- Consolidated Environment prefabs and moved them out of Placeholders subfolder into main Environment folder
- Organized item prefabs from PrefabsPLACEHOLDER into proper Items folder
- Updated prefab references - All scene references updated to new locations
- Removed placeholder files from Characters, Levels, UI, and Minigames folders
### Scene Updates
- Quarry scene with major updates
- Saved multiple working versions (Quarry, Quarry_Fixed, Quarry_OLD)
- Added proper lighting data
- Updated all interactable components to new architecture
### Minor editor tools
- New tool for testing cards from an editor window (no in-scene object required)
- Updated Interactable Inspector
- New debug option to opt in-and-out of the save/load system
- Tooling for easier migration
Co-authored-by: Michal Pikulski <michal.a.pikulski@gmail.com>
Reviewed-on: https://homelab.tailf7f81b.ts.net/tschesky/AppleHillsProduction/pulls/44
2025-11-03 10:12:51 +00:00
{
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
2025-10-10 14:31:51 +02:00
}
}