398 lines
10 KiB
Go
398 lines
10 KiB
Go
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
|
|
}
|