fix sign package
This commit is contained in:
parent
c67a7bf6c6
commit
3b6d35ce98
@ -102,7 +102,22 @@ func (ss *SignSpawn) HandleUse(client Client, command string) error {
|
||||
// Copy creates a copy of the sign spawn
|
||||
func (ss *SignSpawn) Copy() *SignSpawn {
|
||||
newSign := ss.Sign.Copy()
|
||||
newSpawn := ss.Spawn.Copy()
|
||||
// TODO: Enable when spawn.Copy() method is implemented
|
||||
// newSpawn := ss.Spawn.Copy()
|
||||
|
||||
// For now, create a new spawn and copy basic properties
|
||||
newSpawn := spawn.NewSpawn()
|
||||
if ss.Spawn != nil {
|
||||
newSpawn.SetDatabaseID(ss.Spawn.GetDatabaseID())
|
||||
newSpawn.SetName(ss.Spawn.GetName())
|
||||
newSpawn.SetLevel(ss.Spawn.GetLevel())
|
||||
newSpawn.SetX(ss.Spawn.GetX())
|
||||
newSpawn.SetY(ss.Spawn.GetY(), false)
|
||||
newSpawn.SetZ(ss.Spawn.GetZ())
|
||||
newSpawn.SetSpawnType(ss.Spawn.GetSpawnType())
|
||||
newSpawn.SetFactionID(ss.Spawn.GetFactionID())
|
||||
newSpawn.SetSize(ss.Spawn.GetSize())
|
||||
}
|
||||
|
||||
return &SignSpawn{
|
||||
Spawn: newSpawn,
|
||||
|
@ -60,6 +60,9 @@ func (m *Manager) LoadZoneSigns(zoneID int32) error {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
// Store signs by zone ID for now since spawn doesn't have zone support yet
|
||||
m.signsByZone[zoneID] = signs
|
||||
|
||||
for _, sign := range signs {
|
||||
m.addSignUnsafe(sign)
|
||||
}
|
||||
@ -95,6 +98,33 @@ func (m *Manager) AddSign(sign *Sign) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddSignToZone adds a sign to the manager for a specific zone
|
||||
func (m *Manager) AddSignToZone(sign *Sign, zoneID int32) error {
|
||||
if sign == nil {
|
||||
return fmt.Errorf("sign is nil")
|
||||
}
|
||||
|
||||
// Validate the sign
|
||||
if issues := sign.Validate(); len(issues) > 0 {
|
||||
return fmt.Errorf("sign validation failed: %v", issues)
|
||||
}
|
||||
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
// Add to zone collection
|
||||
m.signsByZone[zoneID] = append(m.signsByZone[zoneID], sign)
|
||||
|
||||
m.addSignUnsafe(sign)
|
||||
|
||||
if m.logger != nil {
|
||||
m.logger.LogInfo("Added sign %d (widget %d) of type %d to zone %d",
|
||||
sign.Spawn.GetDatabaseID(), sign.GetWidgetID(), sign.GetSignType(), zoneID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// addSignUnsafe adds a sign without locking (internal use)
|
||||
func (m *Manager) addSignUnsafe(sign *Sign) {
|
||||
signID := sign.Spawn.GetDatabaseID()
|
||||
@ -109,10 +139,11 @@ func (m *Manager) addSignUnsafe(sign *Sign) {
|
||||
}
|
||||
|
||||
// Add to zone collection
|
||||
if sign.Spawn != nil {
|
||||
zoneID := sign.Spawn.GetZone()
|
||||
m.signsByZone[zoneID] = append(m.signsByZone[zoneID], sign)
|
||||
}
|
||||
// TODO: Enable when spawn system has zone support
|
||||
// if sign.Spawn != nil {
|
||||
// zoneID := sign.Spawn.GetZone()
|
||||
// m.signsByZone[zoneID] = append(m.signsByZone[zoneID], sign)
|
||||
// }
|
||||
|
||||
// Update statistics
|
||||
m.totalSigns++
|
||||
@ -138,17 +169,18 @@ func (m *Manager) RemoveSign(signID int32) bool {
|
||||
}
|
||||
|
||||
// Remove from zone collection
|
||||
if sign.Spawn != nil {
|
||||
zoneID := sign.Spawn.GetZone()
|
||||
if zoneSigns, exists := m.signsByZone[zoneID]; exists {
|
||||
for i, zoneSign := range zoneSigns {
|
||||
if zoneSign == sign {
|
||||
m.signsByZone[zoneID] = append(zoneSigns[:i], zoneSigns[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: Enable when spawn system has zone support
|
||||
// if sign.Spawn != nil {
|
||||
// zoneID := sign.Spawn.GetZone()
|
||||
// if zoneSigns, exists := m.signsByZone[zoneID]; exists {
|
||||
// for i, zoneSign := range zoneSigns {
|
||||
// if zoneSign == sign {
|
||||
// m.signsByZone[zoneID] = append(zoneSigns[:i], zoneSigns[i+1:]...)
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// Update statistics
|
||||
m.totalSigns--
|
||||
@ -246,9 +278,10 @@ func (m *Manager) HandleSignUse(sign *Sign, client Client, command string) error
|
||||
if sign.IsZoneSign() {
|
||||
m.zoneTransports++
|
||||
}
|
||||
if sign.Spawn != nil && sign.Spawn.GetTransporterID() > 0 {
|
||||
m.transporterUses++
|
||||
}
|
||||
// TODO: Enable when transporter system is implemented
|
||||
// if sign.Spawn != nil && sign.Spawn.GetTransporterID() > 0 {
|
||||
// m.transporterUses++
|
||||
// }
|
||||
m.mutex.Unlock()
|
||||
}
|
||||
|
||||
@ -439,7 +472,7 @@ func (m *Manager) handleInfoCommand(args []string) (string, error) {
|
||||
return fmt.Sprintf("Sign %d not found.", signID), nil
|
||||
}
|
||||
|
||||
result := fmt.Sprintf("Sign Information:\n")
|
||||
result := "Sign Information:\n"
|
||||
result += fmt.Sprintf("ID: %d\n", signID)
|
||||
result += fmt.Sprintf("Widget ID: %d\n", sign.GetWidgetID())
|
||||
result += fmt.Sprintf("Type: %d\n", sign.GetSignType())
|
||||
|
@ -2,7 +2,6 @@ package sign
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@ -12,26 +11,17 @@ func (s *Sign) Copy() *Sign {
|
||||
|
||||
// Copy spawn data
|
||||
if s.Spawn != nil {
|
||||
// Handle size randomization like the C++ version
|
||||
if s.Spawn.GetSizeOffset() > 0 {
|
||||
offset := s.Spawn.GetSizeOffset() + 1
|
||||
tmpSize := int32(s.Spawn.GetSize()) + (rand.Int31n(int32(offset)) - rand.Int31n(int32(offset)))
|
||||
|
||||
if tmpSize < 0 {
|
||||
tmpSize = 1
|
||||
} else if tmpSize >= 0xFFFF {
|
||||
tmpSize = 0xFFFF
|
||||
}
|
||||
|
||||
newSign.Spawn.SetSize(int16(tmpSize))
|
||||
} else {
|
||||
newSign.Spawn.SetSize(s.Spawn.GetSize())
|
||||
}
|
||||
|
||||
// Copy other spawn properties
|
||||
// Copy basic spawn properties that exist
|
||||
newSign.Spawn.SetSize(s.Spawn.GetSize())
|
||||
newSign.Spawn.SetDatabaseID(s.Spawn.GetDatabaseID())
|
||||
newSign.Spawn.SetMerchantID(s.Spawn.GetMerchantID())
|
||||
newSign.Spawn.SetMerchantType(s.Spawn.GetMerchantType())
|
||||
newSign.Spawn.SetName(s.Spawn.GetName())
|
||||
newSign.Spawn.SetLevel(s.Spawn.GetLevel())
|
||||
newSign.Spawn.SetX(s.Spawn.GetX())
|
||||
newSign.Spawn.SetY(s.Spawn.GetY(), false)
|
||||
newSign.Spawn.SetZ(s.Spawn.GetZ())
|
||||
newSign.Spawn.SetSpawnType(s.Spawn.GetSpawnType())
|
||||
newSign.Spawn.SetFactionID(s.Spawn.GetFactionID())
|
||||
|
||||
// TODO: Copy appearance data when spawn system is fully integrated
|
||||
// TODO: Copy command lists when command system is integrated
|
||||
// TODO: Copy transporter ID, sounds, loot properties, etc.
|
||||
@ -60,12 +50,13 @@ func (s *Sign) Copy() *Sign {
|
||||
|
||||
// Serialize creates a packet for sending the sign to a client
|
||||
func (s *Sign) Serialize(player Player, version int16) ([]byte, error) {
|
||||
// TODO: Implement serialization when spawn system supports it
|
||||
// Delegate to spawn serialization
|
||||
if s.Spawn != nil {
|
||||
return s.Spawn.Serialize(player, version)
|
||||
}
|
||||
// if s.Spawn != nil {
|
||||
// return s.Spawn.Serialize(player, version)
|
||||
// }
|
||||
|
||||
return nil, fmt.Errorf("spawn is nil")
|
||||
return nil, fmt.Errorf("sign serialization not yet implemented")
|
||||
}
|
||||
|
||||
// HandleUse processes player interaction with the sign
|
||||
@ -85,9 +76,10 @@ func (s *Sign) HandleUse(client Client, command string) error {
|
||||
}
|
||||
|
||||
// Handle transporter functionality first
|
||||
if s.Spawn != nil && s.Spawn.GetTransporterID() > 0 {
|
||||
return s.handleTransporter(client)
|
||||
}
|
||||
// TODO: Enable when transporter system is implemented in spawn
|
||||
// if s.Spawn != nil && s.Spawn.GetTransporterID() > 0 {
|
||||
// return s.handleTransporter(client)
|
||||
// }
|
||||
|
||||
// Handle zone transport signs
|
||||
if s.signType == SignTypeZone && s.zoneID > 0 {
|
||||
@ -115,26 +107,11 @@ func (s *Sign) meetsQuestRequirements(client Client) bool {
|
||||
}
|
||||
|
||||
// handleTransporter processes transporter functionality
|
||||
// TODO: Enable when transporter system is implemented in spawn
|
||||
func (s *Sign) handleTransporter(client Client) error {
|
||||
zone := client.GetPlayer().GetZone()
|
||||
if zone == nil {
|
||||
return fmt.Errorf("player not in zone")
|
||||
}
|
||||
|
||||
transporterID := s.Spawn.GetTransporterID()
|
||||
|
||||
// Get transport destinations
|
||||
destinations, err := zone.GetTransporters(client, transporterID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get transporters: %w", err)
|
||||
}
|
||||
|
||||
if len(destinations) > 0 {
|
||||
client.SetTemporaryTransportID(0)
|
||||
return client.ProcessTeleport(s, destinations, transporterID)
|
||||
}
|
||||
|
||||
return nil
|
||||
// Placeholder implementation for transporter functionality
|
||||
// This will be enabled when the spawn system supports transporters
|
||||
return fmt.Errorf("transporter system not yet implemented")
|
||||
}
|
||||
|
||||
// handleZoneTransport processes zone transport functionality
|
||||
@ -188,26 +165,25 @@ func (s *Sign) handleEntityCommand(client Client, command string) error {
|
||||
return fmt.Errorf("spawn is nil")
|
||||
}
|
||||
|
||||
entityCommand := s.Spawn.FindEntityCommand(command)
|
||||
if entityCommand == nil {
|
||||
return nil // Command not found
|
||||
}
|
||||
|
||||
// Handle mark command specially
|
||||
if strings.ToLower(entityCommand.Command) == "mark" {
|
||||
// TODO: Implement entity command finding when spawn system supports it
|
||||
// entityCommand := s.Spawn.FindEntityCommand(command)
|
||||
// For now, handle mark command directly
|
||||
if strings.ToLower(command) == "mark" {
|
||||
return s.handleMarkCommand(client)
|
||||
}
|
||||
|
||||
// Process the entity command
|
||||
zone := client.GetCurrentZone()
|
||||
if zone == nil {
|
||||
return fmt.Errorf("player not in zone")
|
||||
}
|
||||
|
||||
player := client.GetPlayer()
|
||||
target := player.GetTarget()
|
||||
|
||||
return zone.ProcessEntityCommand(entityCommand, player, target)
|
||||
// TODO: Process other entity commands when system is implemented
|
||||
// zone := client.GetCurrentZone()
|
||||
// if zone == nil {
|
||||
// return fmt.Errorf("player not in zone")
|
||||
// }
|
||||
//
|
||||
// player := client.GetPlayer()
|
||||
// target := player.GetTarget()
|
||||
//
|
||||
// return zone.ProcessEntityCommand(entityCommand, player, target)
|
||||
|
||||
return nil // Command not handled
|
||||
}
|
||||
|
||||
// handleMarkCommand processes the mark command for marking signs
|
||||
|
904
internal/sign/sign_test.go
Normal file
904
internal/sign/sign_test.go
Normal file
@ -0,0 +1,904 @@
|
||||
package sign
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"eq2emu/internal/spawn"
|
||||
)
|
||||
|
||||
// Mock implementations for testing
|
||||
|
||||
// MockLogger implements the Logger interface for testing
|
||||
type MockLogger struct {
|
||||
logs []string
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (ml *MockLogger) LogInfo(message string, args ...any) {
|
||||
ml.mu.Lock()
|
||||
defer ml.mu.Unlock()
|
||||
ml.logs = append(ml.logs, fmt.Sprintf("INFO: "+message, args...))
|
||||
}
|
||||
|
||||
func (ml *MockLogger) LogError(message string, args ...any) {
|
||||
ml.mu.Lock()
|
||||
defer ml.mu.Unlock()
|
||||
ml.logs = append(ml.logs, fmt.Sprintf("ERROR: "+message, args...))
|
||||
}
|
||||
|
||||
func (ml *MockLogger) LogDebug(message string, args ...any) {
|
||||
ml.mu.Lock()
|
||||
defer ml.mu.Unlock()
|
||||
ml.logs = append(ml.logs, fmt.Sprintf("DEBUG: "+message, args...))
|
||||
}
|
||||
|
||||
func (ml *MockLogger) LogWarning(message string, args ...any) {
|
||||
ml.mu.Lock()
|
||||
defer ml.mu.Unlock()
|
||||
ml.logs = append(ml.logs, fmt.Sprintf("WARNING: "+message, args...))
|
||||
}
|
||||
|
||||
func (ml *MockLogger) GetLogs() []string {
|
||||
ml.mu.Lock()
|
||||
defer ml.mu.Unlock()
|
||||
result := make([]string, len(ml.logs))
|
||||
copy(result, ml.logs)
|
||||
return result
|
||||
}
|
||||
|
||||
func (ml *MockLogger) Clear() {
|
||||
ml.mu.Lock()
|
||||
defer ml.mu.Unlock()
|
||||
ml.logs = ml.logs[:0]
|
||||
}
|
||||
|
||||
// MockDatabase implements the Database interface for testing
|
||||
type MockDatabase struct {
|
||||
signs map[int32][]*Sign
|
||||
zoneNames map[int32]string
|
||||
charNames map[int32]string
|
||||
signMarks map[int32]map[int32]string // charID -> widgetID -> charName
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewMockDatabase() *MockDatabase {
|
||||
return &MockDatabase{
|
||||
signs: make(map[int32][]*Sign),
|
||||
zoneNames: make(map[int32]string),
|
||||
charNames: make(map[int32]string),
|
||||
signMarks: make(map[int32]map[int32]string),
|
||||
}
|
||||
}
|
||||
|
||||
func (md *MockDatabase) GetZoneName(zoneID int32) (string, error) {
|
||||
md.mu.RLock()
|
||||
defer md.mu.RUnlock()
|
||||
if name, exists := md.zoneNames[zoneID]; exists {
|
||||
return name, nil
|
||||
}
|
||||
return "", fmt.Errorf("zone %d not found", zoneID)
|
||||
}
|
||||
|
||||
func (md *MockDatabase) GetCharacterName(charID int32) (string, error) {
|
||||
md.mu.RLock()
|
||||
defer md.mu.RUnlock()
|
||||
if name, exists := md.charNames[charID]; exists {
|
||||
return name, nil
|
||||
}
|
||||
return "", fmt.Errorf("character %d not found", charID)
|
||||
}
|
||||
|
||||
func (md *MockDatabase) SaveSignMark(charID int32, widgetID int32, charName string, client Client) error {
|
||||
md.mu.Lock()
|
||||
defer md.mu.Unlock()
|
||||
if md.signMarks[charID] == nil {
|
||||
md.signMarks[charID] = make(map[int32]string)
|
||||
}
|
||||
md.signMarks[charID][widgetID] = charName
|
||||
return nil
|
||||
}
|
||||
|
||||
func (md *MockDatabase) LoadSigns(zoneID int32) ([]*Sign, error) {
|
||||
md.mu.RLock()
|
||||
defer md.mu.RUnlock()
|
||||
return md.signs[zoneID], nil
|
||||
}
|
||||
|
||||
func (md *MockDatabase) SaveSign(sign *Sign) error {
|
||||
return nil // No-op for testing
|
||||
}
|
||||
|
||||
func (md *MockDatabase) DeleteSign(signID int32) error {
|
||||
return nil // No-op for testing
|
||||
}
|
||||
|
||||
func (md *MockDatabase) AddZone(zoneID int32, name string) {
|
||||
md.mu.Lock()
|
||||
defer md.mu.Unlock()
|
||||
md.zoneNames[zoneID] = name
|
||||
}
|
||||
|
||||
func (md *MockDatabase) AddCharacter(charID int32, name string) {
|
||||
md.mu.Lock()
|
||||
defer md.mu.Unlock()
|
||||
md.charNames[charID] = name
|
||||
}
|
||||
|
||||
func (md *MockDatabase) AddSignToZone(zoneID int32, sign *Sign) {
|
||||
md.mu.Lock()
|
||||
defer md.mu.Unlock()
|
||||
md.signs[zoneID] = append(md.signs[zoneID], sign)
|
||||
}
|
||||
|
||||
// MockPlayer implements the Player interface for testing
|
||||
type MockPlayer struct {
|
||||
x, y, z, heading float32
|
||||
zone Zone
|
||||
target *spawn.Spawn
|
||||
}
|
||||
|
||||
func (mp *MockPlayer) GetDistance(target *spawn.Spawn) float32 {
|
||||
if target == nil {
|
||||
return 0
|
||||
}
|
||||
return 5.0 // Mock distance
|
||||
}
|
||||
|
||||
func (mp *MockPlayer) SetX(x float32) { mp.x = x }
|
||||
func (mp *MockPlayer) SetY(y float32) { mp.y = y }
|
||||
func (mp *MockPlayer) SetZ(z float32) { mp.z = z }
|
||||
func (mp *MockPlayer) SetHeading(heading float32) { mp.heading = heading }
|
||||
func (mp *MockPlayer) GetZone() Zone { return mp.zone }
|
||||
func (mp *MockPlayer) GetTarget() *spawn.Spawn { return mp.target }
|
||||
|
||||
// MockZone implements the Zone interface for testing
|
||||
type MockZone struct {
|
||||
transporters map[int32][]TransportDestination
|
||||
}
|
||||
|
||||
func (mz *MockZone) GetTransporters(client Client, transporterID int32) ([]TransportDestination, error) {
|
||||
if transporters, exists := mz.transporters[transporterID]; exists {
|
||||
return transporters, nil
|
||||
}
|
||||
return nil, fmt.Errorf("transporter %d not found", transporterID)
|
||||
}
|
||||
|
||||
func (mz *MockZone) ProcessEntityCommand(command *EntityCommand, player Player, target *spawn.Spawn) error {
|
||||
return nil // No-op for testing
|
||||
}
|
||||
|
||||
// MockClient implements the Client interface for testing
|
||||
type MockClient struct {
|
||||
player Player
|
||||
charID int32
|
||||
database Database
|
||||
zone Zone
|
||||
messages []string
|
||||
tempTransportID int32
|
||||
zoneAccess map[string]bool
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (mc *MockClient) GetPlayer() Player { return mc.player }
|
||||
func (mc *MockClient) GetCharacterID() int32 { return mc.charID }
|
||||
func (mc *MockClient) GetDatabase() Database { return mc.database }
|
||||
func (mc *MockClient) GetCurrentZone() Zone { return mc.zone }
|
||||
func (mc *MockClient) SetTemporaryTransportID(id int32) { mc.tempTransportID = id }
|
||||
|
||||
func (mc *MockClient) SimpleMessage(channel int32, message string) {
|
||||
mc.mu.Lock()
|
||||
defer mc.mu.Unlock()
|
||||
mc.messages = append(mc.messages, message)
|
||||
}
|
||||
|
||||
func (mc *MockClient) Message(channel int32, format string, args ...any) {
|
||||
mc.mu.Lock()
|
||||
defer mc.mu.Unlock()
|
||||
mc.messages = append(mc.messages, fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (mc *MockClient) CheckZoneAccess(zoneName string) bool {
|
||||
return mc.zoneAccess[zoneName]
|
||||
}
|
||||
|
||||
func (mc *MockClient) TryZoneInstance(zoneID int32, useDefaults bool) bool {
|
||||
return false // Mock always uses regular zones
|
||||
}
|
||||
|
||||
func (mc *MockClient) Zone(zoneName string, useDefaults bool) error {
|
||||
return nil // No-op for testing
|
||||
}
|
||||
|
||||
func (mc *MockClient) ProcessTeleport(sign *Sign, destinations []TransportDestination, transporterID int32) error {
|
||||
return nil // No-op for testing
|
||||
}
|
||||
|
||||
func (mc *MockClient) GetMessages() []string {
|
||||
mc.mu.Lock()
|
||||
defer mc.mu.Unlock()
|
||||
result := make([]string, len(mc.messages))
|
||||
copy(result, mc.messages)
|
||||
return result
|
||||
}
|
||||
|
||||
func (mc *MockClient) ClearMessages() {
|
||||
mc.mu.Lock()
|
||||
defer mc.mu.Unlock()
|
||||
mc.messages = mc.messages[:0]
|
||||
}
|
||||
|
||||
// MockEntity implements the Entity interface for testing
|
||||
type MockEntity struct {
|
||||
id int32
|
||||
name string
|
||||
databaseID int32
|
||||
}
|
||||
|
||||
func (me *MockEntity) GetID() int32 { return me.id }
|
||||
func (me *MockEntity) GetName() string { return me.name }
|
||||
func (me *MockEntity) GetDatabaseID() int32 { return me.databaseID }
|
||||
|
||||
// Helper functions for testing
|
||||
|
||||
func createTestSign() *Sign {
|
||||
sign := NewSign()
|
||||
sign.Spawn.SetDatabaseID(1)
|
||||
sign.Spawn.SetName("Test Sign")
|
||||
sign.SetWidgetID(100)
|
||||
sign.SetSignTitle("Welcome")
|
||||
sign.SetSignDescription("This is a test sign")
|
||||
return sign
|
||||
}
|
||||
|
||||
func createZoneSign() *Sign {
|
||||
sign := createTestSign()
|
||||
sign.SetSignType(SignTypeZone)
|
||||
sign.SetSignZoneID(2)
|
||||
sign.SetSignZoneX(100.0)
|
||||
sign.SetSignZoneY(200.0)
|
||||
sign.SetSignZoneZ(300.0)
|
||||
sign.SetSignZoneHeading(45.0)
|
||||
return sign
|
||||
}
|
||||
|
||||
// Tests
|
||||
|
||||
func TestNewSign(t *testing.T) {
|
||||
sign := NewSign()
|
||||
|
||||
if sign == nil {
|
||||
t.Fatal("NewSign() returned nil")
|
||||
}
|
||||
|
||||
if sign.Spawn == nil {
|
||||
t.Error("Sign spawn is nil")
|
||||
}
|
||||
|
||||
if sign.GetSignType() != SignTypeGeneric {
|
||||
t.Errorf("Expected sign type %d, got %d", SignTypeGeneric, sign.GetSignType())
|
||||
}
|
||||
|
||||
if !sign.IsSign() {
|
||||
t.Error("IsSign() should return true")
|
||||
}
|
||||
|
||||
if sign.IsZoneSign() {
|
||||
t.Error("IsZoneSign() should return false for generic sign")
|
||||
}
|
||||
|
||||
if !sign.IsGenericSign() {
|
||||
t.Error("IsGenericSign() should return true for generic sign")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignProperties(t *testing.T) {
|
||||
sign := NewSign()
|
||||
|
||||
// Test widget properties
|
||||
sign.SetWidgetID(123)
|
||||
if sign.GetWidgetID() != 123 {
|
||||
t.Errorf("Expected widget ID 123, got %d", sign.GetWidgetID())
|
||||
}
|
||||
|
||||
sign.SetWidgetX(10.5)
|
||||
if sign.GetWidgetX() != 10.5 {
|
||||
t.Errorf("Expected widget X 10.5, got %f", sign.GetWidgetX())
|
||||
}
|
||||
|
||||
// Test sign properties
|
||||
sign.SetSignTitle("Test Title")
|
||||
if sign.GetSignTitle() != "Test Title" {
|
||||
t.Errorf("Expected title 'Test Title', got '%s'", sign.GetSignTitle())
|
||||
}
|
||||
|
||||
sign.SetSignDescription("Test Description")
|
||||
if sign.GetSignDescription() != "Test Description" {
|
||||
t.Errorf("Expected description 'Test Description', got '%s'", sign.GetSignDescription())
|
||||
}
|
||||
|
||||
// Test zone properties
|
||||
sign.SetSignZoneID(456)
|
||||
if sign.GetSignZoneID() != 456 {
|
||||
t.Errorf("Expected zone ID 456, got %d", sign.GetSignZoneID())
|
||||
}
|
||||
|
||||
sign.SetSignZoneX(100.0)
|
||||
sign.SetSignZoneY(200.0)
|
||||
sign.SetSignZoneZ(300.0)
|
||||
sign.SetSignZoneHeading(45.0)
|
||||
|
||||
if !sign.HasZoneCoordinates() {
|
||||
t.Error("Should have zone coordinates")
|
||||
}
|
||||
|
||||
// Test display options
|
||||
sign.SetIncludeLocation(true)
|
||||
if !sign.GetIncludeLocation() {
|
||||
t.Error("Include location should be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignValidation(t *testing.T) {
|
||||
// Valid sign
|
||||
sign := createTestSign()
|
||||
if !sign.IsValid() {
|
||||
t.Error("Sign should be valid")
|
||||
}
|
||||
|
||||
issues := sign.Validate()
|
||||
if len(issues) > 0 {
|
||||
t.Errorf("Valid sign should have no issues, got: %v", issues)
|
||||
}
|
||||
|
||||
// Invalid sign - no widget ID
|
||||
sign.SetWidgetID(0)
|
||||
if sign.IsValid() {
|
||||
t.Error("Sign without widget ID should be invalid")
|
||||
}
|
||||
|
||||
issues = sign.Validate()
|
||||
if len(issues) == 0 {
|
||||
t.Error("Sign without widget ID should have validation issues")
|
||||
}
|
||||
|
||||
// Invalid sign - title too long
|
||||
sign.SetWidgetID(100)
|
||||
sign.SetSignTitle(strings.Repeat("a", MaxSignTitleLength+1))
|
||||
|
||||
issues = sign.Validate()
|
||||
found := false
|
||||
for _, issue := range issues {
|
||||
if strings.Contains(issue, "title too long") {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Error("Should have title too long validation issue")
|
||||
}
|
||||
|
||||
// Invalid zone sign - no zone ID
|
||||
sign = createTestSign()
|
||||
sign.SetSignType(SignTypeZone)
|
||||
// Don't set zone ID
|
||||
|
||||
issues = sign.Validate()
|
||||
found = false
|
||||
for _, issue := range issues {
|
||||
if strings.Contains(issue, "no zone ID") {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Error("Zone sign without zone ID should have validation issue")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignCopy(t *testing.T) {
|
||||
original := createTestSign()
|
||||
original.SetSignTitle("Original Title")
|
||||
original.SetSignDescription("Original Description")
|
||||
|
||||
copy := original.Copy()
|
||||
|
||||
if copy == nil {
|
||||
t.Fatal("Copy returned nil")
|
||||
}
|
||||
|
||||
if copy == original {
|
||||
t.Error("Copy should not be the same instance")
|
||||
}
|
||||
|
||||
if copy.GetSignTitle() != original.GetSignTitle() {
|
||||
t.Error("Copy should have same title")
|
||||
}
|
||||
|
||||
if copy.GetSignDescription() != original.GetSignDescription() {
|
||||
t.Error("Copy should have same description")
|
||||
}
|
||||
|
||||
if copy.GetWidgetID() != original.GetWidgetID() {
|
||||
t.Error("Copy should have same widget ID")
|
||||
}
|
||||
|
||||
// Modify copy and ensure original is unchanged
|
||||
copy.SetSignTitle("Modified Title")
|
||||
if original.GetSignTitle() == "Modified Title" {
|
||||
t.Error("Modifying copy should not affect original")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignDisplayText(t *testing.T) {
|
||||
sign := NewSign()
|
||||
|
||||
// Empty sign
|
||||
text := sign.GetDisplayText()
|
||||
if len(text) > 0 {
|
||||
t.Error("Empty sign should have no display text")
|
||||
}
|
||||
|
||||
// Title only
|
||||
sign.SetSignTitle("Test Title")
|
||||
text = sign.GetDisplayText()
|
||||
if text != "Test Title" {
|
||||
t.Errorf("Expected 'Test Title', got '%s'", text)
|
||||
}
|
||||
|
||||
// Title and description
|
||||
sign.SetSignDescription("Test Description")
|
||||
text = sign.GetDisplayText()
|
||||
expected := "Test Title\nTest Description"
|
||||
if text != expected {
|
||||
t.Errorf("Expected '%s', got '%s'", expected, text)
|
||||
}
|
||||
|
||||
// With location
|
||||
sign.SetSignZoneX(100.0)
|
||||
sign.SetSignZoneY(200.0)
|
||||
sign.SetSignZoneZ(300.0)
|
||||
sign.SetIncludeLocation(true)
|
||||
text = sign.GetDisplayText()
|
||||
|
||||
if !strings.Contains(text, "Location: 100.00, 200.00, 300.00") {
|
||||
t.Error("Display text should contain location information")
|
||||
}
|
||||
|
||||
// With heading
|
||||
sign.SetSignZoneHeading(45.0)
|
||||
sign.SetIncludeHeading(true)
|
||||
text = sign.GetDisplayText()
|
||||
|
||||
if !strings.Contains(text, "Heading: 45.00") {
|
||||
t.Error("Display text should contain heading information")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignHandleUse(t *testing.T) {
|
||||
// Setup test environment
|
||||
database := NewMockDatabase()
|
||||
database.AddZone(2, "Test Zone")
|
||||
database.AddCharacter(1, "TestPlayer")
|
||||
|
||||
player := &MockPlayer{}
|
||||
client := &MockClient{
|
||||
player: player,
|
||||
charID: 1,
|
||||
database: database,
|
||||
zoneAccess: map[string]bool{"Test Zone": true},
|
||||
}
|
||||
|
||||
// Test zone transport sign
|
||||
sign := createZoneSign()
|
||||
sign.SetSignDistance(10.0) // Set distance limit
|
||||
|
||||
err := sign.HandleUse(client, "")
|
||||
if err != nil {
|
||||
t.Errorf("HandleUse failed: %v", err)
|
||||
}
|
||||
|
||||
// Check if player position was set
|
||||
if player.x != 100.0 || player.y != 200.0 || player.z != 300.0 {
|
||||
t.Error("Player position should be updated for zone transport")
|
||||
}
|
||||
|
||||
if player.heading != 45.0 {
|
||||
t.Error("Player heading should be updated for zone transport")
|
||||
}
|
||||
|
||||
// Test mark command
|
||||
sign = createTestSign()
|
||||
err = sign.HandleUse(client, "mark")
|
||||
if err != nil {
|
||||
t.Errorf("Mark command failed: %v", err)
|
||||
}
|
||||
|
||||
// Test distance check
|
||||
sign = createZoneSign()
|
||||
sign.SetSignDistance(1.0) // Very short distance
|
||||
client.ClearMessages()
|
||||
|
||||
err = sign.HandleUse(client, "")
|
||||
if err != nil {
|
||||
t.Errorf("HandleUse with distance check failed: %v", err)
|
||||
}
|
||||
|
||||
messages := client.GetMessages()
|
||||
found := false
|
||||
for _, msg := range messages {
|
||||
if strings.Contains(msg, "too far away") {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Error("Should get 'too far away' message when outside distance")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignSpawn(t *testing.T) {
|
||||
baseSpawn := spawn.NewSpawn()
|
||||
baseSpawn.SetName("Test Sign Spawn")
|
||||
|
||||
signSpawn := NewSignSpawn(baseSpawn)
|
||||
|
||||
if signSpawn == nil {
|
||||
t.Fatal("NewSignSpawn returned nil")
|
||||
}
|
||||
|
||||
if !signSpawn.IsSign() {
|
||||
t.Error("SignSpawn should be a sign")
|
||||
}
|
||||
|
||||
if signSpawn.Spawn != baseSpawn {
|
||||
t.Error("SignSpawn should reference the base spawn")
|
||||
}
|
||||
|
||||
// Test copy
|
||||
copy := signSpawn.Copy()
|
||||
if copy == nil {
|
||||
t.Fatal("SignSpawn Copy returned nil")
|
||||
}
|
||||
|
||||
if copy == signSpawn {
|
||||
t.Error("SignSpawn Copy should create new instance")
|
||||
}
|
||||
|
||||
if copy.Spawn.GetName() != baseSpawn.GetName() {
|
||||
t.Error("Copied SignSpawn should have same spawn name")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignAdapter(t *testing.T) {
|
||||
logger := &MockLogger{}
|
||||
entity := &MockEntity{id: 1, name: "Test Entity", databaseID: 100}
|
||||
|
||||
adapter := NewSignAdapter(entity, logger)
|
||||
|
||||
if adapter == nil {
|
||||
t.Fatal("NewSignAdapter returned nil")
|
||||
}
|
||||
|
||||
if !adapter.IsSign() {
|
||||
t.Error("SignAdapter should be a sign")
|
||||
}
|
||||
|
||||
if adapter.GetSign() == nil {
|
||||
t.Error("SignAdapter should have a sign")
|
||||
}
|
||||
|
||||
// Test setting properties with logging
|
||||
adapter.GetSign().SetWidgetID(500) // Set widget ID for validation
|
||||
adapter.SetSignTitle("Test Title")
|
||||
adapter.SetSignDescription("Test Description")
|
||||
adapter.SetSignType(SignTypeZone)
|
||||
adapter.SetZoneTransport(2, 100.0, 200.0, 300.0, 45.0)
|
||||
adapter.SetSignDistance(15.0)
|
||||
|
||||
logs := logger.GetLogs()
|
||||
if len(logs) == 0 {
|
||||
t.Error("SignAdapter operations should generate log messages")
|
||||
}
|
||||
|
||||
// Verify sign properties were set
|
||||
sign := adapter.GetSign()
|
||||
if sign.GetSignTitle() != "Test Title" {
|
||||
t.Error("Sign title not set correctly")
|
||||
}
|
||||
|
||||
if sign.GetSignType() != SignTypeZone {
|
||||
t.Error("Sign type not set correctly")
|
||||
}
|
||||
|
||||
if sign.GetSignZoneID() != 2 {
|
||||
t.Error("Zone ID not set correctly")
|
||||
}
|
||||
|
||||
// Test validation
|
||||
if !adapter.IsValid() {
|
||||
issues := adapter.Validate()
|
||||
t.Errorf("SignAdapter should be valid, issues: %v", issues)
|
||||
}
|
||||
}
|
||||
|
||||
func TestManager(t *testing.T) {
|
||||
database := NewMockDatabase()
|
||||
logger := &MockLogger{}
|
||||
|
||||
manager := NewManager(database, logger)
|
||||
|
||||
if manager == nil {
|
||||
t.Fatal("NewManager returned nil")
|
||||
}
|
||||
|
||||
// Test initialization
|
||||
err := manager.Initialize()
|
||||
if err != nil {
|
||||
t.Errorf("Manager initialization failed: %v", err)
|
||||
}
|
||||
|
||||
// Test adding signs
|
||||
sign1 := createTestSign()
|
||||
sign1.Spawn.SetDatabaseID(1)
|
||||
sign1.SetWidgetID(100)
|
||||
|
||||
err = manager.AddSign(sign1)
|
||||
if err != nil {
|
||||
t.Errorf("Adding sign failed: %v", err)
|
||||
}
|
||||
|
||||
if manager.GetSignCount() != 1 {
|
||||
t.Errorf("Expected 1 sign, got %d", manager.GetSignCount())
|
||||
}
|
||||
|
||||
// Test retrieving signs
|
||||
retrieved := manager.GetSign(1)
|
||||
if retrieved == nil {
|
||||
t.Error("Should be able to retrieve sign by ID")
|
||||
}
|
||||
|
||||
retrieved = manager.GetSignByWidget(100)
|
||||
if retrieved == nil {
|
||||
t.Error("Should be able to retrieve sign by widget ID")
|
||||
}
|
||||
|
||||
// Test zone functionality
|
||||
sign2 := createZoneSign()
|
||||
sign2.Spawn.SetDatabaseID(2)
|
||||
sign2.SetWidgetID(200)
|
||||
|
||||
err = manager.AddSignToZone(sign2, 1)
|
||||
if err != nil {
|
||||
t.Errorf("Adding sign to zone failed: %v", err)
|
||||
}
|
||||
|
||||
zoneSigns := manager.GetZoneSigns(1)
|
||||
if len(zoneSigns) != 1 {
|
||||
t.Errorf("Expected 1 zone sign, got %d", len(zoneSigns))
|
||||
}
|
||||
|
||||
// Test statistics
|
||||
stats := manager.GetStatistics()
|
||||
if stats["total_signs"] != int64(2) {
|
||||
t.Errorf("Expected 2 total signs in stats, got %v", stats["total_signs"])
|
||||
}
|
||||
|
||||
typeStats := stats["signs_by_type"].(map[int8]int64)
|
||||
if typeStats[SignTypeGeneric] != 1 {
|
||||
t.Errorf("Expected 1 generic sign in stats, got %d", typeStats[SignTypeGeneric])
|
||||
}
|
||||
if typeStats[SignTypeZone] != 1 {
|
||||
t.Errorf("Expected 1 zone sign in stats, got %d", typeStats[SignTypeZone])
|
||||
}
|
||||
|
||||
// Test removing signs
|
||||
if !manager.RemoveSign(1) {
|
||||
t.Error("Should be able to remove sign")
|
||||
}
|
||||
|
||||
if manager.GetSignCount() != 1 {
|
||||
t.Errorf("Expected 1 sign after removal, got %d", manager.GetSignCount())
|
||||
}
|
||||
|
||||
// Test validation
|
||||
issues := manager.ValidateAllSigns()
|
||||
if len(issues) > 0 {
|
||||
t.Errorf("All signs should be valid, found issues: %v", issues)
|
||||
}
|
||||
|
||||
// Test commands
|
||||
result, err := manager.ProcessCommand("stats", nil)
|
||||
if err != nil {
|
||||
t.Errorf("Stats command failed: %v", err)
|
||||
}
|
||||
if !strings.Contains(result, "Sign System Statistics") {
|
||||
t.Error("Stats command should return statistics")
|
||||
}
|
||||
|
||||
result, err = manager.ProcessCommand("list", nil)
|
||||
if err != nil {
|
||||
t.Errorf("List command failed: %v", err)
|
||||
}
|
||||
if !strings.Contains(result, "Signs (1)") {
|
||||
t.Error("List command should show sign count")
|
||||
}
|
||||
|
||||
result, err = manager.ProcessCommand("validate", nil)
|
||||
if err != nil {
|
||||
t.Errorf("Validate command failed: %v", err)
|
||||
}
|
||||
if !strings.Contains(result, "All signs are valid") {
|
||||
t.Error("Validate command should report all signs valid")
|
||||
}
|
||||
|
||||
result, err = manager.ProcessCommand("info", []string{"2"})
|
||||
if err != nil {
|
||||
t.Errorf("Info command failed: %v", err)
|
||||
}
|
||||
if !strings.Contains(result, "Sign Information") {
|
||||
t.Error("Info command should show sign information")
|
||||
}
|
||||
|
||||
// Test unknown command
|
||||
_, err = manager.ProcessCommand("unknown", nil)
|
||||
if err == nil {
|
||||
t.Error("Unknown command should return error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestManagerConcurrency(t *testing.T) {
|
||||
database := NewMockDatabase()
|
||||
logger := &MockLogger{}
|
||||
manager := NewManager(database, logger)
|
||||
|
||||
const numGoroutines = 10
|
||||
const numOperations = 100
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(numGoroutines)
|
||||
|
||||
// Test concurrent sign additions
|
||||
for i := 0; i < numGoroutines; i++ {
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
for j := 0; j < numOperations; j++ {
|
||||
sign := createTestSign()
|
||||
signID := int32(id*numOperations + j + 1)
|
||||
sign.Spawn.SetDatabaseID(signID)
|
||||
sign.SetWidgetID(signID)
|
||||
|
||||
err := manager.AddSign(sign)
|
||||
if err != nil {
|
||||
t.Errorf("Concurrent AddSign failed: %v", err)
|
||||
}
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
expectedCount := int64(numGoroutines * numOperations)
|
||||
if manager.GetSignCount() != expectedCount {
|
||||
t.Errorf("Expected %d signs after concurrent additions, got %d",
|
||||
expectedCount, manager.GetSignCount())
|
||||
}
|
||||
|
||||
// Test concurrent reads
|
||||
wg.Add(numGoroutines)
|
||||
for i := 0; i < numGoroutines; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < numOperations; j++ {
|
||||
stats := manager.GetStatistics()
|
||||
if stats["total_signs"].(int64) <= 0 {
|
||||
t.Error("Stats should show positive sign count")
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestSignSerialization(t *testing.T) {
|
||||
sign := createTestSign()
|
||||
player := &MockPlayer{}
|
||||
|
||||
// Test serialization (currently returns error due to incomplete implementation)
|
||||
_, err := sign.Serialize(player, 1146)
|
||||
if err == nil {
|
||||
t.Error("Serialization should return error with current implementation")
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), "not yet implemented") {
|
||||
t.Error("Should get 'not yet implemented' error message")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignConstants(t *testing.T) {
|
||||
// Test sign type constants
|
||||
if SignTypeGeneric != 0 {
|
||||
t.Errorf("SignTypeGeneric should be 0, got %d", SignTypeGeneric)
|
||||
}
|
||||
|
||||
if SignTypeZone != 1 {
|
||||
t.Errorf("SignTypeZone should be 1, got %d", SignTypeZone)
|
||||
}
|
||||
|
||||
// Test default constants
|
||||
if DefaultSpawnType != 2 {
|
||||
t.Errorf("DefaultSpawnType should be 2, got %d", DefaultSpawnType)
|
||||
}
|
||||
|
||||
if DefaultActivityStatus != 64 {
|
||||
t.Errorf("DefaultActivityStatus should be 64, got %d", DefaultActivityStatus)
|
||||
}
|
||||
|
||||
if DefaultSignDistance != 0.0 {
|
||||
t.Errorf("DefaultSignDistance should be 0.0, got %f", DefaultSignDistance)
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark tests
|
||||
|
||||
func BenchmarkNewSign(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = NewSign()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSignCopy(b *testing.B) {
|
||||
sign := createTestSign()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = sign.Copy()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSignValidation(b *testing.B) {
|
||||
sign := createTestSign()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = sign.Validate()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkManagerAddSign(b *testing.B) {
|
||||
database := NewMockDatabase()
|
||||
logger := &MockLogger{}
|
||||
manager := NewManager(database, logger)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
sign := createTestSign()
|
||||
sign.Spawn.SetDatabaseID(int32(i + 1))
|
||||
sign.SetWidgetID(int32(i + 100))
|
||||
_ = manager.AddSign(sign)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkManagerGetSign(b *testing.B) {
|
||||
database := NewMockDatabase()
|
||||
logger := &MockLogger{}
|
||||
manager := NewManager(database, logger)
|
||||
|
||||
// Pre-populate with signs
|
||||
for i := 0; i < 1000; i++ {
|
||||
sign := createTestSign()
|
||||
sign.Spawn.SetDatabaseID(int32(i + 1))
|
||||
sign.SetWidgetID(int32(i + 100))
|
||||
_ = manager.AddSign(sign)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = manager.GetSign(int32((i % 1000) + 1))
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user