335 lines
8.6 KiB
Go
335 lines
8.6 KiB
Go
package collections
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sort"
|
|
)
|
|
|
|
// NewMasterCollectionList creates a new master collection list
|
|
func NewMasterCollectionList(database CollectionDatabase) *MasterCollectionList {
|
|
return &MasterCollectionList{
|
|
collections: make(map[int32]*Collection),
|
|
database: database,
|
|
}
|
|
}
|
|
|
|
// Initialize loads all collections from the database
|
|
func (mcl *MasterCollectionList) Initialize(ctx context.Context, itemLookup ItemLookup) error {
|
|
mcl.mu.Lock()
|
|
defer mcl.mu.Unlock()
|
|
|
|
// Load collection data
|
|
collectionData, err := mcl.database.LoadCollections(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load collections: %w", err)
|
|
}
|
|
|
|
totalItems := 0
|
|
totalRewards := 0
|
|
|
|
for _, data := range collectionData {
|
|
collection := NewCollection()
|
|
collection.SetID(data.ID)
|
|
collection.SetName(data.Name)
|
|
collection.SetCategory(data.Category)
|
|
collection.SetLevel(data.Level)
|
|
|
|
// Load collection items
|
|
items, err := mcl.database.LoadCollectionItems(ctx, data.ID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load items for collection %d: %w", data.ID, err)
|
|
}
|
|
|
|
for _, item := range items {
|
|
// Validate item exists
|
|
if itemLookup != nil && !itemLookup.ItemExists(item.ItemID) {
|
|
continue // Skip non-existent items
|
|
}
|
|
collection.AddCollectionItem(item)
|
|
totalItems++
|
|
}
|
|
|
|
// Load collection rewards
|
|
rewards, err := mcl.database.LoadCollectionRewards(ctx, data.ID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load rewards for collection %d: %w", data.ID, err)
|
|
}
|
|
|
|
if err := collection.LoadFromRewardData(rewards); err != nil {
|
|
return fmt.Errorf("failed to load reward data for collection %d: %w", data.ID, err)
|
|
}
|
|
totalRewards += len(rewards)
|
|
|
|
// Validate collection before adding
|
|
if err := collection.Validate(); err != nil {
|
|
return fmt.Errorf("invalid collection %d (%s): %w", data.ID, data.Name, err)
|
|
}
|
|
|
|
if !mcl.addCollectionNoLock(collection) {
|
|
return fmt.Errorf("duplicate collection ID: %d", data.ID)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// AddCollection adds a collection to the master list
|
|
func (mcl *MasterCollectionList) AddCollection(collection *Collection) bool {
|
|
mcl.mu.Lock()
|
|
defer mcl.mu.Unlock()
|
|
return mcl.addCollectionNoLock(collection)
|
|
}
|
|
|
|
// addCollectionNoLock adds a collection without acquiring the lock
|
|
func (mcl *MasterCollectionList) addCollectionNoLock(collection *Collection) bool {
|
|
if collection == nil {
|
|
return false
|
|
}
|
|
|
|
id := collection.GetID()
|
|
if _, exists := mcl.collections[id]; exists {
|
|
return false
|
|
}
|
|
|
|
mcl.collections[id] = collection
|
|
return true
|
|
}
|
|
|
|
// GetCollection retrieves a collection by ID
|
|
func (mcl *MasterCollectionList) GetCollection(collectionID int32) *Collection {
|
|
mcl.mu.RLock()
|
|
defer mcl.mu.RUnlock()
|
|
return mcl.collections[collectionID]
|
|
}
|
|
|
|
// GetCollectionCopy retrieves a copy of a collection by ID
|
|
func (mcl *MasterCollectionList) GetCollectionCopy(collectionID int32) *Collection {
|
|
mcl.mu.RLock()
|
|
defer mcl.mu.RUnlock()
|
|
|
|
if collection, exists := mcl.collections[collectionID]; exists {
|
|
return NewCollectionFromData(collection)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ClearCollections removes all collections
|
|
func (mcl *MasterCollectionList) ClearCollections() {
|
|
mcl.mu.Lock()
|
|
defer mcl.mu.Unlock()
|
|
mcl.collections = make(map[int32]*Collection)
|
|
}
|
|
|
|
// Size returns the number of collections
|
|
func (mcl *MasterCollectionList) Size() int {
|
|
mcl.mu.RLock()
|
|
defer mcl.mu.RUnlock()
|
|
return len(mcl.collections)
|
|
}
|
|
|
|
// NeedsItem checks if any collection needs a specific item
|
|
func (mcl *MasterCollectionList) NeedsItem(itemID int32) bool {
|
|
mcl.mu.RLock()
|
|
defer mcl.mu.RUnlock()
|
|
|
|
for _, collection := range mcl.collections {
|
|
if collection.NeedsItem(itemID) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// GetCollectionsByCategory returns collections in a specific category
|
|
func (mcl *MasterCollectionList) GetCollectionsByCategory(category string) []*Collection {
|
|
mcl.mu.RLock()
|
|
defer mcl.mu.RUnlock()
|
|
|
|
var result []*Collection
|
|
for _, collection := range mcl.collections {
|
|
if collection.GetCategory() == category {
|
|
result = append(result, collection)
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// GetCollectionsByLevel returns collections for a specific level range
|
|
func (mcl *MasterCollectionList) GetCollectionsByLevel(minLevel, maxLevel int8) []*Collection {
|
|
mcl.mu.RLock()
|
|
defer mcl.mu.RUnlock()
|
|
|
|
var result []*Collection
|
|
for _, collection := range mcl.collections {
|
|
level := collection.GetLevel()
|
|
if level >= minLevel && level <= maxLevel {
|
|
result = append(result, collection)
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// GetAllCollections returns all collections
|
|
func (mcl *MasterCollectionList) GetAllCollections() []*Collection {
|
|
mcl.mu.RLock()
|
|
defer mcl.mu.RUnlock()
|
|
|
|
result := make([]*Collection, 0, len(mcl.collections))
|
|
for _, collection := range mcl.collections {
|
|
result = append(result, collection)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// GetCollectionIDs returns all collection IDs
|
|
func (mcl *MasterCollectionList) GetCollectionIDs() []int32 {
|
|
mcl.mu.RLock()
|
|
defer mcl.mu.RUnlock()
|
|
|
|
ids := make([]int32, 0, len(mcl.collections))
|
|
for id := range mcl.collections {
|
|
ids = append(ids, id)
|
|
}
|
|
|
|
sort.Slice(ids, func(i, j int) bool {
|
|
return ids[i] < ids[j]
|
|
})
|
|
|
|
return ids
|
|
}
|
|
|
|
// GetCategories returns all unique categories
|
|
func (mcl *MasterCollectionList) GetCategories() []string {
|
|
mcl.mu.RLock()
|
|
defer mcl.mu.RUnlock()
|
|
|
|
categoryMap := make(map[string]bool)
|
|
for _, collection := range mcl.collections {
|
|
category := collection.GetCategory()
|
|
if category != "" {
|
|
categoryMap[category] = true
|
|
}
|
|
}
|
|
|
|
categories := make([]string, 0, len(categoryMap))
|
|
for category := range categoryMap {
|
|
categories = append(categories, category)
|
|
}
|
|
|
|
sort.Strings(categories)
|
|
return categories
|
|
}
|
|
|
|
// GetCollectionsRequiringItem returns collections that need a specific item
|
|
func (mcl *MasterCollectionList) GetCollectionsRequiringItem(itemID int32) []*Collection {
|
|
mcl.mu.RLock()
|
|
defer mcl.mu.RUnlock()
|
|
|
|
var result []*Collection
|
|
for _, collection := range mcl.collections {
|
|
if collection.NeedsItem(itemID) {
|
|
result = append(result, collection)
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// GetStatistics returns master collection list statistics
|
|
func (mcl *MasterCollectionList) GetStatistics() CollectionStatistics {
|
|
mcl.mu.RLock()
|
|
defer mcl.mu.RUnlock()
|
|
|
|
stats := CollectionStatistics{
|
|
TotalCollections: len(mcl.collections),
|
|
}
|
|
|
|
for _, collection := range mcl.collections {
|
|
if collection.GetCompleted() {
|
|
stats.CompletedCollections++
|
|
}
|
|
if collection.GetTotalItemsCount() > 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
|
|
}
|
|
|
|
// ValidateIntegrity checks the integrity of all collections
|
|
func (mcl *MasterCollectionList) ValidateIntegrity(itemLookup ItemLookup) []error {
|
|
mcl.mu.RLock()
|
|
defer mcl.mu.RUnlock()
|
|
|
|
var errors []error
|
|
|
|
for _, collection := range mcl.collections {
|
|
if err := collection.Validate(); err != nil {
|
|
errors = append(errors, fmt.Errorf("collection %d (%s): %w",
|
|
collection.GetID(), collection.GetName(), err))
|
|
}
|
|
|
|
// Check if all required items exist
|
|
if itemLookup != nil {
|
|
for _, item := range collection.GetCollectionItems() {
|
|
if !itemLookup.ItemExists(item.ItemID) {
|
|
errors = append(errors, fmt.Errorf("collection %d (%s) references non-existent item %d",
|
|
collection.GetID(), collection.GetName(), item.ItemID))
|
|
}
|
|
}
|
|
|
|
// Check reward items
|
|
for _, item := range collection.GetRewardItems() {
|
|
if !itemLookup.ItemExists(item.ItemID) {
|
|
errors = append(errors, fmt.Errorf("collection %d (%s) has non-existent reward item %d",
|
|
collection.GetID(), collection.GetName(), item.ItemID))
|
|
}
|
|
}
|
|
|
|
for _, item := range collection.GetSelectableRewardItems() {
|
|
if !itemLookup.ItemExists(item.ItemID) {
|
|
errors = append(errors, fmt.Errorf("collection %d (%s) has non-existent selectable reward item %d",
|
|
collection.GetID(), collection.GetName(), item.ItemID))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return errors
|
|
}
|
|
|
|
// FindCollectionsByName searches for collections by name (case-insensitive)
|
|
func (mcl *MasterCollectionList) FindCollectionsByName(searchTerm string) []*Collection {
|
|
mcl.mu.RLock()
|
|
defer mcl.mu.RUnlock()
|
|
|
|
var result []*Collection
|
|
searchLower := strings.ToLower(searchTerm)
|
|
|
|
for _, collection := range mcl.collections {
|
|
if strings.Contains(strings.ToLower(collection.GetName()), searchLower) {
|
|
result = append(result, collection)
|
|
}
|
|
}
|
|
|
|
// Sort by name
|
|
sort.Slice(result, func(i, j int) bool {
|
|
return result[i].GetName() < result[j].GetName()
|
|
})
|
|
|
|
return result
|
|
} |