eq2go/internal/zone/zone_test.go

423 lines
12 KiB
Go

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)
}