package player import ( "fmt" "strings" "testing" "time" "eq2emu/internal/quests" "zombiezen.com/go/sqlite" "zombiezen.com/go/sqlite/sqlitex" ) // TestNewPlayer tests player creation func TestNewPlayer(t *testing.T) { p := NewPlayer() if p == nil { t.Fatal("NewPlayer returned nil") } // Verify default values if p.GetCharacterID() != 0 { t.Errorf("Expected character ID 0, got %d", p.GetCharacterID()) } if p.GetTutorialStep() != 0 { t.Errorf("Expected tutorial step 0, got %d", p.GetTutorialStep()) } if !p.IsPlayer() { t.Error("Expected IsPlayer to return true") } // Check that maps are initialized if p.playerQuests == nil { t.Error("playerQuests map not initialized") } if p.completedQuests == nil { t.Error("completedQuests map not initialized") } if p.friendList == nil { t.Error("friendList map not initialized") } } // TestPlayerManager tests the player manager functionality func TestPlayerManager(t *testing.T) { config := ManagerConfig{ MaxPlayers: 100, SaveInterval: time.Minute * 5, StatsInterval: time.Second * 30, } manager := NewManager(config) if manager == nil { t.Fatal("NewManager returned nil") } // Test adding a player player := NewPlayer() player.SetSpawnID(1001) // Set unique spawn ID player.SetCharacterID(123) player.SetName("TestPlayer") player.SetLevel(10) err := manager.AddPlayer(player) if err != nil { t.Fatalf("Failed to add player: %v", err) } // Test retrieving player by ID retrieved := manager.GetPlayer(player.GetSpawnID()) if retrieved == nil { t.Error("Failed to retrieve player by ID") } // Test retrieving player by name byName := manager.GetPlayerByName("TestPlayer") if byName == nil { // Debug: Check what name was actually stored allPlayers := manager.GetAllPlayers() if len(allPlayers) > 0 { t.Errorf("Failed to retrieve player by name. Player has name: %s", allPlayers[0].GetName()) } else { t.Error("Failed to retrieve player by name. No players in manager") } } // Test retrieving player by character ID byCharID := manager.GetPlayerByCharacterID(123) if byCharID == nil { t.Error("Failed to retrieve player by character ID") } // Test player count count := manager.GetPlayerCount() if count != 1 { t.Errorf("Expected player count 1, got %d", count) } // Test removing player err = manager.RemovePlayer(player.GetSpawnID()) if err != nil { t.Fatalf("Failed to remove player: %v", err) } count = manager.GetPlayerCount() if count != 0 { t.Errorf("Expected player count 0 after removal, got %d", count) } } // TestPlayerDatabase tests database operations func TestPlayerDatabase(t *testing.T) { // Create in-memory database for testing conn, err := sqlite.OpenConn(":memory:", sqlite.OpenReadWrite|sqlite.OpenCreate) if err != nil { t.Fatalf("Failed to open database: %v", err) } defer conn.Close() // Create test table createTable := ` CREATE TABLE IF NOT EXISTS characters ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, level INTEGER DEFAULT 1, race INTEGER DEFAULT 0, class INTEGER DEFAULT 0, zone_id INTEGER DEFAULT 0, x REAL DEFAULT 0, y REAL DEFAULT 0, z REAL DEFAULT 0, heading REAL DEFAULT 0, created_date TEXT, last_save TEXT )` err = sqlitex.Execute(conn, createTable, nil) if err != nil { t.Fatalf("Failed to create table: %v", err) } db := NewPlayerDatabase(conn) // Create test player player := NewPlayer() player.SetCharacterID(1) player.SetName("TestHero") player.SetLevel(20) player.SetClass(1) player.SetRace(2) player.SetX(100.5) player.SetY(200.5, false) player.SetZ(300.5) // Test saving player err = db.SavePlayer(player) if err != nil { t.Fatalf("Failed to save player: %v", err) } // Test loading player loaded, err := db.LoadPlayer(1) if err != nil { t.Fatalf("Failed to load player: %v", err) } loadedName := strings.TrimSpace(strings.Trim(loaded.GetName(), "\x00")) if loadedName != "TestHero" { t.Errorf("Expected name TestHero, got %s", loadedName) } loadedLevel := loaded.GetLevel() if loadedLevel != 20 { t.Errorf("Expected level 20, got %d", loadedLevel) } // Test updating player loaded.SetLevel(21) err = db.SavePlayer(loaded) if err != nil { t.Fatalf("Failed to update player: %v", err) } // Test deleting player err = db.DeletePlayer(1) if err != nil { t.Fatalf("Failed to delete player: %v", err) } // Verify deletion _, err = db.LoadPlayer(1) if err == nil { t.Error("Expected error loading deleted player") } } // TestPlayerCombat tests combat-related functionality func TestPlayerCombat(t *testing.T) { player := NewPlayer() player.SetCharacterID(1) player.SetLevel(10) player.SetHP(100) player.SetTotalHP(100) // Test combat state player.InCombat(true, false) if player.GetPlayerEngageCommands() == 0 { t.Error("Expected player to be in combat") } player.InCombat(false, false) if player.GetPlayerEngageCommands() != 0 { t.Error("Expected player to not be in combat") } // Test death state player.SetHP(0) if !player.IsDead() { t.Error("Expected player to be dead with 0 HP") } player.SetHP(50) if player.IsDead() { t.Error("Expected player to be alive with 50 HP") } // Test mentorship player.SetMentorStats(5, 0, true) player.EnableResetMentorship() if !player.ResetMentorship() { t.Error("Expected mentorship reset to succeed") } } // TestPlayerExperience tests experience system func TestPlayerExperience(t *testing.T) { player := NewPlayer() player.SetCharacterID(1) player.SetLevel(1) // Set initial XP player.SetXP(0) player.SetNeededXP(1000) // Test XP gain leveledUp := player.AddXP(500) if leveledUp { t.Error("Should not level up with 500/1000 XP") } currentXP := player.GetXP() if currentXP != 500 { t.Errorf("Expected XP 500, got %d", currentXP) } // Test level up leveledUp = player.AddXP(600) // Total: 1100, should level up if !leveledUp { t.Error("Should level up with 1100/1000 XP") } newLevel := player.GetLevel() if newLevel != 2 { t.Errorf("Expected level 2 after level up, got %d", newLevel) } // Test tradeskill XP player.SetTSXP(0) player.SetNeededTSXP(500) leveledUp = player.AddTSXP(600) if !leveledUp { t.Error("Should level up tradeskill with 600/500 XP") } } // TestPlayerQuests tests quest management func TestPlayerQuests(t *testing.T) { player := NewPlayer() player.SetCharacterID(1) // Create test quest quest := &quests.Quest{ ID: 100, Name: "Test Quest", } // Test adding quest // Note: AddQuest method doesn't exist yet - would need implementation // player.AddQuest(quest) player.playerQuests[100] = quest retrieved := player.GetQuest(100) if retrieved == nil { t.Error("Failed to retrieve added quest") } allQuests := player.GetPlayerQuests() if len(allQuests) != 1 { t.Errorf("Expected 1 quest, got %d", len(allQuests)) } // Test completing quest player.RemoveQuest(100, true) retrieved = player.GetQuest(100) if retrieved != nil { t.Error("Quest should be removed after completion") } completed := player.GetCompletedQuest(100) if completed == nil { t.Error("Quest should be in completed list") } } // TestPlayerSkills tests skill management func TestPlayerSkills(t *testing.T) { player := NewPlayer() player.SetCharacterID(1) // Test adding skill player.AddSkill(1, 10, 100, true) // Test skill retrieval by name skill := player.GetSkillByName("TestSkill", false) // Note: This will return nil since we're using stubs _ = skill // Test removing skill player.RemovePlayerSkill(1, false) } // TestPlayerFlags tests character flag management func TestPlayerFlags(t *testing.T) { player := NewPlayer() player.SetCharacterID(1) // Test setting flags player.SetCharacterFlag(CF_ANONYMOUS) if !player.GetCharacterFlag(CF_ANONYMOUS) { t.Error("Expected anonymous flag to be set") } // Test resetting flags player.ResetCharacterFlag(CF_ANONYMOUS) if player.GetCharacterFlag(CF_ANONYMOUS) { t.Error("Expected anonymous flag to be reset") } // Test toggle player.ToggleCharacterFlag(CF_ANONYMOUS) if !player.GetCharacterFlag(CF_ANONYMOUS) { t.Error("Expected anonymous flag to be toggled on") } player.ToggleCharacterFlag(CF_ANONYMOUS) if player.GetCharacterFlag(CF_ANONYMOUS) { t.Error("Expected anonymous flag to be toggled off") } } // TestPlayerFriends tests friend list management func TestPlayerFriends(t *testing.T) { player := NewPlayer() player.SetCharacterID(1) // Test adding friend player.AddFriend("BestFriend", false) if !player.IsFriend("BestFriend") { t.Error("Expected BestFriend to be in friend list") } friends := player.GetFriends() if len(friends) != 1 { t.Errorf("Expected 1 friend, got %d", len(friends)) } // Test removing friend player.RemoveFriend("BestFriend") if player.IsFriend("BestFriend") { t.Error("Expected BestFriend to be removed from friend list") } } // TestPlayerIgnore tests ignore list management func TestPlayerIgnore(t *testing.T) { player := NewPlayer() player.SetCharacterID(1) // Test adding to ignore list player.AddIgnore("Annoying", false) if !player.IsIgnored("Annoying") { t.Error("Expected Annoying to be in ignore list") } ignored := player.GetIgnoredPlayers() if len(ignored) != 1 { t.Errorf("Expected 1 ignored player, got %d", len(ignored)) } // Test removing from ignore list player.RemoveIgnore("Annoying") if player.IsIgnored("Annoying") { t.Error("Expected Annoying to be removed from ignore list") } } // TestPlayerMovement tests movement-related methods func TestPlayerMovement(t *testing.T) { player := NewPlayer() player.SetCharacterID(1) // Test position player.SetX(100.5) player.SetY(200.5, false) player.SetZ(300.5) if player.GetX() != 100.5 { t.Errorf("Expected X 100.5, got %f", player.GetX()) } // Test heading player.SetHeadingFromFloat(180.0) heading := player.GetHeading() _ = heading // Heading conversion is complex, just ensure it doesn't panic // Test distance calculation distance := player.GetDistance(150.5, 250.5, 350.5, true) if distance <= 0 { t.Error("Expected positive distance") } // Test movement speeds player.SetSideSpeed(5.0, false) if player.GetSideSpeed() != 5.0 { t.Errorf("Expected side speed 5.0, got %f", player.GetSideSpeed()) } player.SetVertSpeed(3.0, false) if player.GetVertSpeed() != 3.0 { t.Errorf("Expected vert speed 3.0, got %f", player.GetVertSpeed()) } } // TestPlayerCurrency tests currency management func TestPlayerCurrency(t *testing.T) { player := NewPlayer() player.SetCharacterID(1) // Test adding coins player.AddCoins(1000) if !player.HasCoins(1000) { t.Error("Expected player to have 1000 coins") } // Test removing coins success := player.RemoveCoins(500) if !success { t.Error("Expected to successfully remove 500 coins") } if !player.HasCoins(500) { t.Error("Expected player to have 500 coins remaining") } // Test insufficient coins success = player.RemoveCoins(1000) if success { t.Error("Should not be able to remove 1000 coins when only 500 available") } } // TestPlayerSpells tests spell management func TestPlayerSpells(t *testing.T) { player := NewPlayer() player.SetCharacterID(1) // Test spell book player.AddSpellBookEntry(100, 1, 1, 0, 0, true) hasSpell := player.HasSpell(100, 1, false, false) if !hasSpell { t.Error("Expected player to have spell 100") } // Test removing spell player.RemoveSpellBookEntry(100, false) hasSpell = player.HasSpell(100, 1, false, false) if hasSpell { t.Error("Expected spell to be removed") } // Test passive spells player.ApplyPassiveSpells() player.RemoveAllPassives() } // TestPlayerInfo tests PlayerInfo functionality func TestPlayerInfo(t *testing.T) { player := NewPlayer() player.SetCharacterID(1) info := NewPlayerInfo(player) if info == nil { t.Fatal("NewPlayerInfo returned nil") } // Test bind point info.SetBindZone(100) info.SetBindX(50.0) info.SetBindY(60.0) info.SetBindZ(70.0) info.SetBindHeading(90.0) // Test house zone info.SetHouseZone(200) // Test account age info.SetAccountAge(365) // Test XP calculations player.SetXP(500) player.SetNeededXP(1000) info.CalculateXPPercentages() // Test TS XP calculations player.SetTSXP(250) player.SetNeededTSXP(500) info.CalculateTSXPPercentages() } // TestPlayerEquipment tests equipment and appearance func TestPlayerEquipment(t *testing.T) { player := NewPlayer() player.SetCharacterID(1) player.SetHP(100) // Set HP so player is not dead player.SetTotalHP(100) // Test equipment allowance check canEquip := player.IsAllowedCombatEquip(0, false) if !canEquip { t.Error("Expected to be able to change equipment out of combat") } // Test in combat equipment change player.InCombat(true, false) canEquip = player.IsAllowedCombatEquip(0, false) if canEquip { t.Error("Should not be able to change primary weapon in combat") } } // TestPlayerCleanup tests cleanup methods func TestPlayerCleanup(t *testing.T) { player := NewPlayer() player.SetCharacterID(1) // Add some data player.AddFriend("Friend1", false) player.AddIgnore("Ignored1", false) quest := &quests.Quest{ID: 1, Name: "Quest1"} // player.AddQuest(quest) - method doesn't exist yet player.playerQuests[1] = quest // Test cleanup player.ClearEverything() // Verify data is cleared friends := player.GetFriends() if len(friends) != 0 { t.Error("Expected friends list to be cleared") } ignored := player.GetIgnoredPlayers() if len(ignored) != 0 { t.Error("Expected ignore list to be cleared") } } // BenchmarkPlayerCreation benchmarks player creation func BenchmarkPlayerCreation(b *testing.B) { for i := 0; i < b.N; i++ { p := NewPlayer() p.SetCharacterID(int32(i)) p.SetName(fmt.Sprintf("Player%d", i)) } } // BenchmarkManagerOperations benchmarks manager operations func BenchmarkManagerOperations(b *testing.B) { config := ManagerConfig{ MaxPlayers: 1000, } manager := NewManager(config) // Create players players := make([]*Player, 100) for i := 0; i < 100; i++ { players[i] = NewPlayer() players[i].SetCharacterID(int32(i)) players[i].SetName(fmt.Sprintf("Player%d", i)) players[i].SetSpawnID(int32(2000 + i)) // Unique spawn IDs manager.AddPlayer(players[i]) } b.ResetTimer() for i := 0; i < b.N; i++ { // Benchmark lookups _ = manager.GetPlayer(int32(i % 100)) _ = manager.GetPlayerByCharacterID(int32(i % 100)) } } // TestConcurrentAccess tests thread safety func TestConcurrentAccess(t *testing.T) { config := ManagerConfig{ MaxPlayers: 100, } manager := NewManager(config) // Start manager err := manager.Start() if err != nil { t.Fatalf("Failed to start manager: %v", err) } defer manager.Stop() // Concurrent player additions done := make(chan bool, 10) for i := 0; i < 10; i++ { go func(id int) { player := NewPlayer() player.SetCharacterID(int32(id)) player.SetName(fmt.Sprintf("Player%d", id)) player.SetSpawnID(int32(1000 + id)) // Unique spawn IDs manager.AddPlayer(player) done <- true }(i) } // Wait for all goroutines for i := 0; i < 10; i++ { <-done } // Verify all players added count := manager.GetPlayerCount() if count != 10 { t.Errorf("Expected 10 players, got %d", count) } }