662 lines
15 KiB
Go
662 lines
15 KiB
Go
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)
|
|
}
|
|
} |