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