package zone import ( "database/sql" "testing" "time" "eq2emu/internal/database" _ "zombiezen.com/go/sqlite" ) // TestZoneCreation tests basic zone server creation func TestZoneCreation(t *testing.T) { zoneName := "test_zone" zoneServer := NewZoneServer(zoneName) if zoneServer == nil { t.Fatal("Expected non-nil zone server") } if zoneServer.GetZoneName() != zoneName { t.Errorf("Expected zone name '%s', got '%s'", zoneName, zoneServer.GetZoneName()) } if zoneServer.IsInitialized() { t.Error("Expected zone to not be initialized") } if zoneServer.IsShuttingDown() { t.Error("Expected zone to not be shutting down") } } // TestZoneInitialization tests zone server initialization func TestZoneInitialization(t *testing.T) { zoneServer := NewZoneServer("test_zone") config := &ZoneServerConfig{ ZoneName: "test_zone", ZoneFile: "test.zone", ZoneDescription: "Test Zone", ZoneID: 1, InstanceID: 0, InstanceType: InstanceTypeNone, MaxPlayers: 100, MinLevel: 1, MaxLevel: 100, SafeX: 0.0, SafeY: 0.0, SafeZ: 0.0, SafeHeading: 0.0, LoadMaps: false, // Don't load maps in tests EnableWeather: false, // Don't enable weather in tests EnablePathfinding: false, // Don't enable pathfinding in tests } err := zoneServer.Initialize(config) if err != nil { t.Fatalf("Failed to initialize zone server: %v", err) } if !zoneServer.IsInitialized() { t.Error("Expected zone to be initialized") } if zoneServer.GetZoneID() != 1 { t.Errorf("Expected zone ID 1, got %d", zoneServer.GetZoneID()) } if zoneServer.GetInstanceID() != 0 { t.Errorf("Expected instance ID 0, got %d", zoneServer.GetInstanceID()) } // Test safe position x, y, z, heading := zoneServer.GetSafePosition() if x != 0.0 || y != 0.0 || z != 0.0 || heading != 0.0 { t.Errorf("Expected safe position (0,0,0,0), got (%.2f,%.2f,%.2f,%.2f)", x, y, z, heading) } } // TestPositionCalculations tests position utility functions func TestPositionCalculations(t *testing.T) { // Test 2D distance distance := Distance2D(0, 0, 3, 4) if distance != 5.0 { t.Errorf("Expected 2D distance 5.0, got %.2f", distance) } // Test 3D distance distance3d := Distance3D(0, 0, 0, 3, 4, 12) if distance3d != 13.0 { t.Errorf("Expected 3D distance 13.0, got %.2f", distance3d) } // Test heading calculation heading := CalculateHeading(0, 0, 1, 1) expected := float32(64.0) // 45 degrees in EQ2 heading units (512/8) if abs(heading-expected) > 1.0 { t.Errorf("Expected heading %.2f, got %.2f", expected, heading) } // Test heading normalization normalized := NormalizeHeading(600.0) expected = 88.0 // 600 - 512 if normalized != expected { t.Errorf("Expected normalized heading %.2f, got %.2f", expected, normalized) } } // TestPositionStructs tests position data structures func TestPositionStructs(t *testing.T) { pos1 := NewPosition(10.0, 20.0, 30.0, 128.0) pos2 := NewPosition(13.0, 24.0, 30.0, 128.0) // Test distance calculation distance := pos1.DistanceTo3D(pos2) expected := float32(5.0) // 3-4-5 triangle if distance != expected { t.Errorf("Expected distance %.2f, got %.2f", expected, distance) } // Test position copy posCopy := pos1.Copy() if !pos1.Equals(posCopy) { t.Error("Expected copied position to equal original") } // Test bounding box bbox := NewBoundingBox(0, 0, 0, 10, 10, 10) if !bbox.Contains(5, 5, 5) { t.Error("Expected bounding box to contain point (5,5,5)") } if bbox.Contains(15, 5, 5) { t.Error("Expected bounding box to not contain point (15,5,5)") } } // TestMovementManager tests the movement management system func TestMovementManager(t *testing.T) { // Create a test zone zoneServer := NewZoneServer("test_zone") config := &ZoneServerConfig{ ZoneName: "test_zone", ZoneID: 1, LoadMaps: false, EnableWeather: false, EnablePathfinding: false, } err := zoneServer.Initialize(config) if err != nil { t.Fatalf("Failed to initialize zone server: %v", err) } // Test movement manager creation movementMgr := NewMobMovementManager(zoneServer) if movementMgr == nil { t.Fatal("Expected non-nil movement manager") } // Test adding a spawn to movement tracking spawnID := int32(1001) movementMgr.AddMovementSpawn(spawnID) if !movementMgr.IsMoving(spawnID) == false { // IsMoving should be false initially } // Test queueing a movement command err = movementMgr.MoveTo(spawnID, 10.0, 20.0, 30.0, DefaultRunSpeed) if err != nil { t.Errorf("Failed to queue movement command: %v", err) } // Test getting movement state state := movementMgr.GetMovementState(spawnID) if state == nil { t.Error("Expected non-nil movement state") } else if state.SpawnID != spawnID { t.Errorf("Expected spawn ID %d, got %d", spawnID, state.SpawnID) } } // TestInstanceTypes tests instance type functionality func TestInstanceTypes(t *testing.T) { testCases := []struct { instanceType InstanceType expected string }{ {InstanceTypeNone, "None"}, {InstanceTypeGroupLockout, "Group Lockout"}, {InstanceTypeRaidPersist, "Raid Persistent"}, {InstanceTypePersonalHouse, "Personal House"}, } for _, tc := range testCases { result := tc.instanceType.String() if result != tc.expected { t.Errorf("Expected instance type string '%s', got '%s'", tc.expected, result) } } } // TestZoneManager tests the zone manager functionality func TestZoneManager(t *testing.T) { // Create test database db, err := sql.Open("sqlite", ":memory:") if err != nil { t.Fatalf("Failed to create test database: %v", err) } defer db.Close() // Create test schema schema := ` CREATE TABLE zones ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, file TEXT, description TEXT, safe_x REAL DEFAULT 0, safe_y REAL DEFAULT 0, safe_z REAL DEFAULT 0, safe_heading REAL DEFAULT 0, underworld REAL DEFAULT -1000, min_level INTEGER DEFAULT 0, max_level INTEGER DEFAULT 0, min_status INTEGER DEFAULT 0, min_version INTEGER DEFAULT 0, instance_type INTEGER DEFAULT 0, max_players INTEGER DEFAULT 100, default_lockout_time INTEGER DEFAULT 18000, default_reenter_time INTEGER DEFAULT 3600, default_reset_time INTEGER DEFAULT 259200, group_zone_option INTEGER DEFAULT 0, expansion_flag INTEGER DEFAULT 0, holiday_flag INTEGER DEFAULT 0, can_bind INTEGER DEFAULT 1, can_gate INTEGER DEFAULT 1, can_evac INTEGER DEFAULT 1, city_zone INTEGER DEFAULT 0, always_loaded INTEGER DEFAULT 0, weather_allowed INTEGER DEFAULT 1 ); INSERT INTO zones (id, name, file, description) VALUES (1, 'test_zone', 'test.zone', 'Test Zone'); ` if _, err := db.Exec(schema); err != nil { t.Fatalf("Failed to create test schema: %v", err) } // Create database wrapper dbWrapper := &database.Database{DB: db} // Create zone manager config := &ZoneManagerConfig{ MaxZones: 10, MaxInstanceZones: 50, ProcessInterval: time.Millisecond * 100, CleanupInterval: time.Second * 1, EnableWeather: false, EnablePathfinding: false, EnableCombat: false, EnableSpellProcess: false, } zoneManager := NewZoneManager(config, dbWrapper) if zoneManager == nil { t.Fatal("Expected non-nil zone manager") } // Test zone count initially if zoneManager.GetZoneCount() != 0 { t.Errorf("Expected 0 zones initially, got %d", zoneManager.GetZoneCount()) } // Note: Full zone manager testing would require more complex setup // including proper database schema and mock implementations } // TestWeatherSystem tests weather functionality func TestWeatherSystem(t *testing.T) { zoneServer := NewZoneServer("test_zone") // Initialize with weather enabled config := &ZoneServerConfig{ ZoneName: "test_zone", ZoneID: 1, LoadMaps: false, EnableWeather: true, EnablePathfinding: false, } err := zoneServer.Initialize(config) if err != nil { t.Fatalf("Failed to initialize zone server: %v", err) } // Test setting rain level zoneServer.SetRain(0.5) // Test weather processing (this is mostly internal) zoneServer.ProcessWeather() // Weather system would need more sophisticated testing with time control } // TestConstants tests various constants are properly defined func TestConstants(t *testing.T) { // Test distance constants if SendSpawnDistance != 250.0 { t.Errorf("Expected SendSpawnDistance 250.0, got %.2f", SendSpawnDistance) } if MaxChaseDistance != 80.0 { t.Errorf("Expected MaxChaseDistance 80.0, got %.2f", MaxChaseDistance) } // Test expansion constants if ExpansionDOF != 1024 { t.Errorf("Expected ExpansionDOF 1024, got %d", ExpansionDOF) } // Test EQ2 heading constant if EQ2HeadingMax != 512.0 { t.Errorf("Expected EQ2HeadingMax 512.0, got %.2f", EQ2HeadingMax) } } // BenchmarkDistanceCalculation benchmarks distance calculations func BenchmarkDistanceCalculation(b *testing.B) { x1, y1, z1 := float32(100.0), float32(200.0), float32(300.0) x2, y2, z2 := float32(150.0), float32(250.0), float32(350.0) b.ResetTimer() for i := 0; i < b.N; i++ { Distance3D(x1, y1, z1, x2, y2, z2) } } // BenchmarkPositionDistance benchmarks position-based distance calculations func BenchmarkPositionDistance(b *testing.B) { pos1 := NewPosition(100.0, 200.0, 300.0, 128.0) pos2 := NewPosition(150.0, 250.0, 350.0, 256.0) b.ResetTimer() for i := 0; i < b.N; i++ { pos1.DistanceTo3D(pos2) } } // BenchmarkHeadingCalculation benchmarks heading calculations func BenchmarkHeadingCalculation(b *testing.B) { fromX, fromY := float32(0.0), float32(0.0) toX, toY := float32(100.0), float32(100.0) b.ResetTimer() for i := 0; i < b.N; i++ { CalculateHeading(fromX, fromY, toX, toY) } } // MockClient implements the Client interface for testing type MockClient struct { id uint32 characterID int32 playerName string position *Position clientVersion int32 loadingZone bool inCombat bool spawnRange float32 languageID int32 } func NewMockClient(id uint32, name string) *MockClient { return &MockClient{ id: id, characterID: int32(id), playerName: name, position: NewPosition(0, 0, 0, 0), clientVersion: DefaultClientVersion, loadingZone: false, inCombat: false, spawnRange: SendSpawnDistance, languageID: 0, } } func (mc *MockClient) GetID() uint32 { return mc.id } func (mc *MockClient) GetCharacterID() int32 { return mc.characterID } func (mc *MockClient) GetPlayerName() string { return mc.playerName } func (mc *MockClient) GetPlayer() Player { return nil } // TODO: Mock player func (mc *MockClient) GetClientVersion() int32 { return mc.clientVersion } func (mc *MockClient) IsLoadingZone() bool { return mc.loadingZone } func (mc *MockClient) SendPacket(data []byte) error { return nil } func (mc *MockClient) GetSpawnRange() float32 { return mc.spawnRange } func (mc *MockClient) IsInCombat() bool { return mc.inCombat } func (mc *MockClient) GetLanguageID() int32 { return mc.languageID } func (mc *MockClient) GetLanguageSkill(languageID int32) int32 { return 100 } func (mc *MockClient) GetPosition() (x, y, z, heading float32, zoneID int32) { return mc.position.X, mc.position.Y, mc.position.Z, mc.position.Heading, 1 } func (mc *MockClient) SetPosition(x, y, z, heading float32, zoneID int32) { mc.position.Set(x, y, z, heading) } func (mc *MockClient) CanSeeSpawn(spawn *Spawn) bool { // Simple visibility check based on distance spawnX, spawnY, spawnZ, _ := spawn.GetPosition() distance := Distance3D(mc.position.X, mc.position.Y, mc.position.Z, spawnX, spawnY, spawnZ) return distance <= mc.spawnRange } // Placeholder import fix type Spawn = interface { GetID() int32 GetPosition() (x, y, z, heading float32) }