423 lines
12 KiB
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)
|
|
}
|