737 lines
21 KiB
Go
737 lines
21 KiB
Go
package collections
|
|
|
|
import (
|
|
"fmt"
|
|
"maps"
|
|
"strings"
|
|
"sync"
|
|
|
|
"eq2emu/internal/database"
|
|
)
|
|
|
|
// MasterList is a specialized collection master list optimized for:
|
|
// - Fast ID-based lookups (O(1))
|
|
// - Fast category filtering (O(1))
|
|
// - Fast level range queries (indexed)
|
|
// - Fast item requirement lookups (O(1))
|
|
// - Efficient completion status filtering
|
|
// - Name-based searching with indexing
|
|
type MasterList struct {
|
|
// Core storage
|
|
collections map[int32]*Collection // ID -> Collection
|
|
mutex sync.RWMutex
|
|
|
|
// Specialized indices for O(1) lookups
|
|
byCategory map[string][]*Collection // Category -> collections
|
|
byLevel map[int8][]*Collection // Level -> collections
|
|
byItemNeeded map[int32][]*Collection // ItemID -> collections that need it
|
|
byNameLower map[string]*Collection // Lowercase name -> collection
|
|
byCompletion map[bool][]*Collection // Completion status -> collections
|
|
|
|
// Cached metadata
|
|
categories []string // Unique categories (cached)
|
|
levels []int8 // Unique levels (cached)
|
|
itemsNeeded []int32 // Unique items needed (cached)
|
|
categoryStats map[string]int // Category -> count
|
|
metaStale bool // Whether metadata cache needs refresh
|
|
}
|
|
|
|
// NewMasterList creates a new specialized collection master list
|
|
func NewMasterList() *MasterList {
|
|
return &MasterList{
|
|
collections: make(map[int32]*Collection),
|
|
byCategory: make(map[string][]*Collection),
|
|
byLevel: make(map[int8][]*Collection),
|
|
byItemNeeded: make(map[int32][]*Collection),
|
|
byNameLower: make(map[string]*Collection),
|
|
byCompletion: make(map[bool][]*Collection),
|
|
categoryStats: make(map[string]int),
|
|
metaStale: true,
|
|
}
|
|
}
|
|
|
|
// refreshMetaCache updates the cached metadata
|
|
func (ml *MasterList) refreshMetaCache() {
|
|
if !ml.metaStale {
|
|
return
|
|
}
|
|
|
|
// Clear and rebuild category stats
|
|
ml.categoryStats = make(map[string]int)
|
|
categorySet := make(map[string]struct{})
|
|
levelSet := make(map[int8]struct{})
|
|
itemSet := make(map[int32]struct{})
|
|
|
|
// Collect unique values and stats
|
|
for _, collection := range ml.collections {
|
|
category := collection.GetCategory()
|
|
ml.categoryStats[category]++
|
|
categorySet[category] = struct{}{}
|
|
levelSet[collection.GetLevel()] = struct{}{}
|
|
|
|
// Collect items needed by this collection
|
|
for _, item := range collection.CollectionItems {
|
|
if item.Found == ItemNotFound {
|
|
itemSet[item.ItemID] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Clear and rebuild cached slices
|
|
ml.categories = ml.categories[:0]
|
|
for category := range categorySet {
|
|
ml.categories = append(ml.categories, category)
|
|
}
|
|
|
|
ml.levels = ml.levels[:0]
|
|
for level := range levelSet {
|
|
ml.levels = append(ml.levels, level)
|
|
}
|
|
|
|
ml.itemsNeeded = ml.itemsNeeded[:0]
|
|
for itemID := range itemSet {
|
|
ml.itemsNeeded = append(ml.itemsNeeded, itemID)
|
|
}
|
|
|
|
ml.metaStale = false
|
|
}
|
|
|
|
// updateCollectionIndices updates all indices for a collection
|
|
func (ml *MasterList) updateCollectionIndices(collection *Collection, add bool) {
|
|
if add {
|
|
// Add to category index
|
|
category := collection.GetCategory()
|
|
ml.byCategory[category] = append(ml.byCategory[category], collection)
|
|
|
|
// Add to level index
|
|
level := collection.GetLevel()
|
|
ml.byLevel[level] = append(ml.byLevel[level], collection)
|
|
|
|
// Add to name index
|
|
ml.byNameLower[strings.ToLower(collection.GetName())] = collection
|
|
|
|
// Add to completion index
|
|
completed := collection.Completed
|
|
ml.byCompletion[completed] = append(ml.byCompletion[completed], collection)
|
|
|
|
// Add to item needed index
|
|
for _, item := range collection.CollectionItems {
|
|
if item.Found == ItemNotFound {
|
|
ml.byItemNeeded[item.ItemID] = append(ml.byItemNeeded[item.ItemID], collection)
|
|
}
|
|
}
|
|
} else {
|
|
// Remove from category index
|
|
category := collection.GetCategory()
|
|
categoryCollections := ml.byCategory[category]
|
|
for i, coll := range categoryCollections {
|
|
if coll.ID == collection.ID {
|
|
ml.byCategory[category] = append(categoryCollections[:i], categoryCollections[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
|
|
// Remove from level index
|
|
level := collection.GetLevel()
|
|
levelCollections := ml.byLevel[level]
|
|
for i, coll := range levelCollections {
|
|
if coll.ID == collection.ID {
|
|
ml.byLevel[level] = append(levelCollections[:i], levelCollections[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
|
|
// Remove from name index
|
|
delete(ml.byNameLower, strings.ToLower(collection.GetName()))
|
|
|
|
// Remove from completion index
|
|
completed := collection.Completed
|
|
completionCollections := ml.byCompletion[completed]
|
|
for i, coll := range completionCollections {
|
|
if coll.ID == collection.ID {
|
|
ml.byCompletion[completed] = append(completionCollections[:i], completionCollections[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
|
|
// Remove from item needed index
|
|
for _, item := range collection.CollectionItems {
|
|
if item.Found == ItemNotFound {
|
|
itemCollections := ml.byItemNeeded[item.ItemID]
|
|
for i, coll := range itemCollections {
|
|
if coll.ID == collection.ID {
|
|
ml.byItemNeeded[item.ItemID] = append(itemCollections[:i], itemCollections[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// AddCollection adds a collection with full indexing
|
|
func (ml *MasterList) AddCollection(collection *Collection) bool {
|
|
if collection == nil {
|
|
return false
|
|
}
|
|
|
|
ml.mutex.Lock()
|
|
defer ml.mutex.Unlock()
|
|
|
|
// Check if exists
|
|
if _, exists := ml.collections[collection.ID]; exists {
|
|
return false
|
|
}
|
|
|
|
// Add to core storage
|
|
ml.collections[collection.ID] = collection
|
|
|
|
// Update all indices
|
|
ml.updateCollectionIndices(collection, true)
|
|
|
|
// Invalidate metadata cache
|
|
ml.metaStale = true
|
|
|
|
return true
|
|
}
|
|
|
|
// GetCollection retrieves by ID (O(1))
|
|
func (ml *MasterList) GetCollection(id int32) *Collection {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
return ml.collections[id]
|
|
}
|
|
|
|
// GetCollectionSafe retrieves a collection by ID with existence check
|
|
func (ml *MasterList) GetCollectionSafe(id int32) (*Collection, bool) {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
collection, exists := ml.collections[id]
|
|
return collection, exists
|
|
}
|
|
|
|
// HasCollection checks if a collection exists by ID
|
|
func (ml *MasterList) HasCollection(id int32) bool {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
_, exists := ml.collections[id]
|
|
return exists
|
|
}
|
|
|
|
// GetCollectionClone retrieves a cloned copy of a collection by ID
|
|
func (ml *MasterList) GetCollectionClone(id int32) *Collection {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
collection := ml.collections[id]
|
|
if collection == nil {
|
|
return nil
|
|
}
|
|
return collection.Clone()
|
|
}
|
|
|
|
// RemoveCollection removes a collection and updates all indices
|
|
func (ml *MasterList) RemoveCollection(id int32) bool {
|
|
ml.mutex.Lock()
|
|
defer ml.mutex.Unlock()
|
|
|
|
collection, exists := ml.collections[id]
|
|
if !exists {
|
|
return false
|
|
}
|
|
|
|
// Remove from core storage
|
|
delete(ml.collections, id)
|
|
|
|
// Update all indices
|
|
ml.updateCollectionIndices(collection, false)
|
|
|
|
// Invalidate metadata cache
|
|
ml.metaStale = true
|
|
|
|
return true
|
|
}
|
|
|
|
// GetAllCollections returns a copy of all collections map
|
|
func (ml *MasterList) GetAllCollections() map[int32]*Collection {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
|
|
// Return a copy to prevent external modification
|
|
result := make(map[int32]*Collection, len(ml.collections))
|
|
maps.Copy(result, ml.collections)
|
|
return result
|
|
}
|
|
|
|
// GetAllCollectionsList returns all collections as a slice
|
|
func (ml *MasterList) GetAllCollectionsList() []*Collection {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
|
|
result := make([]*Collection, 0, len(ml.collections))
|
|
for _, collection := range ml.collections {
|
|
result = append(result, collection)
|
|
}
|
|
return result
|
|
}
|
|
|
|
// GetCollectionCount returns the number of collections
|
|
func (ml *MasterList) GetCollectionCount() int {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
return len(ml.collections)
|
|
}
|
|
|
|
// Size returns the total number of collections
|
|
func (ml *MasterList) Size() int {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
return len(ml.collections)
|
|
}
|
|
|
|
// IsEmpty returns true if the master list is empty
|
|
func (ml *MasterList) IsEmpty() bool {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
return len(ml.collections) == 0
|
|
}
|
|
|
|
// ClearCollections removes all collections from the list
|
|
func (ml *MasterList) ClearCollections() {
|
|
ml.mutex.Lock()
|
|
defer ml.mutex.Unlock()
|
|
|
|
// Clear all maps
|
|
ml.collections = make(map[int32]*Collection)
|
|
ml.byCategory = make(map[string][]*Collection)
|
|
ml.byLevel = make(map[int8][]*Collection)
|
|
ml.byItemNeeded = make(map[int32][]*Collection)
|
|
ml.byNameLower = make(map[string]*Collection)
|
|
ml.byCompletion = make(map[bool][]*Collection)
|
|
|
|
// Clear cached metadata
|
|
ml.categories = ml.categories[:0]
|
|
ml.levels = ml.levels[:0]
|
|
ml.itemsNeeded = ml.itemsNeeded[:0]
|
|
ml.categoryStats = make(map[string]int)
|
|
ml.metaStale = true
|
|
}
|
|
|
|
// Clear removes all collections from the master list
|
|
func (ml *MasterList) Clear() {
|
|
ml.ClearCollections()
|
|
}
|
|
|
|
// FindCollectionsByCategory finds collections in a specific category (O(1))
|
|
func (ml *MasterList) FindCollectionsByCategory(category string) []*Collection {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
return ml.byCategory[category]
|
|
}
|
|
|
|
// FindCollectionsByLevel finds collections for a specific level range
|
|
func (ml *MasterList) FindCollectionsByLevel(minLevel, maxLevel int8) []*Collection {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
|
|
var result []*Collection
|
|
for level := minLevel; level <= maxLevel; level++ {
|
|
result = append(result, ml.byLevel[level]...)
|
|
}
|
|
return result
|
|
}
|
|
|
|
// GetCollectionsByExactLevel returns collections with specific level (O(1))
|
|
func (ml *MasterList) GetCollectionsByExactLevel(level int8) []*Collection {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
return ml.byLevel[level]
|
|
}
|
|
|
|
// GetCollectionByName retrieves a collection by name (case-insensitive, O(1))
|
|
func (ml *MasterList) GetCollectionByName(name string) *Collection {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
return ml.byNameLower[strings.ToLower(name)]
|
|
}
|
|
|
|
// NeedsItem checks if any collection needs the specified item (O(1))
|
|
func (ml *MasterList) NeedsItem(itemID int32) bool {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
collections := ml.byItemNeeded[itemID]
|
|
return len(collections) > 0
|
|
}
|
|
|
|
// GetCollectionsNeedingItem returns all collections that need a specific item (O(1))
|
|
func (ml *MasterList) GetCollectionsNeedingItem(itemID int32) []*Collection {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
return ml.byItemNeeded[itemID]
|
|
}
|
|
|
|
// GetCompletedCollections returns all completed collections (O(1))
|
|
func (ml *MasterList) GetCompletedCollections() []*Collection {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
return ml.byCompletion[true]
|
|
}
|
|
|
|
// GetIncompleteCollections returns all incomplete collections (O(1))
|
|
func (ml *MasterList) GetIncompleteCollections() []*Collection {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
return ml.byCompletion[false]
|
|
}
|
|
|
|
// GetReadyToTurnInCollections returns collections ready to be turned in
|
|
func (ml *MasterList) GetReadyToTurnInCollections() []*Collection {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
|
|
var result []*Collection
|
|
for _, collection := range ml.collections {
|
|
if !collection.Completed && collection.GetIsReadyToTurnIn() {
|
|
result = append(result, collection)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// GetCollectionsByLevelRange returns collections within level range using indices
|
|
func (ml *MasterList) GetCollectionsByLevelRange(minLevel, maxLevel int8) []*Collection {
|
|
return ml.FindCollectionsByLevel(minLevel, maxLevel)
|
|
}
|
|
|
|
// GetCategories returns all unique collection categories using cached results
|
|
func (ml *MasterList) GetCategories() []string {
|
|
ml.mutex.Lock() // Need write lock to potentially update cache
|
|
defer ml.mutex.Unlock()
|
|
|
|
ml.refreshMetaCache()
|
|
|
|
// Return a copy to prevent external modification
|
|
result := make([]string, len(ml.categories))
|
|
copy(result, ml.categories)
|
|
return result
|
|
}
|
|
|
|
// GetLevels returns all unique collection levels using cached results
|
|
func (ml *MasterList) GetLevels() []int8 {
|
|
ml.mutex.Lock() // Need write lock to potentially update cache
|
|
defer ml.mutex.Unlock()
|
|
|
|
ml.refreshMetaCache()
|
|
|
|
// Return a copy to prevent external modification
|
|
result := make([]int8, len(ml.levels))
|
|
copy(result, ml.levels)
|
|
return result
|
|
}
|
|
|
|
// GetItemsNeeded returns all unique items needed by collections
|
|
func (ml *MasterList) GetItemsNeeded() []int32 {
|
|
ml.mutex.Lock() // Need write lock to potentially update cache
|
|
defer ml.mutex.Unlock()
|
|
|
|
ml.refreshMetaCache()
|
|
|
|
// Return a copy to prevent external modification
|
|
result := make([]int32, len(ml.itemsNeeded))
|
|
copy(result, ml.itemsNeeded)
|
|
return result
|
|
}
|
|
|
|
// UpdateCollection updates an existing collection and refreshes indices
|
|
func (ml *MasterList) UpdateCollection(collection *Collection) error {
|
|
if collection == nil {
|
|
return fmt.Errorf("collection cannot be nil")
|
|
}
|
|
|
|
ml.mutex.Lock()
|
|
defer ml.mutex.Unlock()
|
|
|
|
// Check if exists
|
|
old, exists := ml.collections[collection.ID]
|
|
if !exists {
|
|
return fmt.Errorf("collection %d not found", collection.ID)
|
|
}
|
|
|
|
// Remove old collection from indices (but not core storage yet)
|
|
ml.updateCollectionIndices(old, false)
|
|
|
|
// Update core storage
|
|
ml.collections[collection.ID] = collection
|
|
|
|
// Add new collection to indices
|
|
ml.updateCollectionIndices(collection, true)
|
|
|
|
// Invalidate metadata cache
|
|
ml.metaStale = true
|
|
|
|
return nil
|
|
}
|
|
|
|
// RefreshCollectionIndices refreshes indices for a collection (used when items found/completion changes)
|
|
func (ml *MasterList) RefreshCollectionIndices(collection *Collection) {
|
|
ml.mutex.Lock()
|
|
defer ml.mutex.Unlock()
|
|
|
|
// Remove from old indices
|
|
ml.updateCollectionIndices(collection, false)
|
|
// Add to new indices
|
|
ml.updateCollectionIndices(collection, true)
|
|
|
|
// Invalidate metadata cache
|
|
ml.metaStale = true
|
|
}
|
|
|
|
// ForEach executes a function for each collection
|
|
func (ml *MasterList) ForEach(fn func(int32, *Collection)) {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
|
|
for id, collection := range ml.collections {
|
|
fn(id, collection)
|
|
}
|
|
}
|
|
|
|
// ValidateCollections checks all collections for consistency
|
|
func (ml *MasterList) ValidateCollections() []string {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
|
|
var issues []string
|
|
|
|
for id, collection := range ml.collections {
|
|
if collection == nil {
|
|
issues = append(issues, fmt.Sprintf("Collection ID %d is nil", id))
|
|
continue
|
|
}
|
|
|
|
if collection.GetID() != id {
|
|
issues = append(issues, fmt.Sprintf("Collection ID mismatch: map key %d != collection ID %d", id, collection.GetID()))
|
|
}
|
|
|
|
if len(collection.GetName()) == 0 {
|
|
issues = append(issues, fmt.Sprintf("Collection ID %d has empty name", id))
|
|
}
|
|
|
|
if len(collection.GetCategory()) == 0 {
|
|
issues = append(issues, fmt.Sprintf("Collection ID %d has empty category", id))
|
|
}
|
|
|
|
if collection.GetLevel() < 0 {
|
|
issues = append(issues, fmt.Sprintf("Collection ID %d has negative level: %d", id, collection.GetLevel()))
|
|
}
|
|
|
|
if len(collection.CollectionItems) == 0 {
|
|
issues = append(issues, fmt.Sprintf("Collection ID %d has no collection items", id))
|
|
}
|
|
|
|
if len(collection.GetName()) > MaxCollectionNameLength {
|
|
issues = append(issues, fmt.Sprintf("Collection ID %d name too long: %d > %d", id, len(collection.GetName()), MaxCollectionNameLength))
|
|
}
|
|
|
|
if len(collection.GetCategory()) > MaxCollectionCategoryLength {
|
|
issues = append(issues, fmt.Sprintf("Collection ID %d category too long: %d > %d", id, len(collection.GetCategory()), MaxCollectionCategoryLength))
|
|
}
|
|
|
|
// Check for duplicate item indices
|
|
indexMap := make(map[int8]bool)
|
|
for _, item := range collection.CollectionItems {
|
|
if indexMap[item.Index] {
|
|
issues = append(issues, fmt.Sprintf("Collection ID %d has duplicate item index: %d", id, item.Index))
|
|
}
|
|
indexMap[item.Index] = true
|
|
}
|
|
}
|
|
|
|
return issues
|
|
}
|
|
|
|
// IsValid returns true if all collections are valid
|
|
func (ml *MasterList) IsValid() bool {
|
|
issues := ml.ValidateCollections()
|
|
return len(issues) == 0
|
|
}
|
|
|
|
// GetStatistics returns statistics about the collection collection using cached data
|
|
func (ml *MasterList) GetStatistics() map[string]any {
|
|
ml.mutex.Lock() // Need write lock to potentially update cache
|
|
defer ml.mutex.Unlock()
|
|
|
|
ml.refreshMetaCache()
|
|
|
|
stats := make(map[string]any)
|
|
stats["total_collections"] = len(ml.collections)
|
|
|
|
if len(ml.collections) == 0 {
|
|
return stats
|
|
}
|
|
|
|
// Use cached category stats
|
|
stats["collections_by_category"] = ml.categoryStats
|
|
|
|
// Calculate additional stats
|
|
var totalItems, totalRewards int
|
|
var completedCount, readyCount int
|
|
var minLevel, maxLevel int8 = 127, 0
|
|
var minID, maxID int32
|
|
first := true
|
|
|
|
for id, collection := range ml.collections {
|
|
totalItems += len(collection.CollectionItems)
|
|
totalRewards += len(collection.RewardItems) + len(collection.SelectableRewardItems)
|
|
|
|
if collection.Completed {
|
|
completedCount++
|
|
} else if collection.GetIsReadyToTurnIn() {
|
|
readyCount++
|
|
}
|
|
|
|
level := collection.GetLevel()
|
|
if level < minLevel {
|
|
minLevel = level
|
|
}
|
|
if level > maxLevel {
|
|
maxLevel = level
|
|
}
|
|
|
|
if first {
|
|
minID = id
|
|
maxID = id
|
|
first = false
|
|
} else {
|
|
if id < minID {
|
|
minID = id
|
|
}
|
|
if id > maxID {
|
|
maxID = id
|
|
}
|
|
}
|
|
}
|
|
|
|
stats["total_collection_items"] = totalItems
|
|
stats["total_rewards"] = totalRewards
|
|
stats["completed_collections"] = completedCount
|
|
stats["ready_to_turn_in"] = readyCount
|
|
stats["min_level"] = minLevel
|
|
stats["max_level"] = maxLevel
|
|
stats["min_id"] = minID
|
|
stats["max_id"] = maxID
|
|
stats["id_range"] = maxID - minID
|
|
stats["average_items_per_collection"] = float64(totalItems) / float64(len(ml.collections))
|
|
|
|
return stats
|
|
}
|
|
|
|
// LoadAllCollections loads all collections from the database into the master list
|
|
func (ml *MasterList) LoadAllCollections(db *database.Database) error {
|
|
if db == nil {
|
|
return fmt.Errorf("database connection is nil")
|
|
}
|
|
|
|
// Clear existing collections
|
|
ml.Clear()
|
|
|
|
query := `SELECT id, collection_name, collection_category, level FROM collections ORDER BY id`
|
|
rows, err := db.Query(query)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to query collections: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
count := 0
|
|
for rows.Next() {
|
|
collection := &Collection{
|
|
db: db,
|
|
isNew: false,
|
|
CollectionItems: make([]CollectionItem, 0),
|
|
RewardItems: make([]CollectionRewardItem, 0),
|
|
SelectableRewardItems: make([]CollectionRewardItem, 0),
|
|
}
|
|
|
|
err := rows.Scan(&collection.ID, &collection.Name, &collection.Category, &collection.Level)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to scan collection: %w", err)
|
|
}
|
|
|
|
// Load collection items for this collection
|
|
itemQuery := `SELECT item_id, item_index, found FROM collection_items WHERE collection_id = ? ORDER BY item_index`
|
|
itemRows, err := db.Query(itemQuery, collection.ID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load collection items for collection %d: %w", collection.ID, err)
|
|
}
|
|
|
|
for itemRows.Next() {
|
|
var item CollectionItem
|
|
if err := itemRows.Scan(&item.ItemID, &item.Index, &item.Found); err != nil {
|
|
itemRows.Close()
|
|
return fmt.Errorf("failed to scan collection item: %w", err)
|
|
}
|
|
collection.CollectionItems = append(collection.CollectionItems, item)
|
|
}
|
|
itemRows.Close()
|
|
|
|
// Load rewards for this collection
|
|
rewardQuery := `SELECT reward_type, reward_value, reward_quantity FROM collection_rewards WHERE collection_id = ?`
|
|
rewardRows, err := db.Query(rewardQuery, collection.ID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load rewards for collection %d: %w", collection.ID, err)
|
|
}
|
|
|
|
for rewardRows.Next() {
|
|
var rewardType, rewardValue string
|
|
var quantity int8
|
|
|
|
if err := rewardRows.Scan(&rewardType, &rewardValue, &quantity); err != nil {
|
|
rewardRows.Close()
|
|
return fmt.Errorf("failed to scan collection reward: %w", err)
|
|
}
|
|
|
|
switch rewardType {
|
|
case "coin":
|
|
fmt.Sscanf(rewardValue, "%d", &collection.RewardCoin)
|
|
case "xp":
|
|
fmt.Sscanf(rewardValue, "%d", &collection.RewardXP)
|
|
case "item":
|
|
var itemID int32
|
|
fmt.Sscanf(rewardValue, "%d", &itemID)
|
|
collection.RewardItems = append(collection.RewardItems, CollectionRewardItem{
|
|
ItemID: itemID,
|
|
Quantity: quantity,
|
|
})
|
|
case "selectable_item":
|
|
var itemID int32
|
|
fmt.Sscanf(rewardValue, "%d", &itemID)
|
|
collection.SelectableRewardItems = append(collection.SelectableRewardItems, CollectionRewardItem{
|
|
ItemID: itemID,
|
|
Quantity: quantity,
|
|
})
|
|
}
|
|
}
|
|
rewardRows.Close()
|
|
|
|
if !ml.AddCollection(collection) {
|
|
return fmt.Errorf("failed to add collection %d to master list", collection.ID)
|
|
}
|
|
|
|
count++
|
|
}
|
|
|
|
if err := rows.Err(); err != nil {
|
|
return fmt.Errorf("error iterating collection rows: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// LoadAllCollectionsFromDatabase is a convenience function that creates a master list and loads all collections
|
|
func LoadAllCollectionsFromDatabase(db *database.Database) (*MasterList, error) {
|
|
masterList := NewMasterList()
|
|
err := masterList.LoadAllCollections(db)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return masterList, nil
|
|
} |