327 lines
9.2 KiB
Go

package collections
import (
"fmt"
"eq2emu/internal/common"
"eq2emu/internal/database"
)
// MasterList manages a collection of collections using the generic MasterList base
type MasterList struct {
*common.MasterList[int32, *Collection]
}
// NewMasterList creates a new collection master list
func NewMasterList() *MasterList {
return &MasterList{
MasterList: common.NewMasterList[int32, *Collection](),
}
}
// AddCollection adds a collection to the master list
func (ml *MasterList) AddCollection(collection *Collection) bool {
return ml.Add(collection)
}
// GetCollection retrieves a collection by ID
func (ml *MasterList) GetCollection(id int32) *Collection {
return ml.Get(id)
}
// GetCollectionSafe retrieves a collection by ID with existence check
func (ml *MasterList) GetCollectionSafe(id int32) (*Collection, bool) {
return ml.GetSafe(id)
}
// HasCollection checks if a collection exists by ID
func (ml *MasterList) HasCollection(id int32) bool {
return ml.Exists(id)
}
// RemoveCollection removes a collection by ID
func (ml *MasterList) RemoveCollection(id int32) bool {
return ml.Remove(id)
}
// GetAllCollections returns all collections as a map
func (ml *MasterList) GetAllCollections() map[int32]*Collection {
return ml.GetAll()
}
// GetAllCollectionsList returns all collections as a slice
func (ml *MasterList) GetAllCollectionsList() []*Collection {
return ml.GetAllSlice()
}
// GetCollectionCount returns the number of collections
func (ml *MasterList) GetCollectionCount() int {
return ml.Size()
}
// ClearCollections removes all collections from the list
func (ml *MasterList) ClearCollections() {
ml.Clear()
}
// NeedsItem checks if any collection needs the specified item
func (ml *MasterList) NeedsItem(itemID int32) bool {
for _, collection := range ml.GetAll() {
if collection.NeedsItem(itemID) {
return true
}
}
return false
}
// FindCollectionsByCategory finds collections in a specific category
func (ml *MasterList) FindCollectionsByCategory(category string) []*Collection {
return ml.Filter(func(collection *Collection) bool {
return collection.GetCategory() == category
})
}
// FindCollectionsByLevel finds collections for a specific level range
func (ml *MasterList) FindCollectionsByLevel(minLevel, maxLevel int8) []*Collection {
return ml.Filter(func(collection *Collection) bool {
level := collection.GetLevel()
return level >= minLevel && level <= maxLevel
})
}
// GetCollectionsNeedingItem returns all collections that need a specific item
func (ml *MasterList) GetCollectionsNeedingItem(itemID int32) []*Collection {
return ml.Filter(func(collection *Collection) bool {
return collection.NeedsItem(itemID)
})
}
// GetCategories returns all unique collection categories
func (ml *MasterList) GetCategories() []string {
categoryMap := make(map[string]bool)
ml.ForEach(func(id int32, collection *Collection) {
categoryMap[collection.GetCategory()] = true
})
categories := make([]string, 0, len(categoryMap))
for category := range categoryMap {
categories = append(categories, category)
}
return categories
}
// ValidateCollections checks all collections for consistency
func (ml *MasterList) ValidateCollections() []string {
var issues []string
ml.ForEach(func(id int32, collection *Collection) {
if collection == nil {
issues = append(issues, fmt.Sprintf("Collection ID %d is nil", id))
return
}
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))
}
// 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
func (ml *MasterList) GetStatistics() map[string]any {
stats := make(map[string]any)
stats["total_collections"] = ml.Size()
if ml.IsEmpty() {
return stats
}
// Count by category
categoryCounts := make(map[string]int)
var totalItems, totalRewards int
var minLevel, maxLevel int8 = 127, 0
var minID, maxID int32
first := true
ml.ForEach(func(id int32, collection *Collection) {
categoryCounts[collection.GetCategory()]++
totalItems += len(collection.CollectionItems)
totalRewards += len(collection.RewardItems) + len(collection.SelectableRewardItems)
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["collections_by_category"] = categoryCounts
stats["total_collection_items"] = totalItems
stats["total_rewards"] = totalRewards
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(ml.Size())
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
}