package collections import ( "context" "fmt" "sort" ) // NewPlayerCollectionList creates a new player collection list func NewPlayerCollectionList(characterID int32, database CollectionDatabase) *PlayerCollectionList { return &PlayerCollectionList{ characterID: characterID, collections: make(map[int32]*Collection), database: database, } } // Initialize loads player's collection progress from database func (pcl *PlayerCollectionList) Initialize(ctx context.Context, masterList *MasterCollectionList) error { pcl.mu.Lock() defer pcl.mu.Unlock() // Load player collection data playerCollections, err := pcl.database.LoadPlayerCollections(ctx, pcl.characterID) if err != nil { return fmt.Errorf("failed to load player collections: %w", err) } for _, playerCollection := range playerCollections { // Get the master collection template masterCollection := masterList.GetCollection(playerCollection.CollectionID) if masterCollection == nil { continue // Skip collections that no longer exist } // Create a copy for the player collection := NewCollectionFromData(masterCollection) if collection == nil { continue } collection.SetCompleted(playerCollection.Completed) // Load player's found items foundItems, err := pcl.database.LoadPlayerCollectionItems(ctx, pcl.characterID, playerCollection.CollectionID) if err != nil { return fmt.Errorf("failed to load player collection items for collection %d: %w", playerCollection.CollectionID, err) } // Mark found items for _, itemID := range foundItems { collection.MarkItemFound(itemID) } // Reset save needed flag after loading collection.SetSaveNeeded(false) pcl.collections[playerCollection.CollectionID] = collection } return nil } // AddCollection adds a collection to the player's list func (pcl *PlayerCollectionList) AddCollection(collection *Collection) bool { pcl.mu.Lock() defer pcl.mu.Unlock() if collection == nil { return false } id := collection.GetID() if _, exists := pcl.collections[id]; exists { return false } pcl.collections[id] = collection return true } // GetCollection retrieves a collection by ID func (pcl *PlayerCollectionList) GetCollection(collectionID int32) *Collection { pcl.mu.RLock() defer pcl.mu.RUnlock() return pcl.collections[collectionID] } // ClearCollections removes all collections func (pcl *PlayerCollectionList) ClearCollections() { pcl.mu.Lock() defer pcl.mu.Unlock() pcl.collections = make(map[int32]*Collection) } // Size returns the number of collections func (pcl *PlayerCollectionList) Size() int { pcl.mu.RLock() defer pcl.mu.RUnlock() return len(pcl.collections) } // NeedsItem checks if any player collection or potential collection needs an item func (pcl *PlayerCollectionList) NeedsItem(itemID int32, masterList *MasterCollectionList) bool { pcl.mu.RLock() defer pcl.mu.RUnlock() // Check player's active collections first for _, collection := range pcl.collections { if collection.NeedsItem(itemID) { return true } } // Check if any master collection the player doesn't have needs this item if masterList != nil { for _, masterCollection := range masterList.GetAllCollections() { if masterCollection.NeedsItem(itemID) { // Player doesn't have this collection yet if _, hasCollection := pcl.collections[masterCollection.GetID()]; !hasCollection { return true } } } } return false } // HasCollectionsToHandIn checks if any collections are ready to turn in func (pcl *PlayerCollectionList) HasCollectionsToHandIn() bool { pcl.mu.RLock() defer pcl.mu.RUnlock() for _, collection := range pcl.collections { if collection.GetIsReadyToTurnIn() { return true } } return false } // GetCollectionsReadyToTurnIn returns collections that are ready to complete func (pcl *PlayerCollectionList) GetCollectionsReadyToTurnIn() []*Collection { pcl.mu.RLock() defer pcl.mu.RUnlock() var result []*Collection for _, collection := range pcl.collections { if collection.GetIsReadyToTurnIn() { result = append(result, collection) } } return result } // GetCompletedCollections returns all completed collections func (pcl *PlayerCollectionList) GetCompletedCollections() []*Collection { pcl.mu.RLock() defer pcl.mu.RUnlock() var result []*Collection for _, collection := range pcl.collections { if collection.GetCompleted() { result = append(result, collection) } } return result } // GetActiveCollections returns all active (incomplete) collections func (pcl *PlayerCollectionList) GetActiveCollections() []*Collection { pcl.mu.RLock() defer pcl.mu.RUnlock() var result []*Collection for _, collection := range pcl.collections { if !collection.GetCompleted() { result = append(result, collection) } } return result } // GetAllCollections returns all player collections func (pcl *PlayerCollectionList) GetAllCollections() []*Collection { pcl.mu.RLock() defer pcl.mu.RUnlock() result := make([]*Collection, 0, len(pcl.collections)) for _, collection := range pcl.collections { result = append(result, collection) } return result } // GetCollectionsByCategory returns collections in a specific category func (pcl *PlayerCollectionList) GetCollectionsByCategory(category string) []*Collection { pcl.mu.RLock() defer pcl.mu.RUnlock() var result []*Collection for _, collection := range pcl.collections { if collection.GetCategory() == category { result = append(result, collection) } } return result } // ProcessItemFound processes when a player finds an item that may belong to collections func (pcl *PlayerCollectionList) ProcessItemFound(itemID int32, masterList *MasterCollectionList) ([]*Collection, error) { pcl.mu.Lock() defer pcl.mu.Unlock() var updatedCollections []*Collection // Check existing player collections for _, collection := range pcl.collections { if collection.NeedsItem(itemID) { if collection.MarkItemFound(itemID) { updatedCollections = append(updatedCollections, collection) } } } // Check if player should start new collections if masterList != nil { for _, masterCollection := range masterList.GetAllCollections() { // Skip if player already has this collection if _, hasCollection := pcl.collections[masterCollection.GetID()]; hasCollection { continue } // Check if master collection needs this item if masterCollection.NeedsItem(itemID) { // Create new collection for player newCollection := NewCollectionFromData(masterCollection) if newCollection != nil { newCollection.MarkItemFound(itemID) pcl.collections[masterCollection.GetID()] = newCollection updatedCollections = append(updatedCollections, newCollection) } } } } return updatedCollections, nil } // CompleteCollection marks a collection as completed func (pcl *PlayerCollectionList) CompleteCollection(collectionID int32) error { pcl.mu.Lock() defer pcl.mu.Unlock() collection, exists := pcl.collections[collectionID] if !exists { return fmt.Errorf("collection %d not found", collectionID) } if collection.GetCompleted() { return fmt.Errorf("collection %d is already completed", collectionID) } if !collection.GetIsReadyToTurnIn() { return fmt.Errorf("collection %d is not ready to complete", collectionID) } collection.SetCompleted(true) collection.SetSaveNeeded(true) return nil } // GetCollectionsNeedingSave returns collections that need to be saved func (pcl *PlayerCollectionList) GetCollectionsNeedingSave() []*Collection { pcl.mu.RLock() defer pcl.mu.RUnlock() var result []*Collection for _, collection := range pcl.collections { if collection.GetSaveNeeded() { result = append(result, collection) } } return result } // SaveCollections saves all collections that need saving func (pcl *PlayerCollectionList) SaveCollections(ctx context.Context) error { collectionsToSave := pcl.GetCollectionsNeedingSave() if len(collectionsToSave) == 0 { return nil } if err := pcl.database.SavePlayerCollections(ctx, pcl.characterID, collectionsToSave); err != nil { return fmt.Errorf("failed to save player collections: %w", err) } // Mark collections as saved for _, collection := range collectionsToSave { collection.SetSaveNeeded(false) } return nil } // GetStatistics returns player collection statistics func (pcl *PlayerCollectionList) GetStatistics() CollectionStatistics { pcl.mu.RLock() defer pcl.mu.RUnlock() stats := CollectionStatistics{ TotalCollections: len(pcl.collections), PlayersWithCollections: 1, // This player } for _, collection := range pcl.collections { if collection.GetCompleted() { stats.CompletedCollections++ } if !collection.GetCompleted() && collection.GetFoundItemsCount() > 0 { stats.ActiveCollections++ } stats.TotalItems += collection.GetTotalItemsCount() stats.FoundItems += collection.GetFoundItemsCount() stats.TotalRewards += len(collection.GetRewardItems()) + len(collection.GetSelectableRewardItems()) if collection.GetRewardCoin() > 0 { stats.TotalRewards++ } if collection.GetRewardXP() > 0 { stats.TotalRewards++ } } return stats } // GetCollectionProgress returns detailed progress for all collections func (pcl *PlayerCollectionList) GetCollectionProgress() []CollectionProgress { pcl.mu.RLock() defer pcl.mu.RUnlock() progress := make([]CollectionProgress, 0, len(pcl.collections)) for _, collection := range pcl.collections { progress = append(progress, collection.GetCollectionProgress()) } // Sort by name sort.Slice(progress, func(i, j int) bool { return progress[i].Name < progress[j].Name }) return progress } // GetCharacterID returns the character ID for this collection list func (pcl *PlayerCollectionList) GetCharacterID() int32 { return pcl.characterID } // RemoveCollection removes a collection from the player's list func (pcl *PlayerCollectionList) RemoveCollection(collectionID int32) bool { pcl.mu.Lock() defer pcl.mu.Unlock() if _, exists := pcl.collections[collectionID]; exists { delete(pcl.collections, collectionID) return true } return false } // GetCollectionIDs returns all collection IDs the player has func (pcl *PlayerCollectionList) GetCollectionIDs() []int32 { pcl.mu.RLock() defer pcl.mu.RUnlock() ids := make([]int32, 0, len(pcl.collections)) for id := range pcl.collections { ids = append(ids, id) } sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] }) return ids }