2025-10-10 14:31:51 +02:00
using System ;
using System.Collections.Generic ;
2025-10-20 16:33:18 +02:00
using System.Linq ;
2025-10-14 14:57:50 +02:00
using AppleHills.Data.CardSystem ;
2025-10-16 19:43:19 +02:00
using Bootstrap ;
2025-10-14 15:53:58 +02:00
using Core ;
2025-10-10 14:31:51 +02:00
using UnityEngine ;
2025-10-20 16:33:18 +02:00
#if UNITY_EDITOR
using UnityEditor ;
#endif
2025-10-10 14:31:51 +02:00
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.
/// Uses a singleton pattern for global access.
/// </summary>
public class CardSystemManager : MonoBehaviour
{
private static CardSystemManager _instance ;
private static bool _isQuitting = false ;
2025-10-20 12:04:55 +02:00
public static CardSystemManager Instance = > _instance ;
2025-10-10 14:31:51 +02:00
[Header("Card Collection")]
[SerializeField] private List < CardDefinition > availableCards = new List < CardDefinition > ( ) ;
2025-10-20 16:33:18 +02:00
[Header("Auto-Loading Configuration")]
[SerializeField] private bool autoLoadCardDefinitions = true ;
[SerializeField] private string cardDataPath = "Data/Cards" ;
2025-10-10 14:31:51 +02:00
// Runtime data - will be serialized for save/load
[SerializeField] private CardInventory playerInventory = new CardInventory ( ) ;
// Dictionary to quickly look up card definitions by ID
private Dictionary < string , CardDefinition > _definitionLookup = new Dictionary < string , CardDefinition > ( ) ;
// Event callbacks using System.Action
public event Action < List < CardData > > OnBoosterOpened ;
public event Action < CardData > OnCardCollected ;
public event Action < CardData > OnCardRarityUpgraded ;
public event Action < int > OnBoosterCountChanged ;
private void Awake ( )
{
_instance = this ;
2025-10-20 16:33:18 +02:00
// Auto-load card definitions if enabled
if ( autoLoadCardDefinitions )
{
LoadCardDefinitionsFromFolder ( ) ;
}
2025-10-10 14:31:51 +02:00
// Build lookup dictionary
BuildDefinitionLookup ( ) ;
2025-10-16 19:43:19 +02:00
// Register for post-boot initialization
BootCompletionService . RegisterInitAction ( InitializePostBoot ) ;
}
private void InitializePostBoot ( )
{
// Initialize any dependencies that require other services to be ready
Logging . Debug ( "[CardSystemManager] Post-boot initialization complete" ) ;
2025-10-10 14:31:51 +02:00
}
private void OnApplicationQuit ( )
{
_isQuitting = true ;
}
2025-10-20 16:33:18 +02:00
/// <summary>
/// Loads all card definitions from the specified folder
/// </summary>
private void LoadCardDefinitionsFromFolder ( )
{
// Initialize list if needed
if ( availableCards = = null )
{
availableCards = new List < CardDefinition > ( ) ;
}
#if UNITY_EDITOR
// In editor we can load assets directly from the project folder
string folderPath = "Assets/" + cardDataPath ;
string [ ] guids = AssetDatabase . FindAssets ( "t:CardDefinition" , new [ ] { folderPath } ) ;
List < CardDefinition > loadedDefinitions = new List < CardDefinition > ( ) ;
foreach ( string guid in guids )
{
string assetPath = AssetDatabase . GUIDToAssetPath ( guid ) ;
CardDefinition cardDef = AssetDatabase . LoadAssetAtPath < CardDefinition > ( assetPath ) ;
if ( cardDef ! = null & & ! string . IsNullOrEmpty ( cardDef . Id ) )
{
loadedDefinitions . Add ( cardDef ) ;
}
}
// Replace the existing list with loaded definitions
availableCards = loadedDefinitions ;
#else
// In build, load from Resources folder
CardDefinition [ ] resourceCards = Resources . LoadAll < CardDefinition > ( cardDataPath ) ;
if ( resourceCards ! = null & & resourceCards . Length > 0 )
{
availableCards = resourceCards . Where ( card = > card ! = null & & ! string . IsNullOrEmpty ( card . Id ) ) . ToList ( ) ;
}
#endif
Logging . Debug ( $"[CardSystemManager] Loaded {availableCards.Count} card definitions from {cardDataPath}" ) ;
}
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 ( )
{
_definitionLookup . Clear ( ) ;
foreach ( var cardDef in availableCards )
{
if ( cardDef ! = null & & ! string . IsNullOrEmpty ( cardDef . Id ) )
{
_definitionLookup [ cardDef . Id ] = cardDef ;
}
}
// Link existing card data to their definitions
foreach ( var cardData in playerInventory . CollectedCards . Values )
{
if ( ! string . IsNullOrEmpty ( cardData . DefinitionId ) & &
_definitionLookup . TryGetValue ( cardData . DefinitionId , out CardDefinition def ) )
{
cardData . SetDefinition ( def ) ;
}
}
}
/// <summary>
/// Adds a booster pack to the player's inventory
/// </summary>
public void AddBoosterPack ( int count = 1 )
{
playerInventory . BoosterPackCount + = count ;
OnBoosterCountChanged ? . Invoke ( playerInventory . BoosterPackCount ) ;
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
/// </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 ) ;
// Add cards to the inventory
foreach ( var card in drawnCards )
{
AddCardToInventory ( card ) ;
}
// 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 ;
}
/// <summary>
/// Adds a card to the player's inventory, handles duplicates
/// </summary>
private void AddCardToInventory ( CardData card )
{
// Check if the player already has this card type (definition)
2025-10-14 14:57:50 +02:00
if ( playerInventory . HasCard ( card . DefinitionId ) )
2025-10-10 14:31:51 +02:00
{
2025-10-14 14:57:50 +02:00
CardData existingCard = playerInventory . GetCard ( card . DefinitionId ) ;
2025-10-10 14:31:51 +02:00
existingCard . CopiesOwned + + ;
// Check if the card can be upgraded
if ( existingCard . TryUpgradeRarity ( ) )
{
OnCardRarityUpgraded ? . Invoke ( existingCard ) ;
}
2025-10-14 15:53:58 +02:00
Logging . Debug ( $"[CardSystemManager] Added duplicate card '{card.Name}'. Now have {existingCard.CopiesOwned} copies." ) ;
2025-10-10 14:31:51 +02:00
}
else
{
// Add new card
2025-10-14 14:57:50 +02:00
playerInventory . AddCard ( card ) ;
2025-10-10 14:31:51 +02:00
OnCardCollected ? . Invoke ( card ) ;
2025-10-14 15:53:58 +02:00
Logging . Debug ( $"[CardSystemManager] Added new card '{card.Name}' to collection." ) ;
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 ( )
{
// Simple weighted random - can be adjusted for better distribution
float rand = UnityEngine . Random . value ;
if ( rand < 0.6f ) return CardRarity . Common ;
if ( rand < 0.85f ) return CardRarity . Uncommon ;
if ( rand < 0.95f ) return CardRarity . Rare ;
if ( rand < 0.99f ) return CardRarity . Epic ;
return CardRarity . Legendary ;
}
/// <summary>
/// Returns all cards from the player's collection
/// </summary>
public List < CardData > GetAllCollectedCards ( )
{
2025-10-14 14:57:50 +02:00
return playerInventory . GetAllCards ( ) ;
}
/// <summary>
/// Returns cards from a specific zone
/// </summary>
public List < CardData > GetCardsByZone ( CardZone zone )
{
return playerInventory . GetCardsByZone ( zone ) ;
}
/// <summary>
/// Returns cards of a specific rarity
/// </summary>
public List < CardData > GetCardsByRarity ( CardRarity rarity )
{
return playerInventory . GetCardsByRarity ( rarity ) ;
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>
/// Returns whether a specific card definition has been collected
/// </summary>
public bool IsCardCollected ( string definitionId )
{
2025-10-14 14:57:50 +02:00
return playerInventory . HasCard ( definitionId ) ;
}
/// <summary>
/// Gets total unique card count
/// </summary>
public int GetUniqueCardCount ( )
{
return playerInventory . GetUniqueCardCount ( ) ;
}
/// <summary>
/// Gets completion percentage for a specific zone (0-100)
/// </summary>
public float GetZoneCompletionPercentage ( CardZone zone )
{
// Count available cards in this zone
int totalInZone = availableCards . FindAll ( c = > c . Zone = = zone ) . Count ;
if ( totalInZone = = 0 ) return 0 ;
// Count collected cards in this zone
int collectedInZone = playerInventory . GetCardsByZone ( zone ) . Count ;
return ( float ) collectedInZone / totalInZone * 100f ;
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>
/// Returns the count of cards by rarity
/// </summary>
public int GetCardCountByRarity ( CardRarity rarity )
{
return playerInventory . GetCardsByRarity ( rarity ) . Count ;
}
/// <summary>
/// Returns the count of cards by zone
/// </summary>
public int GetCardCountByZone ( CardZone zone )
{
return playerInventory . GetCardsByZone ( zone ) . Count ;
}
/// <summary>
/// Gets the total number of card definitions available in the system
/// </summary>
public int GetTotalCardDefinitionsCount ( )
{
return availableCards . Count ;
}
/// <summary>
/// Gets the total collection completion percentage (0-100)
/// </summary>
public float GetTotalCompletionPercentage ( )
{
if ( availableCards . Count = = 0 ) return 0 ;
return ( float ) GetUniqueCardCount ( ) / availableCards . Count * 100f ;
}
/// <summary>
/// Gets total completion percentage for a specific rarity (0-100)
/// </summary>
public float GetRarityCompletionPercentage ( CardRarity rarity )
{
// Count available cards of this rarity
int totalOfRarity = availableCards . FindAll ( c = > c . Rarity = = rarity ) . Count ;
if ( totalOfRarity = = 0 ) return 0 ;
// Count collected cards of this rarity
int collectedOfRarity = playerInventory . GetCardsByRarity ( rarity ) . Count ;
return ( float ) collectedOfRarity / totalOfRarity * 100f ;
}
2025-10-10 14:31:51 +02:00
}
}