500 lines
12 KiB
Go
500 lines
12 KiB
Go
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
|
|
}
|