eq2go/internal/collections/player_list.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
}