eq2go/internal/collections/collection.go

423 lines
12 KiB
Go

package collections
import (
"fmt"
"time"
"eq2emu/internal/database"
)
// Collection represents a collection that players can complete
type Collection struct {
ID int32 `json:"id"`
Name string `json:"name"`
Category string `json:"category"`
Level int8 `json:"level"`
RewardCoin int64 `json:"reward_coin"`
RewardXP int64 `json:"reward_xp"`
Completed bool `json:"completed"`
SaveNeeded bool `json:"-"`
CollectionItems []CollectionItem `json:"collection_items"`
RewardItems []CollectionRewardItem `json:"reward_items"`
SelectableRewardItems []CollectionRewardItem `json:"selectable_reward_items"`
LastModified time.Time `json:"last_modified"`
db *database.Database `json:"-"`
isNew bool `json:"-"`
}
// CollectionItem represents an item required for a collection
type CollectionItem struct {
ItemID int32 `json:"item_id"`
Index int8 `json:"index"`
Found int8 `json:"found"`
}
// CollectionRewardItem represents a reward item for completing a collection
type CollectionRewardItem struct {
ItemID int32 `json:"item_id"`
Quantity int8 `json:"quantity"`
}
// New creates a new collection with the given database
func New(db *database.Database) *Collection {
return &Collection{
db: db,
isNew: true,
CollectionItems: make([]CollectionItem, 0),
RewardItems: make([]CollectionRewardItem, 0),
SelectableRewardItems: make([]CollectionRewardItem, 0),
LastModified: time.Now(),
}
}
// NewWithData creates a new collection with data
func NewWithData(id int32, name, category string, level int8, db *database.Database) *Collection {
return &Collection{
ID: id,
Name: name,
Category: category,
Level: level,
db: db,
isNew: true,
CollectionItems: make([]CollectionItem, 0),
RewardItems: make([]CollectionRewardItem, 0),
SelectableRewardItems: make([]CollectionRewardItem, 0),
LastModified: time.Now(),
}
}
// Load loads a collection by ID from the database
func Load(db *database.Database, id int32) (*Collection, error) {
collection := &Collection{
db: db,
isNew: false,
CollectionItems: make([]CollectionItem, 0),
RewardItems: make([]CollectionRewardItem, 0),
SelectableRewardItems: make([]CollectionRewardItem, 0),
}
// Load collection base data
query := `SELECT id, collection_name, collection_category, level FROM collections WHERE id = ?`
row := db.QueryRow(query, id)
err := row.Scan(&collection.ID, &collection.Name, &collection.Category, &collection.Level)
if err != nil {
return nil, fmt.Errorf("failed to load collection %d: %w", id, err)
}
// Load collection items
itemQuery := `SELECT item_id, item_index, found FROM collection_items WHERE collection_id = ? ORDER BY item_index`
rows, err := db.Query(itemQuery, id)
if err != nil {
return nil, fmt.Errorf("failed to load collection items: %w", err)
}
defer rows.Close()
for rows.Next() {
var item CollectionItem
if err := rows.Scan(&item.ItemID, &item.Index, &item.Found); err != nil {
return nil, fmt.Errorf("failed to scan collection item: %w", err)
}
collection.CollectionItems = append(collection.CollectionItems, item)
}
// Load reward data
rewardQuery := `SELECT reward_type, reward_value, reward_quantity FROM collection_rewards WHERE collection_id = ?`
rows, err = db.Query(rewardQuery, id)
if err != nil {
return nil, fmt.Errorf("failed to load collection rewards: %w", err)
}
defer rows.Close()
for rows.Next() {
var rewardType string
var rewardValue string
var quantity int8
if err := rows.Scan(&rewardType, &rewardValue, &quantity); err != nil {
return nil, 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,
})
}
}
collection.LastModified = time.Now()
return collection, nil
}
// GetID returns the collection ID (implements Identifiable interface)
func (c *Collection) GetID() int32 {
return c.ID
}
// GetName returns the collection name
func (c *Collection) GetName() string {
return c.Name
}
// GetCategory returns the collection category
func (c *Collection) GetCategory() string {
return c.Category
}
// GetLevel returns the collection level
func (c *Collection) GetLevel() int8 {
return c.Level
}
// GetIsReadyToTurnIn returns true if all items have been found
func (c *Collection) GetIsReadyToTurnIn() bool {
if c.Completed {
return false
}
for _, item := range c.CollectionItems {
if item.Found == 0 {
return false
}
}
return true
}
// NeedsItem checks if the collection needs a specific item
func (c *Collection) NeedsItem(itemID int32) bool {
for _, item := range c.CollectionItems {
if item.ItemID == itemID && item.Found == 0 {
return true
}
}
return false
}
// GetCollectionItemByItemID returns the collection item by item ID
func (c *Collection) GetCollectionItemByItemID(itemID int32) *CollectionItem {
for i := range c.CollectionItems {
if c.CollectionItems[i].ItemID == itemID {
return &c.CollectionItems[i]
}
}
return nil
}
// MarkItemFound marks an item as found in the collection
func (c *Collection) MarkItemFound(itemID int32) bool {
for i := range c.CollectionItems {
if c.CollectionItems[i].ItemID == itemID && c.CollectionItems[i].Found == 0 {
c.CollectionItems[i].Found = 1
c.SaveNeeded = true
c.LastModified = time.Now()
return true
}
}
return false
}
// GetProgress returns the collection progress percentage
func (c *Collection) GetProgress() float64 {
if len(c.CollectionItems) == 0 {
return 0.0
}
found := 0
for _, item := range c.CollectionItems {
if item.Found != 0 {
found++
}
}
return float64(found) / float64(len(c.CollectionItems)) * 100.0
}
// IsNew returns true if this is a new collection not yet saved to database
func (c *Collection) IsNew() bool {
return c.isNew
}
// Save saves the collection to the database
func (c *Collection) Save() error {
if c.db == nil {
return fmt.Errorf("no database connection available")
}
if c.isNew {
return c.insert()
}
return c.update()
}
// Delete removes the collection from the database
func (c *Collection) Delete() error {
if c.db == nil {
return fmt.Errorf("no database connection available")
}
if c.isNew {
return fmt.Errorf("cannot delete unsaved collection")
}
// Delete collection items first
_, err := c.db.Exec(`DELETE FROM collection_items WHERE collection_id = ?`, c.ID)
if err != nil {
return fmt.Errorf("failed to delete collection items: %w", err)
}
// Delete collection rewards
_, err = c.db.Exec(`DELETE FROM collection_rewards WHERE collection_id = ?`, c.ID)
if err != nil {
return fmt.Errorf("failed to delete collection rewards: %w", err)
}
// Delete collection
_, err = c.db.Exec(`DELETE FROM collections WHERE id = ?`, c.ID)
if err != nil {
return fmt.Errorf("failed to delete collection %d: %w", c.ID, err)
}
return nil
}
// Reload reloads the collection data from the database
func (c *Collection) Reload() error {
if c.db == nil {
return fmt.Errorf("no database connection available")
}
if c.isNew {
return fmt.Errorf("cannot reload unsaved collection")
}
reloaded, err := Load(c.db, c.ID)
if err != nil {
return err
}
// Copy reloaded data
c.Name = reloaded.Name
c.Category = reloaded.Category
c.Level = reloaded.Level
c.RewardCoin = reloaded.RewardCoin
c.RewardXP = reloaded.RewardXP
c.CollectionItems = reloaded.CollectionItems
c.RewardItems = reloaded.RewardItems
c.SelectableRewardItems = reloaded.SelectableRewardItems
c.LastModified = reloaded.LastModified
return nil
}
// Clone creates a copy of the collection
func (c *Collection) Clone() *Collection {
newCollection := &Collection{
ID: c.ID,
Name: c.Name,
Category: c.Category,
Level: c.Level,
RewardCoin: c.RewardCoin,
RewardXP: c.RewardXP,
Completed: c.Completed,
SaveNeeded: c.SaveNeeded,
db: c.db,
isNew: true, // Clone is always new
CollectionItems: make([]CollectionItem, len(c.CollectionItems)),
RewardItems: make([]CollectionRewardItem, len(c.RewardItems)),
SelectableRewardItems: make([]CollectionRewardItem, len(c.SelectableRewardItems)),
LastModified: time.Now(),
}
copy(newCollection.CollectionItems, c.CollectionItems)
copy(newCollection.RewardItems, c.RewardItems)
copy(newCollection.SelectableRewardItems, c.SelectableRewardItems)
return newCollection
}
// insert inserts a new collection into the database
func (c *Collection) insert() error {
query := `INSERT INTO collections (collection_name, collection_category, level) VALUES (?, ?, ?)`
result, err := c.db.Exec(query, c.Name, c.Category, c.Level)
if err != nil {
return fmt.Errorf("failed to insert collection: %w", err)
}
id, err := result.LastInsertId()
if err != nil {
return fmt.Errorf("failed to get inserted collection ID: %w", err)
}
c.ID = int32(id)
// Insert collection items
for _, item := range c.CollectionItems {
_, err = c.db.Exec(`INSERT INTO collection_items (collection_id, item_id, item_index, found) VALUES (?, ?, ?, ?)`,
c.ID, item.ItemID, item.Index, item.Found)
if err != nil {
return fmt.Errorf("failed to insert collection item: %w", err)
}
}
// Insert rewards
if c.RewardCoin > 0 {
_, err = c.db.Exec(`INSERT INTO collection_rewards (collection_id, reward_type, reward_value, reward_quantity) VALUES (?, 'coin', ?, 1)`,
c.ID, fmt.Sprintf("%d", c.RewardCoin))
if err != nil {
return fmt.Errorf("failed to insert coin reward: %w", err)
}
}
if c.RewardXP > 0 {
_, err = c.db.Exec(`INSERT INTO collection_rewards (collection_id, reward_type, reward_value, reward_quantity) VALUES (?, 'xp', ?, 1)`,
c.ID, fmt.Sprintf("%d", c.RewardXP))
if err != nil {
return fmt.Errorf("failed to insert XP reward: %w", err)
}
}
for _, reward := range c.RewardItems {
_, err = c.db.Exec(`INSERT INTO collection_rewards (collection_id, reward_type, reward_value, reward_quantity) VALUES (?, 'item', ?, ?)`,
c.ID, fmt.Sprintf("%d", reward.ItemID), reward.Quantity)
if err != nil {
return fmt.Errorf("failed to insert item reward: %w", err)
}
}
for _, reward := range c.SelectableRewardItems {
_, err = c.db.Exec(`INSERT INTO collection_rewards (collection_id, reward_type, reward_value, reward_quantity) VALUES (?, 'selectable_item', ?, ?)`,
c.ID, fmt.Sprintf("%d", reward.ItemID), reward.Quantity)
if err != nil {
return fmt.Errorf("failed to insert selectable item reward: %w", err)
}
}
c.isNew = false
c.SaveNeeded = false
return nil
}
// update updates an existing collection in the database
func (c *Collection) update() error {
query := `UPDATE collections SET collection_name = ?, collection_category = ?, level = ? WHERE id = ?`
result, err := c.db.Exec(query, c.Name, c.Category, c.Level, c.ID)
if err != nil {
return fmt.Errorf("failed to update collection: %w", err)
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("failed to get rows affected: %w", err)
}
if rowsAffected == 0 {
return fmt.Errorf("collection %d not found for update", c.ID)
}
// Update collection items (just update found status)
for _, item := range c.CollectionItems {
_, err = c.db.Exec(`UPDATE collection_items SET found = ? WHERE collection_id = ? AND item_id = ?`,
item.Found, c.ID, item.ItemID)
if err != nil {
return fmt.Errorf("failed to update collection item: %w", err)
}
}
c.SaveNeeded = false
return nil
}