From cc49aac68987f1bb73f280d084dc65fb8500bd5d Mon Sep 17 00:00:00 2001 From: Sky Johnson Date: Wed, 6 Aug 2025 13:37:44 -0500 Subject: [PATCH] fix transmute package --- internal/transmute/constants.go | 12 +- internal/transmute/database.go | 175 ++-- internal/transmute/transmute_test.go | 1257 ++++++++++++++++++++++++++ internal/transmute/types.go | 4 +- 4 files changed, 1374 insertions(+), 74 deletions(-) create mode 100644 internal/transmute/transmute_test.go diff --git a/internal/transmute/constants.go b/internal/transmute/constants.go index 5a89f96..b5d2377 100644 --- a/internal/transmute/constants.go +++ b/internal/transmute/constants.go @@ -2,16 +2,16 @@ package transmute // Item flags that disqualify items from transmutation const ( - NoZone = 1 << 0 // NO_ZONE flag - NoValue = 1 << 1 // NO_VALUE flag - Temporary = 1 << 2 // TEMPORARY flag - NoDestroy = 1 << 3 // NO_DESTROY flag - NoTransmute = 1 << 14 // NO_TRANSMUTE flag (16384) + NoZone = int32(1 << 0) // NO_ZONE flag + NoValue = int32(1 << 1) // NO_VALUE flag + Temporary = int32(1 << 2) // TEMPORARY flag + NoDestroy = int32(1 << 3) // NO_DESTROY flag + NoTransmute = int32(1 << 14) // NO_TRANSMUTE flag (16384) ) // Item flags2 that disqualify items from transmutation const ( - Ornate = 1 << 0 // ORNATE flag + Ornate = int32(1 << 0) // ORNATE flag ) // Item tiers/rarities diff --git a/internal/transmute/database.go b/internal/transmute/database.go index f6db030..35ca534 100644 --- a/internal/transmute/database.go +++ b/internal/transmute/database.go @@ -2,25 +2,49 @@ package transmute import ( "fmt" - "time" - "eq2emu/internal/database" + "zombiezen.com/go/sqlite" + "zombiezen.com/go/sqlite/sqlitex" ) // DatabaseImpl provides a default implementation of the Database interface type DatabaseImpl struct { - db *database.DB + conn *sqlite.Conn } // NewDatabase creates a new database implementation -func NewDatabase(db *database.DB) *DatabaseImpl { - return &DatabaseImpl{db: db} +func NewDatabase(conn *sqlite.Conn) *DatabaseImpl { + return &DatabaseImpl{conn: conn} +} + +// OpenDB opens a database connection for transmutation system +func OpenDB(path string) (*DatabaseImpl, error) { + conn, err := sqlite.OpenConn(path, sqlite.OpenReadWrite|sqlite.OpenCreate|sqlite.OpenWAL) + if err != nil { + return nil, fmt.Errorf("failed to open database: %w", err) + } + + // Enable foreign keys + if err := sqlitex.ExecTransient(conn, "PRAGMA foreign_keys = ON;", nil); err != nil { + conn.Close() + return nil, fmt.Errorf("failed to enable foreign keys: %w", err) + } + + return &DatabaseImpl{conn: conn}, nil +} + +// Close closes the database connection +func (dbi *DatabaseImpl) Close() error { + if dbi.conn != nil { + return dbi.conn.Close() + } + return nil } // LoadTransmutingTiers loads transmuting tiers from the database func (dbi *DatabaseImpl) LoadTransmutingTiers() ([]*TransmutingTier, error) { // Create transmuting_tiers table if it doesn't exist - if err := dbi.db.Exec(` + if err := sqlitex.ExecuteScript(dbi.conn, ` CREATE TABLE IF NOT EXISTS transmuting_tiers ( min_level INTEGER NOT NULL, max_level INTEGER NOT NULL, @@ -31,20 +55,21 @@ func (dbi *DatabaseImpl) LoadTransmutingTiers() ([]*TransmutingTier, error) { created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (min_level, max_level) ) - `); err != nil { + `, &sqlitex.ExecOptions{}); err != nil { return nil, fmt.Errorf("failed to create transmuting_tiers table: %w", err) } // Check if table is empty and populate with default data - var count int - row, err := dbi.db.QueryRow("SELECT COUNT(*) FROM transmuting_tiers") + var count int64 + err := sqlitex.Execute(dbi.conn, "SELECT COUNT(*) FROM transmuting_tiers", &sqlitex.ExecOptions{ + ResultFunc: func(stmt *sqlite.Stmt) error { + count = stmt.ColumnInt64(0) + return nil + }, + }) if err != nil { return nil, fmt.Errorf("failed to count transmuting tiers: %w", err) } - if row != nil { - count = row.Int(0) - row.Close() - } // If empty, populate with default EQ2 transmuting tiers if count == 0 { @@ -55,19 +80,20 @@ func (dbi *DatabaseImpl) LoadTransmutingTiers() ([]*TransmutingTier, error) { // Load all tiers from database var tiers []*TransmutingTier - err = dbi.db.Query("SELECT min_level, max_level, fragment_id, powder_id, infusion_id, mana_id FROM transmuting_tiers ORDER BY min_level", - func(row *database.Row) error { + err = sqlitex.Execute(dbi.conn, "SELECT min_level, max_level, fragment_id, powder_id, infusion_id, mana_id FROM transmuting_tiers ORDER BY min_level", &sqlitex.ExecOptions{ + ResultFunc: func(stmt *sqlite.Stmt) error { tier := &TransmutingTier{ - MinLevel: int32(row.Int64(0)), - MaxLevel: int32(row.Int64(1)), - FragmentID: int32(row.Int64(2)), - PowderID: int32(row.Int64(3)), - InfusionID: int32(row.Int64(4)), - ManaID: int32(row.Int64(5)), + MinLevel: int32(stmt.ColumnInt64(0)), + MaxLevel: int32(stmt.ColumnInt64(1)), + FragmentID: int32(stmt.ColumnInt64(2)), + PowderID: int32(stmt.ColumnInt64(3)), + InfusionID: int32(stmt.ColumnInt64(4)), + ManaID: int32(stmt.ColumnInt64(5)), } tiers = append(tiers, tier) return nil - }) + }, + }) if err != nil { return nil, fmt.Errorf("failed to load transmuting tiers: %w", err) @@ -94,19 +120,26 @@ func (dbi *DatabaseImpl) populateDefaultTiers() error { {90, 100, 1037, 1038, 1039, 1040}, } - return dbi.db.Transaction(func(txDB *database.DB) error { - for _, tier := range defaultTiers { - err := txDB.Exec(` - INSERT INTO transmuting_tiers (min_level, max_level, fragment_id, powder_id, infusion_id, mana_id) - VALUES (?, ?, ?, ?, ?, ?) - `, tier.minLevel, tier.maxLevel, tier.fragmentID, tier.powderID, tier.infusionID, tier.manaID) + // Use transaction for atomic inserts + endFn, err := sqlitex.ImmediateTransaction(dbi.conn) + if err != nil { + return fmt.Errorf("failed to start transaction: %w", err) + } + defer endFn(&err) - if err != nil { - return fmt.Errorf("failed to insert tier %d-%d: %w", tier.minLevel, tier.maxLevel, err) - } + for _, tier := range defaultTiers { + err = sqlitex.Execute(dbi.conn, ` + INSERT INTO transmuting_tiers (min_level, max_level, fragment_id, powder_id, infusion_id, mana_id) + VALUES (?, ?, ?, ?, ?, ?) + `, &sqlitex.ExecOptions{ + Args: []any{tier.minLevel, tier.maxLevel, tier.fragmentID, tier.powderID, tier.infusionID, tier.manaID}, + }) + + if err != nil { + return fmt.Errorf("failed to insert tier %d-%d: %w", tier.minLevel, tier.maxLevel, err) } - return nil - }) + } + return nil } // TODO: When integrating with a real database system, replace this with actual database queries @@ -167,10 +200,12 @@ func (dbi *DatabaseImpl) SaveTransmutingTier(tier *TransmutingTier) error { return fmt.Errorf("all material IDs must be positive") } - err := dbi.db.Exec(` + err := sqlitex.Execute(dbi.conn, ` INSERT OR REPLACE INTO transmuting_tiers (min_level, max_level, fragment_id, powder_id, infusion_id, mana_id) VALUES (?, ?, ?, ?, ?, ?) - `, tier.MinLevel, tier.MaxLevel, tier.FragmentID, tier.PowderID, tier.InfusionID, tier.ManaID) + `, &sqlitex.ExecOptions{ + Args: []any{tier.MinLevel, tier.MaxLevel, tier.FragmentID, tier.PowderID, tier.InfusionID, tier.ManaID}, + }) if err != nil { return fmt.Errorf("failed to save transmuting tier %d-%d: %w", tier.MinLevel, tier.MaxLevel, err) @@ -185,7 +220,9 @@ func (dbi *DatabaseImpl) DeleteTransmutingTier(minLevel, maxLevel int32) error { return fmt.Errorf("invalid level range: %d-%d", minLevel, maxLevel) } - err := dbi.db.Exec("DELETE FROM transmuting_tiers WHERE min_level = ? AND max_level = ?", minLevel, maxLevel) + err := sqlitex.Execute(dbi.conn, "DELETE FROM transmuting_tiers WHERE min_level = ? AND max_level = ?", &sqlitex.ExecOptions{ + Args: []any{minLevel, maxLevel}, + }) if err != nil { return fmt.Errorf("failed to delete transmuting tier %d-%d: %w", minLevel, maxLevel, err) } @@ -195,26 +232,31 @@ func (dbi *DatabaseImpl) DeleteTransmutingTier(minLevel, maxLevel int32) error { // GetTransmutingTierByLevel gets a specific transmuting tier by level range func (dbi *DatabaseImpl) GetTransmutingTierByLevel(itemLevel int32) (*TransmutingTier, error) { - row, err := dbi.db.QueryRow("SELECT min_level, max_level, fragment_id, powder_id, infusion_id, mana_id FROM transmuting_tiers WHERE min_level <= ? AND max_level >= ?", itemLevel, itemLevel) + var tier *TransmutingTier + + err := sqlitex.Execute(dbi.conn, "SELECT min_level, max_level, fragment_id, powder_id, infusion_id, mana_id FROM transmuting_tiers WHERE min_level <= ? AND max_level >= ?", &sqlitex.ExecOptions{ + Args: []any{itemLevel, itemLevel}, + ResultFunc: func(stmt *sqlite.Stmt) error { + tier = &TransmutingTier{ + MinLevel: int32(stmt.ColumnInt64(0)), + MaxLevel: int32(stmt.ColumnInt64(1)), + FragmentID: int32(stmt.ColumnInt64(2)), + PowderID: int32(stmt.ColumnInt64(3)), + InfusionID: int32(stmt.ColumnInt64(4)), + ManaID: int32(stmt.ColumnInt64(5)), + } + return nil + }, + }) + if err != nil { return nil, fmt.Errorf("failed to query transmuting tier for level %d: %w", itemLevel, err) } - if row == nil { + if tier == nil { return nil, fmt.Errorf("no transmuting tier found for level %d", itemLevel) } - defer row.Close() - - tier := &TransmutingTier{ - MinLevel: int32(row.Int64(0)), - MaxLevel: int32(row.Int64(1)), - FragmentID: int32(row.Int64(2)), - PowderID: int32(row.Int64(3)), - InfusionID: int32(row.Int64(4)), - ManaID: int32(row.Int64(5)), - } - return tier, nil } @@ -237,12 +279,14 @@ func (dbi *DatabaseImpl) UpdateTransmutingTier(oldMinLevel, oldMaxLevel int32, n return fmt.Errorf("all material IDs must be positive") } - err := dbi.db.Exec(` + err := sqlitex.Execute(dbi.conn, ` UPDATE transmuting_tiers SET min_level=?, max_level=?, fragment_id=?, powder_id=?, infusion_id=?, mana_id=? WHERE min_level=? AND max_level=? - `, newTier.MinLevel, newTier.MaxLevel, newTier.FragmentID, newTier.PowderID, - newTier.InfusionID, newTier.ManaID, oldMinLevel, oldMaxLevel) + `, &sqlitex.ExecOptions{ + Args: []any{newTier.MinLevel, newTier.MaxLevel, newTier.FragmentID, newTier.PowderID, + newTier.InfusionID, newTier.ManaID, oldMinLevel, oldMaxLevel}, + }) if err != nil { return fmt.Errorf("failed to update transmuting tier %d-%d: %w", oldMinLevel, oldMaxLevel, err) @@ -252,21 +296,20 @@ func (dbi *DatabaseImpl) UpdateTransmutingTier(oldMinLevel, oldMaxLevel int32, n } // TransmutingTierExists checks if a transmuting tier exists for the given level range -func (db *DatabaseImpl) TransmutingTierExists(minLevel, maxLevel int32) (bool, error) { - // Placeholder implementation - // In a real implementation: - // SELECT COUNT(*) FROM transmuting WHERE min_level = ? AND max_level = ? - - tiers, err := db.LoadTransmutingTiers() +func (dbi *DatabaseImpl) TransmutingTierExists(minLevel, maxLevel int32) (bool, error) { + var count int64 + + err := sqlitex.Execute(dbi.conn, "SELECT COUNT(*) FROM transmuting_tiers WHERE min_level = ? AND max_level = ?", &sqlitex.ExecOptions{ + Args: []any{minLevel, maxLevel}, + ResultFunc: func(stmt *sqlite.Stmt) error { + count = stmt.ColumnInt64(0) + return nil + }, + }) + if err != nil { - return false, err + return false, fmt.Errorf("failed to check tier existence: %w", err) } - for _, tier := range tiers { - if tier.MinLevel == minLevel && tier.MaxLevel == maxLevel { - return true, nil - } - } - - return false, nil + return count > 0, nil } diff --git a/internal/transmute/transmute_test.go b/internal/transmute/transmute_test.go new file mode 100644 index 0000000..7b4053a --- /dev/null +++ b/internal/transmute/transmute_test.go @@ -0,0 +1,1257 @@ +package transmute + +import ( + "path/filepath" + "sync" + "testing" +) + +// Mock implementations for testing + +// MockItem implements the Item interface for testing +type MockItem struct { + id int32 + uniqueID int32 + name string + adventureDefaultLevel int32 + itemFlags int32 + itemFlags2 int32 + tier int32 + stackCount int32 + count int32 +} + +func NewMockItem(id, uniqueID int32, name string, level int32) *MockItem { + return &MockItem{ + id: id, + uniqueID: uniqueID, + name: name, + adventureDefaultLevel: level, + tier: ItemTagLegendary, + stackCount: 1, + count: 1, + } +} + +func (m *MockItem) GetID() int32 { return m.id } +func (m *MockItem) GetUniqueID() int32 { return m.uniqueID } +func (m *MockItem) GetName() string { return m.name } +func (m *MockItem) GetAdventureDefaultLevel() int32 { return m.adventureDefaultLevel } +func (m *MockItem) GetItemFlags() int32 { return m.itemFlags } +func (m *MockItem) GetItemFlags2() int32 { return m.itemFlags2 } +func (m *MockItem) GetTier() int32 { return m.tier } +func (m *MockItem) GetStackCount() int32 { return m.stackCount } +func (m *MockItem) SetCount(count int32) { m.count = count } +func (m *MockItem) CreateItemLink(version int32, detailed bool) string { + return "[" + m.name + "]" +} + +// MockPlayer implements the Player interface for testing +type MockPlayer struct { + items map[int32]Item + skills map[string]Skill + stats map[int32]int32 + name string + zone Zone +} + +func NewMockPlayer(name string) *MockPlayer { + return &MockPlayer{ + items: make(map[int32]Item), + skills: make(map[string]Skill), + stats: make(map[int32]int32), + name: name, + } +} + +func (m *MockPlayer) GetItemList() map[int32]Item { return m.items } +func (m *MockPlayer) GetItemFromUniqueID(uniqueID int32) Item { return m.items[uniqueID] } +func (m *MockPlayer) GetSkillByName(skillName string) Skill { return m.skills[skillName] } +func (m *MockPlayer) GetStat(statType int32) int32 { return m.stats[statType] } +func (m *MockPlayer) GetName() string { return m.name } +func (m *MockPlayer) GetZone() Zone { return m.zone } +func (m *MockPlayer) RemoveItem(item Item, deleteItem bool) bool { delete(m.items, item.GetUniqueID()); return true } +func (m *MockPlayer) AddItem(item Item) (bool, error) { m.items[item.GetUniqueID()] = item; return true, nil } +func (m *MockPlayer) IncreaseSkill(skillName string, amount int32) error { return nil } + +// MockClient implements the Client interface for testing +type MockClient struct { + version int32 + transmuteID int32 + packets [][]byte + messages []string + mutex sync.Mutex +} + +func NewMockClient(version int32) *MockClient { + return &MockClient{ + version: version, + packets: make([][]byte, 0), + messages: make([]string, 0), + } +} + +func (m *MockClient) GetVersion() int32 { return m.version } +func (m *MockClient) GetTransmuteID() int32 { return m.transmuteID } +func (m *MockClient) SetTransmuteID(id int32) { m.transmuteID = id } +func (m *MockClient) QueuePacket(packet []byte) { + m.mutex.Lock() + m.packets = append(m.packets, packet) + m.mutex.Unlock() +} +func (m *MockClient) SimpleMessage(channel int32, message string) { + m.mutex.Lock() + m.messages = append(m.messages, message) + m.mutex.Unlock() +} +func (m *MockClient) Message(channel int32, format string, args ...any) { + // Simple implementation for testing + m.SimpleMessage(channel, format) +} +func (m *MockClient) AddItem(item Item, itemDeleted *bool) error { + *itemDeleted = false + return nil +} + +func (m *MockClient) GetPackets() [][]byte { + m.mutex.Lock() + defer m.mutex.Unlock() + return append([][]byte(nil), m.packets...) +} + +func (m *MockClient) GetMessages() []string { + m.mutex.Lock() + defer m.mutex.Unlock() + return append([]string(nil), m.messages...) +} + +// MockSkill implements the Skill interface for testing +type MockSkill struct { + current int32 + max int32 +} + +func NewMockSkill(current, max int32) *MockSkill { + return &MockSkill{current: current, max: max} +} + +func (m *MockSkill) GetCurrentValue() int32 { return m.current } +func (m *MockSkill) GetMaxValue() int32 { return m.max } + +// MockZone implements the Zone interface for testing +type MockZone struct{} + +func (m *MockZone) ProcessSpell(spell Spell, caster Player) error { + // For testing, we'll simulate spell processing + return nil +} + +// MockSpell implements the Spell interface for testing +type MockSpell struct { + id int32 + name string +} + +func (m *MockSpell) GetID() int32 { return m.id } +func (m *MockSpell) GetName() string { return m.name } + +// MockSpellMaster implements the SpellMaster interface for testing +type MockSpellMaster struct { + spells map[int32]Spell +} + +func NewMockSpellMaster() *MockSpellMaster { + return &MockSpellMaster{ + spells: make(map[int32]Spell), + } +} + +func (m *MockSpellMaster) GetSpell(spellID int32, tier int32) Spell { + return m.spells[spellID] +} + +// MockItemMaster implements the ItemMaster interface for testing +type MockItemMaster struct { + items map[int32]Item +} + +func NewMockItemMaster() *MockItemMaster { + return &MockItemMaster{ + items: make(map[int32]Item), + } +} + +func (m *MockItemMaster) GetItem(itemID int32) Item { + return m.items[itemID] +} + +func (m *MockItemMaster) CreateItem(itemID int32) Item { + if item, exists := m.items[itemID]; exists { + // Return a copy + mockItem := item.(*MockItem) + return &MockItem{ + id: mockItem.id, + uniqueID: mockItem.uniqueID, + name: mockItem.name, + adventureDefaultLevel: mockItem.adventureDefaultLevel, + itemFlags: mockItem.itemFlags, + itemFlags2: mockItem.itemFlags2, + tier: mockItem.tier, + stackCount: mockItem.stackCount, + count: 1, + } + } + // Create a default item if not found + return NewMockItem(itemID, itemID, "Mock Item", 50) +} + +// Test database functionality +func TestDatabaseOperations(t *testing.T) { + // Create temporary database + tempDir := t.TempDir() + dbPath := filepath.Join(tempDir, "test_transmute.db") + + db, err := OpenDB(dbPath) + if err != nil { + t.Fatalf("Failed to open test database: %v", err) + } + defer db.Close() + + // Test LoadTransmutingTiers (should populate default tiers) + tiers, err := db.LoadTransmutingTiers() + if err != nil { + t.Fatalf("Failed to load transmuting tiers: %v", err) + } + + if len(tiers) == 0 { + t.Fatal("Expected default tiers to be loaded") + } + + expectedTiers := 10 // Should have 10 default tiers (1-9, 10-19, ..., 90-100) + if len(tiers) != expectedTiers { + t.Errorf("Expected %d default tiers, got %d", expectedTiers, len(tiers)) + } + + // Verify first tier + firstTier := tiers[0] + if firstTier.MinLevel != 1 || firstTier.MaxLevel != 9 { + t.Errorf("Expected first tier to be 1-9, got %d-%d", firstTier.MinLevel, firstTier.MaxLevel) + } + + // Test GetTransmutingTierByLevel + tier, err := db.GetTransmutingTierByLevel(5) + if err != nil { + t.Fatalf("Failed to get tier by level: %v", err) + } + + if tier.MinLevel != 1 || tier.MaxLevel != 9 { + t.Errorf("Expected tier 1-9 for level 5, got %d-%d", tier.MinLevel, tier.MaxLevel) + } + + // Test non-existent level + _, err = db.GetTransmutingTierByLevel(200) + if err == nil { + t.Error("Expected error for non-existent tier level") + } + + // Test SaveTransmutingTier + newTier := &TransmutingTier{ + MinLevel: 101, + MaxLevel: 110, + FragmentID: 2001, + PowderID: 2002, + InfusionID: 2003, + ManaID: 2004, + } + + err = db.SaveTransmutingTier(newTier) + if err != nil { + t.Fatalf("Failed to save new tier: %v", err) + } + + // Verify it was saved + savedTier, err := db.GetTransmutingTierByLevel(105) + if err != nil { + t.Fatalf("Failed to get saved tier: %v", err) + } + + if savedTier.FragmentID != 2001 { + t.Errorf("Expected fragment ID 2001, got %d", savedTier.FragmentID) + } + + // Test TransmutingTierExists + exists, err := db.TransmutingTierExists(101, 110) + if err != nil { + t.Fatalf("Failed to check tier existence: %v", err) + } + + if !exists { + t.Error("Expected tier 101-110 to exist") + } + + exists, err = db.TransmutingTierExists(999, 1000) + if err != nil { + t.Fatalf("Failed to check non-existent tier: %v", err) + } + + if exists { + t.Error("Expected tier 999-1000 not to exist") + } + + // Test UpdateTransmutingTier + updatedTier := &TransmutingTier{ + MinLevel: 101, + MaxLevel: 110, + FragmentID: 3001, + PowderID: 3002, + InfusionID: 3003, + ManaID: 3004, + } + + err = db.UpdateTransmutingTier(101, 110, updatedTier) + if err != nil { + t.Fatalf("Failed to update tier: %v", err) + } + + // Verify update + updated, err := db.GetTransmutingTierByLevel(105) + if err != nil { + t.Fatalf("Failed to get updated tier: %v", err) + } + + if updated.FragmentID != 3001 { + t.Errorf("Expected updated fragment ID 3001, got %d", updated.FragmentID) + } + + // Test DeleteTransmutingTier + err = db.DeleteTransmutingTier(101, 110) + if err != nil { + t.Fatalf("Failed to delete tier: %v", err) + } + + // Verify deletion + _, err = db.GetTransmutingTierByLevel(105) + if err == nil { + t.Error("Expected error after deleting tier") + } +} + +func TestDatabaseValidation(t *testing.T) { + tempDir := t.TempDir() + dbPath := filepath.Join(tempDir, "test_validation.db") + + db, err := OpenDB(dbPath) + if err != nil { + t.Fatalf("Failed to open test database: %v", err) + } + defer db.Close() + + // Test saving nil tier + err = db.SaveTransmutingTier(nil) + if err == nil { + t.Error("Expected error when saving nil tier") + } + + // Test invalid level range + invalidTier := &TransmutingTier{ + MinLevel: -1, + MaxLevel: 10, + } + + err = db.SaveTransmutingTier(invalidTier) + if err == nil { + t.Error("Expected error for invalid level range") + } + + // Test min > max + invalidTier.MinLevel = 20 + invalidTier.MaxLevel = 10 + + err = db.SaveTransmutingTier(invalidTier) + if err == nil { + t.Error("Expected error when min level > max level") + } + + // Test invalid material IDs + invalidTier.MinLevel = 10 + invalidTier.MaxLevel = 20 + invalidTier.FragmentID = 0 + + err = db.SaveTransmutingTier(invalidTier) + if err == nil { + t.Error("Expected error for invalid material ID") + } +} + +// Test transmutation logic +func TestTransmuter(t *testing.T) { + itemMaster := NewMockItemMaster() + spellMaster := NewMockSpellMaster() + packetBuilder := NewPacketBuilder() + + // Add some test materials to item master + itemMaster.items[1001] = NewMockItem(1001, 1001, "Fragment", 10) + itemMaster.items[1002] = NewMockItem(1002, 1002, "Powder", 10) + + transmuter := NewTransmuter(itemMaster, spellMaster, packetBuilder) + + // Create test database + tempDir := t.TempDir() + dbPath := filepath.Join(tempDir, "test_transmuter.db") + + db, err := OpenDB(dbPath) + if err != nil { + t.Fatalf("Failed to open test database: %v", err) + } + defer db.Close() + + // Load tiers + err = transmuter.LoadTransmutingTiers(db) + if err != nil { + t.Fatalf("Failed to load transmuting tiers: %v", err) + } + + tiers := transmuter.GetTransmutingTiers() + if len(tiers) == 0 { + t.Fatal("Expected transmuting tiers to be loaded") + } + + // Test item transmutability + transmutableItem := NewMockItem(500, 500, "Legendary Sword", 25) + transmutableItem.tier = ItemTagLegendary + + if !transmuter.IsItemTransmutable(transmutableItem) { + t.Error("Expected legendary item to be transmutable") + } + + // Test non-transmutable item (wrong tier) + nonTransmutableItem := NewMockItem(501, 501, "Common Sword", 25) + nonTransmutableItem.tier = ItemTagTreasured + + if transmuter.IsItemTransmutable(nonTransmutableItem) { + t.Error("Expected treasured item not to be transmutable") + } + + // Test flagged item + flaggedItem := NewMockItem(502, 502, "No-Trade Sword", 25) + flaggedItem.tier = ItemTagLegendary + flaggedItem.itemFlags = NoTransmute + + if transmuter.IsItemTransmutable(flaggedItem) { + t.Error("Expected no-transmute item not to be transmutable") + } + + // Test stacked item + stackedItem := NewMockItem(503, 503, "Stacked Item", 25) + stackedItem.tier = ItemTagLegendary + stackedItem.stackCount = 5 + + if transmuter.IsItemTransmutable(stackedItem) { + t.Error("Expected stacked item not to be transmutable") + } +} + +func TestCreateItemRequest(t *testing.T) { + itemMaster := NewMockItemMaster() + spellMaster := NewMockSpellMaster() + packetBuilder := NewPacketBuilder() + + transmuter := NewTransmuter(itemMaster, spellMaster, packetBuilder) + + // Set up database + tempDir := t.TempDir() + dbPath := filepath.Join(tempDir, "test_request.db") + + db, err := OpenDB(dbPath) + if err != nil { + t.Fatalf("Failed to open test database: %v", err) + } + defer db.Close() + + err = transmuter.LoadTransmutingTiers(db) + if err != nil { + t.Fatalf("Failed to load transmuting tiers: %v", err) + } + + // Create test player with transmutable items + player := NewMockPlayer("TestPlayer") + transmutableItem1 := NewMockItem(600, 600, "Legendary Sword", 25) + transmutableItem1.tier = ItemTagLegendary + transmutableItem2 := NewMockItem(601, 601, "Fabled Shield", 35) + transmutableItem2.tier = ItemTagFabled + + player.items[600] = transmutableItem1 + player.items[601] = transmutableItem2 + + // Add non-transmutable item + nonTransmutableItem := NewMockItem(602, 602, "Common Helm", 25) + nonTransmutableItem.tier = ItemTagTreasured + player.items[602] = nonTransmutableItem + + client := NewMockClient(1000) + + // Create item request + requestID, err := transmuter.CreateItemRequest(client, player) + if err != nil { + t.Fatalf("Failed to create item request: %v", err) + } + + if requestID == 0 { + t.Error("Expected non-zero request ID") + } + + if client.GetTransmuteID() != requestID { + t.Error("Expected client transmute ID to match request ID") + } + + // Verify request was stored + request := transmuter.GetActiveRequest(requestID) + if request == nil { + t.Error("Expected active request to be stored") + } + + if request.Phase != PhaseItemSelection { + t.Errorf("Expected phase %d, got %d", PhaseItemSelection, request.Phase) + } +} + +func TestHandleItemResponse(t *testing.T) { + itemMaster := NewMockItemMaster() + spellMaster := NewMockSpellMaster() + packetBuilder := NewPacketBuilder() + + transmuter := NewTransmuter(itemMaster, spellMaster, packetBuilder) + + // Set up database + tempDir := t.TempDir() + dbPath := filepath.Join(tempDir, "test_response.db") + + db, err := OpenDB(dbPath) + if err != nil { + t.Fatalf("Failed to open test database: %v", err) + } + defer db.Close() + + err = transmuter.LoadTransmutingTiers(db) + if err != nil { + t.Fatalf("Failed to load transmuting tiers: %v", err) + } + + // Create test player with transmuting skill + player := NewMockPlayer("TestPlayer") + transmutingSkill := NewMockSkill(100, 300) + player.skills["Transmuting"] = transmutingSkill + + transmutableItem := NewMockItem(700, 700, "Legendary Weapon", 25) + transmutableItem.tier = ItemTagLegendary + player.items[700] = transmutableItem + + client := NewMockClient(1000) + + // Create initial request + requestID, err := transmuter.CreateItemRequest(client, player) + if err != nil { + t.Fatalf("Failed to create initial request: %v", err) + } + + // Handle item response + err = transmuter.HandleItemResponse(client, player, requestID, 700) + if err != nil { + t.Fatalf("Failed to handle item response: %v", err) + } + + // Verify request was updated + request := transmuter.GetActiveRequest(requestID) + if request == nil { + t.Fatal("Expected request to still exist") + } + + if request.Phase != PhaseConfirmation { + t.Errorf("Expected phase %d, got %d", PhaseConfirmation, request.Phase) + } + + if request.ItemID != 700 { + t.Errorf("Expected item ID 700, got %d", request.ItemID) + } + + // Test insufficient skill + lowSkillPlayer := NewMockPlayer("LowSkillPlayer") + lowSkill := NewMockSkill(1, 300) + lowSkillPlayer.skills["Transmuting"] = lowSkill + + highLevelItem := NewMockItem(701, 701, "High Level Item", 80) + highLevelItem.tier = ItemTagLegendary + lowSkillPlayer.items[701] = highLevelItem + + requestID2, err := transmuter.CreateItemRequest(client, lowSkillPlayer) + if err != nil { + t.Fatalf("Failed to create second request: %v", err) + } + + err = transmuter.HandleItemResponse(client, lowSkillPlayer, requestID2, 701) + if err == nil { + t.Error("Expected error for insufficient transmuting skill") + } + + // Test non-existent item + err = transmuter.HandleItemResponse(client, player, requestID, 9999) + if err == nil { + t.Error("Expected error for non-existent item") + } + + // Test non-transmutable item + nonTransmutableItem := NewMockItem(702, 702, "Common Item", 25) + nonTransmutableItem.tier = ItemTagTreasured + player.items[702] = nonTransmutableItem + + err = transmuter.HandleItemResponse(client, player, requestID, 702) + if err == nil { + t.Error("Expected error for non-transmutable item") + } +} + +func TestHandleConfirmResponse(t *testing.T) { + itemMaster := NewMockItemMaster() + spellMaster := NewMockSpellMaster() + packetBuilder := NewPacketBuilder() + + // Add transmute spell to spell master + transmuteSpell := &MockSpell{id: TransmuteItemSpellID, name: "Transmute Item"} + spellMaster.spells[TransmuteItemSpellID] = transmuteSpell + + transmuter := NewTransmuter(itemMaster, spellMaster, packetBuilder) + + // Set up database + tempDir := t.TempDir() + dbPath := filepath.Join(tempDir, "test_confirm.db") + + db, err := OpenDB(dbPath) + if err != nil { + t.Fatalf("Failed to open test database: %v", err) + } + defer db.Close() + + err = transmuter.LoadTransmutingTiers(db) + if err != nil { + t.Fatalf("Failed to load transmuting tiers: %v", err) + } + + // Create test setup + player := NewMockPlayer("TestPlayer") + zone := &MockZone{} + player.zone = zone + + transmutableItem := NewMockItem(800, 800, "Legendary Item", 25) + transmutableItem.tier = ItemTagLegendary + player.items[800] = transmutableItem + + client := NewMockClient(1000) + client.SetTransmuteID(800) + + // Test successful confirmation + err = transmuter.HandleConfirmResponse(client, player, 800) + if err != nil { + t.Fatalf("Failed to handle confirm response: %v", err) + } + + // Test non-existent item + err = transmuter.HandleConfirmResponse(client, player, 9999) + if err == nil { + t.Error("Expected error for non-existent item") + } + + // Test player without zone + playerNoZone := NewMockPlayer("NoZonePlayer") + playerNoZone.items[800] = transmutableItem + + err = transmuter.HandleConfirmResponse(client, playerNoZone, 800) + if err == nil { + t.Error("Expected error for player without zone") + } +} + +// Test material calculation logic +func TestCalculateTransmuteResult(t *testing.T) { + itemMaster := NewMockItemMaster() + spellMaster := NewMockSpellMaster() + packetBuilder := NewPacketBuilder() + + // Add test materials to item master + itemMaster.items[1001] = NewMockItem(1001, 1001, "Fragment", 10) // Tier 1 fragment + itemMaster.items[1002] = NewMockItem(1002, 1002, "Powder", 10) // Tier 1 powder + + transmuter := NewTransmuter(itemMaster, spellMaster, packetBuilder) + + // Set up database and load tiers + tempDir := t.TempDir() + dbPath := filepath.Join(tempDir, "test_materials.db") + + db, err := OpenDB(dbPath) + if err != nil { + t.Fatalf("Failed to open test database: %v", err) + } + defer db.Close() + + err = transmuter.LoadTransmutingTiers(db) + if err != nil { + t.Fatalf("Failed to load transmuting tiers: %v", err) + } + + // Test legendary item (should give powder/infusion) + legendaryItem := NewMockItem(900, 900, "Legendary Item", 25) + legendaryItem.tier = ItemTagLegendary + + // We can't easily test the random result, but we can test the structure + result, err := transmuter.calculateTransmuteResult(legendaryItem) + if err != nil { + t.Fatalf("Failed to calculate transmute result: %v", err) + } + + if !result.Success { + t.Errorf("Expected successful result, got error: %s", result.ErrorMessage) + } + + // Test item with no tier match + highLevelItem := NewMockItem(901, 901, "High Level Item", 200) + highLevelItem.tier = ItemTagLegendary + + result, err = transmuter.calculateTransmuteResult(highLevelItem) + if err != nil { + t.Fatalf("Failed to calculate result for high level item: %v", err) + } + + if result.Success { + t.Error("Expected failure for item with no tier match") + } +} + +func TestCompleteTransmutation(t *testing.T) { + itemMaster := NewMockItemMaster() + spellMaster := NewMockSpellMaster() + packetBuilder := NewPacketBuilder() + + // Add test materials + fragment := NewMockItem(1001, 1001, "Fragment", 10) + powder := NewMockItem(1002, 1002, "Powder", 10) + itemMaster.items[1001] = fragment + itemMaster.items[1002] = powder + + transmuter := NewTransmuter(itemMaster, spellMaster, packetBuilder) + + // Set up database + tempDir := t.TempDir() + dbPath := filepath.Join(tempDir, "test_complete.db") + + db, err := OpenDB(dbPath) + if err != nil { + t.Fatalf("Failed to open test database: %v", err) + } + defer db.Close() + + err = transmuter.LoadTransmutingTiers(db) + if err != nil { + t.Fatalf("Failed to load transmuting tiers: %v", err) + } + + // Create test setup + player := NewMockPlayer("TestPlayer") + transmutingSkill := NewMockSkill(100, 300) + player.skills["Transmuting"] = transmutingSkill + + transmutableItem := NewMockItem(1000, 1000, "Test Item", 25) + transmutableItem.tier = ItemTagLegendary + player.items[1000] = transmutableItem + + client := NewMockClient(1000) + client.SetTransmuteID(1000) + + // Complete transmutation + err = transmuter.CompleteTransmutation(client, player) + if err != nil { + t.Fatalf("Failed to complete transmutation: %v", err) + } + + // Verify item was removed from player + if _, exists := player.items[1000]; exists { + t.Error("Expected transmuted item to be removed from player") + } + + // Verify messages were sent + messages := client.GetMessages() + if len(messages) == 0 { + t.Error("Expected completion messages to be sent") + } + + // Test non-existent item + client.SetTransmuteID(9999) + err = transmuter.CompleteTransmutation(client, player) + if err == nil { + t.Error("Expected error for non-existent item") + } +} + +// Test Manager functionality +func TestManager(t *testing.T) { + // Create test database + tempDir := t.TempDir() + dbPath := filepath.Join(tempDir, "test_manager.db") + + db, err := OpenDB(dbPath) + if err != nil { + t.Fatalf("Failed to open test database: %v", err) + } + defer db.Close() + + itemMaster := NewMockItemMaster() + spellMaster := NewMockSpellMaster() + packetBuilder := NewPacketBuilder() + + manager := NewManager(db, itemMaster, spellMaster, packetBuilder) + defer manager.Shutdown() + + // Test initialization + err = manager.Initialize() + if err != nil { + t.Fatalf("Failed to initialize manager: %v", err) + } + + tiers := manager.GetTransmutingTiers() + if len(tiers) == 0 { + t.Error("Expected tiers to be loaded after initialization") + } + + // Test statistics + stats := manager.GetStatistics() + if stats["total_transmutes"] != int64(0) { + t.Error("Expected zero transmutes initially") + } + + // Test validation + issues := manager.ValidateTransmutingSetup() + if len(issues) > 0 { + t.Errorf("Expected no validation issues, got: %v", issues) + } + + // Test GetTierForItemLevel + tier := manager.GetTierForItemLevel(25) + if tier == nil { + t.Error("Expected to find tier for level 25") + } + + if tier.MinLevel > 25 || tier.MaxLevel < 25 { + t.Errorf("Expected tier to contain level 25, got %d-%d", tier.MinLevel, tier.MaxLevel) + } + + // Test GetTierForItemLevel for non-existent level + noTier := manager.GetTierForItemLevel(200) + if noTier != nil { + t.Error("Expected no tier for level 200") + } + + // Test CalculateRequiredSkill + item := NewMockItem(1100, 1100, "Test Item", 25) + requiredSkill := manager.CalculateRequiredSkill(item) + expectedSkill := (25 - 5) * 5 // (level - 5) * 5 = 100 + if requiredSkill != int32(expectedSkill) { + t.Errorf("Expected required skill %d, got %d", expectedSkill, requiredSkill) + } + + // Test low level item + lowLevelItem := NewMockItem(1101, 1101, "Low Level Item", 3) + requiredSkill = manager.CalculateRequiredSkill(lowLevelItem) + if requiredSkill != 0 { + t.Errorf("Expected required skill 0 for low level item, got %d", requiredSkill) + } +} + +func TestManagerPlayerOperations(t *testing.T) { + tempDir := t.TempDir() + dbPath := filepath.Join(tempDir, "test_player_ops.db") + + db, err := OpenDB(dbPath) + if err != nil { + t.Fatalf("Failed to open test database: %v", err) + } + defer db.Close() + + itemMaster := NewMockItemMaster() + spellMaster := NewMockSpellMaster() + packetBuilder := NewPacketBuilder() + + manager := NewManager(db, itemMaster, spellMaster, packetBuilder) + defer manager.Shutdown() + + err = manager.Initialize() + if err != nil { + t.Fatalf("Failed to initialize manager: %v", err) + } + + // Create test player + player := NewMockPlayer("TestPlayer") + transmutingSkill := NewMockSkill(100, 300) + player.skills["Transmuting"] = transmutingSkill + + // Add transmutable and non-transmutable items + transmutableItem := NewMockItem(1200, 1200, "Legendary Sword", 25) + transmutableItem.tier = ItemTagLegendary + player.items[1200] = transmutableItem + + nonTransmutableItem := NewMockItem(1201, 1201, "Common Sword", 25) + nonTransmutableItem.tier = ItemTagTreasured + player.items[1201] = nonTransmutableItem + + // Test GetTransmutableItems + transmutableItems := manager.GetTransmutableItems(player) + if len(transmutableItems) != 1 { + t.Errorf("Expected 1 transmutable item, got %d", len(transmutableItems)) + } + + if transmutableItems[0].GetID() != 1200 { + t.Errorf("Expected transmutable item ID 1200, got %d", transmutableItems[0].GetID()) + } + + // Test CanPlayerTransmuteItem + canTransmute, reason := manager.CanPlayerTransmuteItem(player, transmutableItem) + if !canTransmute { + t.Errorf("Expected player to be able to transmute item, reason: %s", reason) + } + + // Test with insufficient skill + highLevelItem := NewMockItem(1202, 1202, "High Level Item", 80) + highLevelItem.tier = ItemTagLegendary + + canTransmute, reason = manager.CanPlayerTransmuteItem(player, highLevelItem) + if canTransmute { + t.Error("Expected player not to be able to transmute high level item") + } + + if reason == "" { + t.Error("Expected reason for inability to transmute") + } + + // Test with non-transmutable item + canTransmute, reason = manager.CanPlayerTransmuteItem(player, nonTransmutableItem) + if canTransmute { + t.Error("Expected player not to be able to transmute non-transmutable item") + } +} + +func TestManagerCommandProcessing(t *testing.T) { + tempDir := t.TempDir() + dbPath := filepath.Join(tempDir, "test_commands.db") + + db, err := OpenDB(dbPath) + if err != nil { + t.Fatalf("Failed to open test database: %v", err) + } + defer db.Close() + + itemMaster := NewMockItemMaster() + spellMaster := NewMockSpellMaster() + packetBuilder := NewPacketBuilder() + + manager := NewManager(db, itemMaster, spellMaster, packetBuilder) + defer manager.Shutdown() + + err = manager.Initialize() + if err != nil { + t.Fatalf("Failed to initialize manager: %v", err) + } + + client := NewMockClient(1000) + player := NewMockPlayer("TestPlayer") + + // Test stats command + result, err := manager.ProcessCommand("stats", []string{}, client, player) + if err != nil { + t.Fatalf("Failed to process stats command: %v", err) + } + + if result == "" { + t.Error("Expected stats command to return result") + } + + // Test validate command + result, err = manager.ProcessCommand("validate", []string{}, client, player) + if err != nil { + t.Fatalf("Failed to process validate command: %v", err) + } + + if result == "" { + t.Error("Expected validate command to return result") + } + + // Test tiers command + result, err = manager.ProcessCommand("tiers", []string{}, client, player) + if err != nil { + t.Fatalf("Failed to process tiers command: %v", err) + } + + if result == "" { + t.Error("Expected tiers command to return result") + } + + // Test reload command + result, err = manager.ProcessCommand("reload", []string{}, client, player) + if err != nil { + t.Fatalf("Failed to process reload command: %v", err) + } + + if result == "" { + t.Error("Expected reload command to return result") + } + + // Test unknown command + _, err = manager.ProcessCommand("unknown", []string{}, client, player) + if err == nil { + t.Error("Expected error for unknown command") + } +} + +func TestManagerStatistics(t *testing.T) { + tempDir := t.TempDir() + dbPath := filepath.Join(tempDir, "test_stats.db") + + db, err := OpenDB(dbPath) + if err != nil { + t.Fatalf("Failed to open test database: %v", err) + } + defer db.Close() + + itemMaster := NewMockItemMaster() + spellMaster := NewMockSpellMaster() + packetBuilder := NewPacketBuilder() + + manager := NewManager(db, itemMaster, spellMaster, packetBuilder) + defer manager.Shutdown() + + // Test initial statistics + stats := manager.GetStatistics() + if stats["total_transmutes"] != int64(0) { + t.Error("Expected zero total transmutes initially") + } + + // Test RecordMaterialProduced + manager.RecordMaterialProduced(1001, 5) + manager.RecordMaterialProduced(1002, 3) + manager.RecordMaterialProduced(1001, 2) // Add more of the same material + + count := manager.GetMaterialProductionCount(1001) + if count != 7 { + t.Errorf("Expected material 1001 count 7, got %d", count) + } + + count = manager.GetMaterialProductionCount(1002) + if count != 3 { + t.Errorf("Expected material 1002 count 3, got %d", count) + } + + // Test statistics after recording + stats = manager.GetStatistics() + materialStats := stats["material_counts"].(map[int32]int64) + + if materialStats[1001] != 7 { + t.Errorf("Expected material 1001 in stats to be 7, got %d", materialStats[1001]) + } + + // Test ResetStatistics + manager.ResetStatistics() + + stats = manager.GetStatistics() + if stats["total_transmutes"] != int64(0) { + t.Error("Expected statistics to be reset") + } + + count = manager.GetMaterialProductionCount(1001) + if count != 0 { + t.Error("Expected material count to be reset") + } +} + +// Test concurrent operations +func TestConcurrency(t *testing.T) { + tempDir := t.TempDir() + dbPath := filepath.Join(tempDir, "test_concurrency.db") + + db, err := OpenDB(dbPath) + if err != nil { + t.Fatalf("Failed to open test database: %v", err) + } + defer db.Close() + + itemMaster := NewMockItemMaster() + spellMaster := NewMockSpellMaster() + packetBuilder := NewPacketBuilder() + + manager := NewManager(db, itemMaster, spellMaster, packetBuilder) + defer manager.Shutdown() + + err = manager.Initialize() + if err != nil { + t.Fatalf("Failed to initialize manager: %v", err) + } + + var wg sync.WaitGroup + numGoroutines := 10 + numOperations := 100 + + // Test concurrent material recording + wg.Add(numGoroutines) + for i := 0; i < numGoroutines; i++ { + go func(id int) { + defer wg.Done() + for j := 0; j < numOperations; j++ { + materialID := int32(1000 + (id % 5)) // Use 5 different materials + manager.RecordMaterialProduced(materialID, 1) + } + }(i) + } + + wg.Wait() + + // Verify concurrent operations worked correctly + stats := manager.GetStatistics() + materialStats := stats["material_counts"].(map[int32]int64) + + totalRecorded := int64(0) + for _, count := range materialStats { + totalRecorded += count + } + + expectedTotal := int64(numGoroutines * numOperations) + if totalRecorded != expectedTotal { + t.Errorf("Expected total recorded %d, got %d", expectedTotal, totalRecorded) + } +} + +// Test packet builder +func TestPacketBuilder(t *testing.T) { + builder := NewPacketBuilder() + + // Test BuildItemRequestPacket + items := []int32{100, 200, 300} + packet, err := builder.BuildItemRequestPacket(12345, items, 1000) + if err != nil { + t.Fatalf("Failed to build item request packet: %v", err) + } + + if packet == nil { + t.Error("Expected packet to be non-nil") + } + + // Test empty items list + _, err = builder.BuildItemRequestPacket(12345, []int32{}, 1000) + if err == nil { + t.Error("Expected error for empty items list") + } + + // Test BuildConfirmationPacket + item := NewMockItem(400, 400, "Test Item", 50) + packet, err = builder.BuildConfirmationPacket(12345, item, 1000) + if err != nil { + t.Fatalf("Failed to build confirmation packet: %v", err) + } + + if packet == nil { + t.Error("Expected packet to be non-nil") + } + + // Test nil item + _, err = builder.BuildConfirmationPacket(12345, nil, 1000) + if err == nil { + t.Error("Expected error for nil item") + } + + // Test BuildRewardPacket + rewardItems := []Item{ + NewMockItem(500, 500, "Reward 1", 30), + NewMockItem(501, 501, "Reward 2", 30), + } + + packet, err = builder.BuildRewardPacket(rewardItems, 1000) + if err != nil { + t.Fatalf("Failed to build reward packet: %v", err) + } + + if packet == nil { + t.Error("Expected packet to be non-nil") + } + + // Test empty rewards + _, err = builder.BuildRewardPacket([]Item{}, 1000) + if err == nil { + t.Error("Expected error for empty rewards list") + } +} + +// Benchmark tests +func BenchmarkIsItemTransmutable(b *testing.B) { + itemMaster := NewMockItemMaster() + spellMaster := NewMockSpellMaster() + packetBuilder := NewPacketBuilder() + + transmuter := NewTransmuter(itemMaster, spellMaster, packetBuilder) + item := NewMockItem(1000, 1000, "Test Item", 50) + item.tier = ItemTagLegendary + + b.ResetTimer() + for i := 0; i < b.N; i++ { + transmuter.IsItemTransmutable(item) + } +} + +func BenchmarkDatabaseOperations(b *testing.B) { + tempDir := b.TempDir() + dbPath := filepath.Join(tempDir, "bench_db.db") + + db, err := OpenDB(dbPath) + if err != nil { + b.Fatalf("Failed to open benchmark database: %v", err) + } + defer db.Close() + + // Load initial tiers + _, err = db.LoadTransmutingTiers() + if err != nil { + b.Fatalf("Failed to load initial tiers: %v", err) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := db.GetTransmutingTierByLevel(int32(25 + (i % 75))) // Test levels 25-99 + if err != nil { + b.Fatalf("Failed to get tier by level: %v", err) + } + } +} + +func BenchmarkManagerOperations(b *testing.B) { + tempDir := b.TempDir() + dbPath := filepath.Join(tempDir, "bench_manager.db") + + db, err := OpenDB(dbPath) + if err != nil { + b.Fatalf("Failed to open benchmark database: %v", err) + } + defer db.Close() + + itemMaster := NewMockItemMaster() + spellMaster := NewMockSpellMaster() + packetBuilder := NewPacketBuilder() + + manager := NewManager(db, itemMaster, spellMaster, packetBuilder) + defer manager.Shutdown() + + err = manager.Initialize() + if err != nil { + b.Fatalf("Failed to initialize manager: %v", err) + } + + item := NewMockItem(1000, 1000, "Benchmark Item", 50) + item.tier = ItemTagLegendary + + b.ResetTimer() + for i := 0; i < b.N; i++ { + manager.IsItemTransmutable(item) + } +} \ No newline at end of file diff --git a/internal/transmute/types.go b/internal/transmute/types.go index 08158f2..669f1ff 100644 --- a/internal/transmute/types.go +++ b/internal/transmute/types.go @@ -31,8 +31,8 @@ const ( // TransmuteResult represents the outcome of a transmutation type TransmuteResult struct { Success bool // Whether transmutation was successful - CommonMaterial *Item // Common material received (if any) - RareMaterial *Item // Rare material received (if any) + CommonMaterial Item // Common material received (if any) + RareMaterial Item // Rare material received (if any) ErrorMessage string // Error message if unsuccessful SkillIncrease bool // Whether player received skill increase }