package collections import ( "fmt" "strconv" "strings" "time" ) // NewCollection creates a new collection instance func NewCollection() *Collection { return &Collection{ collectionItems: make([]CollectionItem, 0), rewardItems: make([]CollectionRewardItem, 0), selectableRewardItems: make([]CollectionRewardItem, 0), lastModified: time.Now(), } } // NewCollectionFromData creates a collection from another collection (copy constructor) func NewCollectionFromData(source *Collection) *Collection { if source == nil { return nil } source.mu.RLock() defer source.mu.RUnlock() collection := &Collection{ id: source.id, name: source.name, category: source.category, level: source.level, rewardCoin: source.rewardCoin, rewardXP: source.rewardXP, completed: source.completed, saveNeeded: source.saveNeeded, collectionItems: make([]CollectionItem, len(source.collectionItems)), rewardItems: make([]CollectionRewardItem, len(source.rewardItems)), selectableRewardItems: make([]CollectionRewardItem, len(source.selectableRewardItems)), lastModified: time.Now(), } // Deep copy collection items copy(collection.collectionItems, source.collectionItems) // Deep copy reward items copy(collection.rewardItems, source.rewardItems) // Deep copy selectable reward items copy(collection.selectableRewardItems, source.selectableRewardItems) return collection } // SetID sets the collection ID func (c *Collection) SetID(id int32) { c.mu.Lock() defer c.mu.Unlock() c.id = id } // SetName sets the collection name func (c *Collection) SetName(name string) { c.mu.Lock() defer c.mu.Unlock() if len(name) > MaxCollectionNameLength { name = name[:MaxCollectionNameLength] } c.name = name } // SetCategory sets the collection category func (c *Collection) SetCategory(category string) { c.mu.Lock() defer c.mu.Unlock() if len(category) > MaxCollectionCategoryLength { category = category[:MaxCollectionCategoryLength] } c.category = category } // SetLevel sets the collection level func (c *Collection) SetLevel(level int8) { c.mu.Lock() defer c.mu.Unlock() c.level = level } // SetCompleted sets the collection completion status func (c *Collection) SetCompleted(completed bool) { c.mu.Lock() defer c.mu.Unlock() c.completed = completed c.lastModified = time.Now() } // SetSaveNeeded sets whether the collection needs to be saved func (c *Collection) SetSaveNeeded(saveNeeded bool) { c.mu.Lock() defer c.mu.Unlock() c.saveNeeded = saveNeeded } // SetRewardCoin sets the coin reward amount func (c *Collection) SetRewardCoin(coin int64) { c.mu.Lock() defer c.mu.Unlock() c.rewardCoin = coin } // SetRewardXP sets the XP reward amount func (c *Collection) SetRewardXP(xp int64) { c.mu.Lock() defer c.mu.Unlock() c.rewardXP = xp } // AddCollectionItem adds a required item to the collection func (c *Collection) AddCollectionItem(item CollectionItem) { c.mu.Lock() defer c.mu.Unlock() c.collectionItems = append(c.collectionItems, item) } // AddRewardItem adds a reward item to the collection func (c *Collection) AddRewardItem(item CollectionRewardItem) { c.mu.Lock() defer c.mu.Unlock() c.rewardItems = append(c.rewardItems, item) } // AddSelectableRewardItem adds a selectable reward item to the collection func (c *Collection) AddSelectableRewardItem(item CollectionRewardItem) { c.mu.Lock() defer c.mu.Unlock() c.selectableRewardItems = append(c.selectableRewardItems, item) } // GetID returns the collection ID func (c *Collection) GetID() int32 { c.mu.RLock() defer c.mu.RUnlock() return c.id } // GetName returns the collection name func (c *Collection) GetName() string { c.mu.RLock() defer c.mu.RUnlock() return c.name } // GetCategory returns the collection category func (c *Collection) GetCategory() string { c.mu.RLock() defer c.mu.RUnlock() return c.category } // GetLevel returns the collection level func (c *Collection) GetLevel() int8 { c.mu.RLock() defer c.mu.RUnlock() return c.level } // GetCompleted returns whether the collection is completed func (c *Collection) GetCompleted() bool { c.mu.RLock() defer c.mu.RUnlock() return c.completed } // GetSaveNeeded returns whether the collection needs to be saved func (c *Collection) GetSaveNeeded() bool { c.mu.RLock() defer c.mu.RUnlock() return c.saveNeeded } // GetRewardCoin returns the coin reward amount func (c *Collection) GetRewardCoin() int64 { c.mu.RLock() defer c.mu.RUnlock() return c.rewardCoin } // GetRewardXP returns the XP reward amount func (c *Collection) GetRewardXP() int64 { c.mu.RLock() defer c.mu.RUnlock() return c.rewardXP } // GetCollectionItems returns a copy of the collection items func (c *Collection) GetCollectionItems() []CollectionItem { c.mu.RLock() defer c.mu.RUnlock() items := make([]CollectionItem, len(c.collectionItems)) copy(items, c.collectionItems) return items } // GetRewardItems returns a copy of the reward items func (c *Collection) GetRewardItems() []CollectionRewardItem { c.mu.RLock() defer c.mu.RUnlock() items := make([]CollectionRewardItem, len(c.rewardItems)) copy(items, c.rewardItems) return items } // GetSelectableRewardItems returns a copy of the selectable reward items func (c *Collection) GetSelectableRewardItems() []CollectionRewardItem { c.mu.RLock() defer c.mu.RUnlock() items := make([]CollectionRewardItem, len(c.selectableRewardItems)) copy(items, c.selectableRewardItems) return items } // NeedsItem checks if the collection needs a specific item func (c *Collection) NeedsItem(itemID int32) bool { c.mu.RLock() defer c.mu.RUnlock() if c.completed { return false } for _, item := range c.collectionItems { if item.ItemID == itemID { return item.Found == ItemNotFound } } return false } // GetCollectionItemByItemID returns the collection item for a specific item ID func (c *Collection) GetCollectionItemByItemID(itemID int32) *CollectionItem { c.mu.RLock() defer c.mu.RUnlock() for i := range c.collectionItems { if c.collectionItems[i].ItemID == itemID { return &c.collectionItems[i] } } return nil } // GetIsReadyToTurnIn checks if all required items have been found func (c *Collection) GetIsReadyToTurnIn() bool { c.mu.RLock() defer c.mu.RUnlock() if c.completed { return false } for _, item := range c.collectionItems { if item.Found == ItemNotFound { return false } } return true } // MarkItemFound marks an item as found in the collection func (c *Collection) MarkItemFound(itemID int32) bool { c.mu.Lock() defer c.mu.Unlock() if c.completed { return false } for i := range c.collectionItems { if c.collectionItems[i].ItemID == itemID && c.collectionItems[i].Found == ItemNotFound { c.collectionItems[i].Found = ItemFound c.saveNeeded = true c.lastModified = time.Now() return true } } return false } // GetProgress returns the completion progress as a percentage func (c *Collection) GetProgress() float64 { c.mu.RLock() defer c.mu.RUnlock() if len(c.collectionItems) == 0 { return 0.0 } foundCount := 0 for _, item := range c.collectionItems { if item.Found == ItemFound { foundCount++ } } return float64(foundCount) / float64(len(c.collectionItems)) * 100.0 } // GetFoundItemsCount returns the number of found items func (c *Collection) GetFoundItemsCount() int { c.mu.RLock() defer c.mu.RUnlock() count := 0 for _, item := range c.collectionItems { if item.Found == ItemFound { count++ } } return count } // GetTotalItemsCount returns the total number of required items func (c *Collection) GetTotalItemsCount() int { c.mu.RLock() defer c.mu.RUnlock() return len(c.collectionItems) } // GetCollectionInfo returns detailed collection information func (c *Collection) GetCollectionInfo() CollectionInfo { c.mu.RLock() defer c.mu.RUnlock() return CollectionInfo{ ID: c.id, Name: c.name, Category: c.category, Level: c.level, Completed: c.completed, ReadyToTurnIn: c.getIsReadyToTurnInNoLock(), ItemsFound: c.getFoundItemsCountNoLock(), ItemsTotal: len(c.collectionItems), RewardCoin: c.rewardCoin, RewardXP: c.rewardXP, RewardItems: append([]CollectionRewardItem(nil), c.rewardItems...), SelectableRewards: append([]CollectionRewardItem(nil), c.selectableRewardItems...), RequiredItems: append([]CollectionItem(nil), c.collectionItems...), } } // GetCollectionProgress returns detailed progress information func (c *Collection) GetCollectionProgress() CollectionProgress { c.mu.RLock() defer c.mu.RUnlock() var foundItems, neededItems []CollectionItem for _, item := range c.collectionItems { if item.Found == ItemFound { foundItems = append(foundItems, item) } else { neededItems = append(neededItems, item) } } return CollectionProgress{ CollectionID: c.id, Name: c.name, Category: c.category, Level: c.level, Completed: c.completed, ReadyToTurnIn: c.getIsReadyToTurnInNoLock(), Progress: c.getProgressNoLock(), ItemsFound: foundItems, ItemsNeeded: neededItems, LastUpdated: c.lastModified, } } // LoadFromRewardData loads reward data into the collection func (c *Collection) LoadFromRewardData(rewards []CollectionRewardData) error { c.mu.Lock() defer c.mu.Unlock() for _, reward := range rewards { switch strings.ToLower(reward.RewardType) { case strings.ToLower(RewardTypeItem): itemID, err := strconv.ParseInt(reward.RewardValue, 10, 32) if err != nil { return fmt.Errorf("invalid item ID in reward: %s", reward.RewardValue) } c.rewardItems = append(c.rewardItems, CollectionRewardItem{ ItemID: int32(itemID), Quantity: reward.Quantity, }) case strings.ToLower(RewardTypeSelectable): itemID, err := strconv.ParseInt(reward.RewardValue, 10, 32) if err != nil { return fmt.Errorf("invalid item ID in selectable reward: %s", reward.RewardValue) } c.selectableRewardItems = append(c.selectableRewardItems, CollectionRewardItem{ ItemID: int32(itemID), Quantity: reward.Quantity, }) case strings.ToLower(RewardTypeCoin): coin, err := strconv.ParseInt(reward.RewardValue, 10, 64) if err != nil { return fmt.Errorf("invalid coin amount in reward: %s", reward.RewardValue) } c.rewardCoin = coin case strings.ToLower(RewardTypeXP): xp, err := strconv.ParseInt(reward.RewardValue, 10, 64) if err != nil { return fmt.Errorf("invalid XP amount in reward: %s", reward.RewardValue) } c.rewardXP = xp default: return fmt.Errorf("unknown reward type: %s", reward.RewardType) } } return nil } // Validate checks if the collection data is valid func (c *Collection) Validate() error { c.mu.RLock() defer c.mu.RUnlock() if c.id <= 0 { return fmt.Errorf("collection ID must be positive") } if strings.TrimSpace(c.name) == "" { return fmt.Errorf("collection name cannot be empty") } if len(c.collectionItems) == 0 { return fmt.Errorf("collection must have at least one required item") } // Check for duplicate item IDs itemIDs := make(map[int32]bool) for _, item := range c.collectionItems { if itemIDs[item.ItemID] { return fmt.Errorf("duplicate item ID in collection: %d", item.ItemID) } itemIDs[item.ItemID] = true if item.ItemID <= 0 { return fmt.Errorf("collection item ID must be positive: %d", item.ItemID) } } return nil } // Helper methods (no lock versions for internal use) func (c *Collection) getIsReadyToTurnInNoLock() bool { if c.completed { return false } for _, item := range c.collectionItems { if item.Found == ItemNotFound { return false } } return true } func (c *Collection) getFoundItemsCountNoLock() int { count := 0 for _, item := range c.collectionItems { if item.Found == ItemFound { count++ } } return count } func (c *Collection) getProgressNoLock() float64 { if len(c.collectionItems) == 0 { return 0.0 } foundCount := c.getFoundItemsCountNoLock() return float64(foundCount) / float64(len(c.collectionItems)) * 100.0 }