implement tests and beches for ground_spawn package

This commit is contained in:
Sky Johnson 2025-08-01 22:16:43 -05:00
parent 0889eae1cf
commit 0534c49610
79 changed files with 2967 additions and 1195 deletions

View File

@ -368,17 +368,17 @@ fmt.Printf("Cache hits: %d, misses: %d\n",
// Custom packet handler
type MyAAPacketHandler struct{}
func (ph *MyAAPacketHandler) GetAAListPacket(client interface{}) ([]byte, error) {
func (ph *MyAAPacketHandler) GetAAListPacket(client any) ([]byte, error) {
// Build AA list packet for client
return []byte{}, nil
}
func (ph *MyAAPacketHandler) SendAAUpdate(client interface{}, playerState *AAPlayerState) error {
func (ph *MyAAPacketHandler) SendAAUpdate(client any, playerState *AAPlayerState) error {
// Send AA update to client
return nil
}
func (ph *MyAAPacketHandler) HandleAAPurchase(client interface{}, nodeID int32, rank int8) error {
func (ph *MyAAPacketHandler) HandleAAPurchase(client any, nodeID int32, rank int8) error {
// Handle AA purchase from client
return nil
}

View File

@ -1585,8 +1585,8 @@ func (m *mockAADatabase) LoadPlayerAADefaults(classID int8) (map[int8][]*AAEntry
return make(map[int8][]*AAEntry), nil
}
func (m *mockAADatabase) GetAAStatistics() (map[string]interface{}, error) {
return make(map[string]interface{}), nil
func (m *mockAADatabase) GetAAStatistics() (map[string]any, error) {
return make(map[string]any), nil
}
// Test mock event handler
@ -2293,11 +2293,21 @@ type mockAADatabaseWithErrors struct{}
func (m *mockAADatabaseWithErrors) LoadAltAdvancements() error { return fmt.Errorf("load AA error") }
func (m *mockAADatabaseWithErrors) LoadTreeNodes() error { return fmt.Errorf("load nodes error") }
func (m *mockAADatabaseWithErrors) LoadPlayerAA(characterID int32) (*AAPlayerState, error) { return nil, fmt.Errorf("load player error") }
func (m *mockAADatabaseWithErrors) SavePlayerAA(playerState *AAPlayerState) error { return fmt.Errorf("save player error") }
func (m *mockAADatabaseWithErrors) DeletePlayerAA(characterID int32) error { return fmt.Errorf("delete player error") }
func (m *mockAADatabaseWithErrors) LoadPlayerAADefaults(classID int8) (map[int8][]*AAEntry, error) { return nil, fmt.Errorf("load defaults error") }
func (m *mockAADatabaseWithErrors) GetAAStatistics() (map[string]interface{}, error) { return nil, fmt.Errorf("stats error") }
func (m *mockAADatabaseWithErrors) LoadPlayerAA(characterID int32) (*AAPlayerState, error) {
return nil, fmt.Errorf("load player error")
}
func (m *mockAADatabaseWithErrors) SavePlayerAA(playerState *AAPlayerState) error {
return fmt.Errorf("save player error")
}
func (m *mockAADatabaseWithErrors) DeletePlayerAA(characterID int32) error {
return fmt.Errorf("delete player error")
}
func (m *mockAADatabaseWithErrors) LoadPlayerAADefaults(classID int8) (map[int8][]*AAEntry, error) {
return nil, fmt.Errorf("load defaults error")
}
func (m *mockAADatabaseWithErrors) GetAAStatistics() (map[string]any, error) {
return nil, fmt.Errorf("stats error")
}
// Test more adapter methods
func TestAdapterMethods(t *testing.T) {
@ -2992,4 +3002,3 @@ func TestComplexScenarios(t *testing.T) {
}
})
}

View File

@ -514,8 +514,8 @@ func (db *DatabaseImpl) DeletePlayerAA(characterID int32) error {
}
// GetAAStatistics returns statistics about AA usage
func (db *DatabaseImpl) GetAAStatistics() (map[string]interface{}, error) {
stats := make(map[string]interface{})
func (db *DatabaseImpl) GetAAStatistics() (map[string]any, error) {
stats := make(map[string]any)
// Get total players with AA data
var totalPlayers int64

View File

@ -22,26 +22,26 @@ type AADatabase interface {
LoadPlayerAADefaults(classID int8) (map[int8][]*AAEntry, error)
// Statistics
GetAAStatistics() (map[string]interface{}, error)
GetAAStatistics() (map[string]any, error)
}
// AAPacketHandler interface for handling AA-related packets
type AAPacketHandler interface {
// List packets
GetAAListPacket(client interface{}) ([]byte, error)
SendAAUpdate(client interface{}, playerState *AAPlayerState) error
GetAAListPacket(client any) ([]byte, error)
SendAAUpdate(client any, playerState *AAPlayerState) error
// Purchase packets
HandleAAPurchase(client interface{}, nodeID int32, rank int8) error
SendAAPurchaseResponse(client interface{}, success bool, nodeID int32, newRank int8) error
HandleAAPurchase(client any, nodeID int32, rank int8) error
SendAAPurchaseResponse(client any, success bool, nodeID int32, newRank int8) error
// Template packets
SendAATemplateList(client interface{}, templates map[int8]*AATemplate) error
HandleAATemplateChange(client interface{}, templateID int8) error
SendAATemplateList(client any, templates map[int8]*AATemplate) error
HandleAATemplateChange(client any, templateID int8) error
// Display packets
DisplayAA(client interface{}, templateID int8, changeMode int8) error
SendAATabUpdate(client interface{}, tabID int8, tab *AATab) error
DisplayAA(client any, templateID int8, changeMode int8) error
SendAATabUpdate(client any, tabID int8, tab *AATab) error
}
// AAEventHandler interface for handling AA events
@ -124,8 +124,8 @@ type AAStatistics interface {
// Aggregated statistics
GetAAPurchaseStats() map[int32]int64
GetPopularAAs() map[int32]int64
GetPlayerProgressStats() map[string]interface{}
GetSystemPerformanceStats() map[string]interface{}
GetPlayerProgressStats() map[string]any
GetSystemPerformanceStats() map[string]any
}
// AACache interface for caching AA data
@ -147,7 +147,7 @@ type AACache interface {
// Cache management
Clear()
GetStats() map[string]interface{}
GetStats() map[string]any
SetMaxSize(maxSize int32)
}
@ -172,9 +172,9 @@ type Player interface {
// Transaction interface for database transactions
type Transaction interface {
Exec(query string, args ...interface{}) (sql.Result, error)
Query(query string, args ...interface{}) (*sql.Rows, error)
QueryRow(query string, args ...interface{}) *sql.Row
Exec(query string, args ...any) (sql.Result, error)
Query(query string, args ...any) (*sql.Rows, error)
QueryRow(query string, args ...any) *sql.Row
Commit() error
Rollback() error
}
@ -235,7 +235,7 @@ type AAManagerInterface interface {
// Statistics
GetSystemStats() *AAManagerStats
GetPlayerStats(characterID int32) map[string]interface{}
GetPlayerStats(characterID int32) map[string]any
// Configuration
SetConfig(config AAManagerConfig) error
@ -327,7 +327,7 @@ func (aa *AAAdapter) GetTemplates() (map[int8]*AATemplate, error) {
}
// GetPlayerStats returns AA statistics for the character
func (aa *AAAdapter) GetPlayerStats() map[string]interface{} {
func (aa *AAAdapter) GetPlayerStats() map[string]any {
return aa.manager.GetPlayerStats(aa.characterID)
}
@ -579,11 +579,11 @@ func (c *SimpleAACache) Clear() {
}
// GetStats returns cache statistics
func (c *SimpleAACache) GetStats() map[string]interface{} {
func (c *SimpleAACache) GetStats() map[string]any {
c.mutex.RLock()
defer c.mutex.RUnlock()
return map[string]interface{}{
return map[string]any{
"hits": c.hits,
"misses": c.misses,
"aa_data_count": len(c.aaData),

View File

@ -456,16 +456,16 @@ func (am *AAManager) GetSystemStats() *AAManagerStats {
}
// GetPlayerStats returns player-specific statistics
func (am *AAManager) GetPlayerStats(characterID int32) map[string]interface{} {
func (am *AAManager) GetPlayerStats(characterID int32) map[string]any {
playerState := am.getPlayerState(characterID)
if playerState == nil {
return map[string]interface{}{"error": "player not found"}
return map[string]any{"error": "player not found"}
}
playerState.mutex.RLock()
defer playerState.mutex.RUnlock()
return map[string]interface{}{
return map[string]any{
"character_id": characterID,
"total_points": playerState.TotalPoints,
"spent_points": playerState.SpentPoints,

View File

@ -251,11 +251,11 @@ func (mal *MasterAAList) ValidateAAData() []error {
}
// GetStats returns statistics about the master AA list
func (mal *MasterAAList) GetStats() map[string]interface{} {
func (mal *MasterAAList) GetStats() map[string]any {
mal.mutex.RLock()
defer mal.mutex.RUnlock()
stats := make(map[string]interface{})
stats := make(map[string]any)
stats[STAT_TOTAL_AAS_LOADED] = mal.totalLoaded
stats["last_load_time"] = mal.lastLoadTime
stats["groups_count"] = len(mal.aaByGroup)
@ -429,11 +429,11 @@ func (manl *MasterAANodeList) ValidateTreeNodes() []error {
}
// GetStats returns statistics about the master node list
func (manl *MasterAANodeList) GetStats() map[string]interface{} {
func (manl *MasterAANodeList) GetStats() map[string]any {
manl.mutex.RLock()
defer manl.mutex.RUnlock()
stats := make(map[string]interface{})
stats := make(map[string]any)
stats[STAT_TOTAL_NODES_LOADED] = manl.totalLoaded
stats["last_load_time"] = manl.lastLoadTime
stats["classes_count"] = len(manl.nodesByClass)

View File

@ -227,11 +227,11 @@ func (a *Appearances) IsValid() bool {
}
// GetStatistics returns statistics about the appearance collection
func (a *Appearances) GetStatistics() map[string]interface{} {
func (a *Appearances) GetStatistics() map[string]any {
a.mutex.RLock()
defer a.mutex.RUnlock()
stats := make(map[string]interface{})
stats := make(map[string]any)
stats["total_appearances"] = len(a.appearanceMap)
// Count by minimum client version

View File

@ -684,19 +684,19 @@ type MockLogger struct {
logs []string
}
func (m *MockLogger) LogInfo(message string, args ...interface{}) {
func (m *MockLogger) LogInfo(message string, args ...any) {
m.logs = append(m.logs, fmt.Sprintf("INFO: "+message, args...))
}
func (m *MockLogger) LogError(message string, args ...interface{}) {
func (m *MockLogger) LogError(message string, args ...any) {
m.logs = append(m.logs, fmt.Sprintf("ERROR: "+message, args...))
}
func (m *MockLogger) LogDebug(message string, args ...interface{}) {
func (m *MockLogger) LogDebug(message string, args ...any) {
m.logs = append(m.logs, fmt.Sprintf("DEBUG: "+message, args...))
}
func (m *MockLogger) LogWarning(message string, args ...interface{}) {
func (m *MockLogger) LogWarning(message string, args ...any) {
m.logs = append(m.logs, fmt.Sprintf("WARNING: "+message, args...))
}

View File

@ -15,10 +15,10 @@ type Database interface {
// Logger interface for appearance logging
type Logger interface {
LogInfo(message string, args ...interface{})
LogError(message string, args ...interface{})
LogDebug(message string, args ...interface{})
LogWarning(message string, args ...interface{})
LogInfo(message string, args ...any)
LogError(message string, args ...any)
LogDebug(message string, args ...any)
LogWarning(message string, args ...any)
}
// AppearanceProvider interface for entities that provide appearances

View File

@ -201,7 +201,7 @@ func (m *Manager) SearchAppearancesByName(nameSubstring string) []*Appearance {
}
// GetStatistics returns appearance system statistics
func (m *Manager) GetStatistics() map[string]interface{} {
func (m *Manager) GetStatistics() map[string]any {
m.mutex.RLock()
defer m.mutex.RUnlock()

View File

@ -329,11 +329,11 @@ func (c *Classes) GetClassCount() int {
}
// GetClassInfo returns comprehensive information about a class
func (c *Classes) GetClassInfo(classID int8) map[string]interface{} {
func (c *Classes) GetClassInfo(classID int8) map[string]any {
c.mutex.RLock()
defer c.mutex.RUnlock()
info := make(map[string]interface{})
info := make(map[string]any)
if !c.IsValidClassID(classID) {
info["valid"] = false

View File

@ -1185,7 +1185,7 @@ func TestGetClassSelectionData(t *testing.T) {
t.Error("Selection data should include statistics")
}
adventureClasses := selectionData["adventure_classes"].([]map[string]interface{})
adventureClasses := selectionData["adventure_classes"].([]map[string]any)
if len(adventureClasses) == 0 {
t.Error("Selection data should include adventure classes")
}
@ -1442,7 +1442,7 @@ func TestGetClassRecommendations(t *testing.T) {
manager := NewClassManager()
// Test recommendations by class type
preferences := map[string]interface{}{
preferences := map[string]any{
"class_type": ClassTypeAdventure,
}
@ -1459,7 +1459,7 @@ func TestGetClassRecommendations(t *testing.T) {
}
// Test recommendations by base class
basePreferences := map[string]interface{}{
basePreferences := map[string]any{
"base_class": ClassFighter,
}
@ -1469,7 +1469,7 @@ func TestGetClassRecommendations(t *testing.T) {
}
// Test recommendations by preferred stats
statPreferences := map[string]interface{}{
statPreferences := map[string]any{
"preferred_stats": []string{"strength", "stamina"},
}
@ -1479,7 +1479,7 @@ func TestGetClassRecommendations(t *testing.T) {
}
// Test empty preferences (should get defaults)
emptyPreferences := map[string]interface{}{}
emptyPreferences := map[string]any{}
defaultRecommendations := manager.GetClassRecommendations(emptyPreferences)
if len(defaultRecommendations) == 0 {
t.Error("Should get default recommendations when no preferences given")

View File

@ -33,7 +33,7 @@ func NewClassIntegration() *ClassIntegration {
}
// ValidateEntityClass validates an entity's class and provides detailed information
func (ci *ClassIntegration) ValidateEntityClass(entity ClassAware) (bool, string, map[string]interface{}) {
func (ci *ClassIntegration) ValidateEntityClass(entity ClassAware) (bool, string, map[string]any) {
classID := entity.GetClass()
if !ci.classes.IsValidClassID(classID) {
@ -45,8 +45,8 @@ func (ci *ClassIntegration) ValidateEntityClass(entity ClassAware) (bool, string
}
// GetEntityClassInfo returns comprehensive class information for an entity
func (ci *ClassIntegration) GetEntityClassInfo(entity EntityWithClass) map[string]interface{} {
info := make(map[string]interface{})
func (ci *ClassIntegration) GetEntityClassInfo(entity EntityWithClass) map[string]any {
info := make(map[string]any)
// Basic entity info
info["entity_id"] = entity.GetID()
@ -281,12 +281,12 @@ func (ci *ClassIntegration) GetClassStartingStats(classID int8) map[string]int16
}
// CreateClassSpecificEntity creates entity data with class-specific properties
func (ci *ClassIntegration) CreateClassSpecificEntity(classID int8) map[string]interface{} {
func (ci *ClassIntegration) CreateClassSpecificEntity(classID int8) map[string]any {
if !ci.classes.IsValidClassID(classID) {
return nil
}
entityData := make(map[string]interface{})
entityData := make(map[string]any)
// Basic class info
entityData["class_id"] = classID
@ -310,16 +310,16 @@ func (ci *ClassIntegration) CreateClassSpecificEntity(classID int8) map[string]i
}
// GetClassSelectionData returns data for class selection UI
func (ci *ClassIntegration) GetClassSelectionData() map[string]interface{} {
data := make(map[string]interface{})
func (ci *ClassIntegration) GetClassSelectionData() map[string]any {
data := make(map[string]any)
// All available adventure classes (exclude tradeskill for character creation)
allClasses := ci.classes.GetAllClasses()
adventureClasses := make([]map[string]interface{}, 0)
adventureClasses := make([]map[string]any, 0)
for classID, displayName := range allClasses {
if ci.classes.IsAdventureClass(classID) {
classData := map[string]interface{}{
classData := map[string]any{
"id": classID,
"name": displayName,
"type": ci.classes.GetClassType(classID),

View File

@ -330,8 +330,8 @@ func (cm *ClassManager) handleProgressionCommand(args []string) (string, error)
}
// ValidateEntityClasses validates classes for a collection of entities
func (cm *ClassManager) ValidateEntityClasses(entities []ClassAware) map[string]interface{} {
validationResults := make(map[string]interface{})
func (cm *ClassManager) ValidateEntityClasses(entities []ClassAware) map[string]any {
validationResults := make(map[string]any)
validCount := 0
invalidCount := 0
@ -351,11 +351,11 @@ func (cm *ClassManager) ValidateEntityClasses(entities []ClassAware) map[string]
// Track invalid entities
if !isValid {
if validationResults["invalid_entities"] == nil {
validationResults["invalid_entities"] = make([]map[string]interface{}, 0)
validationResults["invalid_entities"] = make([]map[string]any, 0)
}
invalidList := validationResults["invalid_entities"].([]map[string]interface{})
invalidList = append(invalidList, map[string]interface{}{
invalidList := validationResults["invalid_entities"].([]map[string]any)
invalidList = append(invalidList, map[string]any{
"index": i,
"class_id": classID,
})
@ -372,7 +372,7 @@ func (cm *ClassManager) ValidateEntityClasses(entities []ClassAware) map[string]
}
// GetClassRecommendations returns class recommendations for character creation
func (cm *ClassManager) GetClassRecommendations(preferences map[string]interface{}) []int8 {
func (cm *ClassManager) GetClassRecommendations(preferences map[string]any) []int8 {
recommendations := make([]int8, 0)
// Check for class type preference

View File

@ -366,8 +366,8 @@ func (cu *ClassUtils) GetClassAliases(classID int8) []string {
}
// GetClassStatistics returns statistics about the class system
func (cu *ClassUtils) GetClassStatistics() map[string]interface{} {
stats := make(map[string]interface{})
func (cu *ClassUtils) GetClassStatistics() map[string]any {
stats := make(map[string]any)
allClasses := cu.classes.GetAllClasses()
stats["total_classes"] = len(allClasses)

View File

@ -163,14 +163,14 @@ type CollectionEventHandler interface {
// LogHandler provides logging functionality
type LogHandler interface {
// LogDebug logs debug messages
LogDebug(category, message string, args ...interface{})
LogDebug(category, message string, args ...any)
// LogInfo logs informational messages
LogInfo(category, message string, args ...interface{})
LogInfo(category, message string, args ...any)
// LogError logs error messages
LogError(category, message string, args ...interface{})
LogError(category, message string, args ...any)
// LogWarning logs warning messages
LogWarning(category, message string, args ...interface{})
LogWarning(category, message string, args ...any)
}

View File

@ -151,7 +151,7 @@ func (e *Entity) SetInfoStruct(info *InfoStruct) {
}
// GetClient returns the client for this entity (overridden by Player)
func (e *Entity) GetClient() interface{} {
func (e *Entity) GetClient() any {
return nil
}

View File

@ -18,10 +18,10 @@ type Database interface {
// Logger interface for faction logging
type Logger interface {
LogInfo(message string, args ...interface{})
LogError(message string, args ...interface{})
LogDebug(message string, args ...interface{})
LogWarning(message string, args ...interface{})
LogInfo(message string, args ...any)
LogError(message string, args ...any)
LogDebug(message string, args ...any)
LogWarning(message string, args ...any)
}
// FactionRelation represents a relationship between two factions

View File

@ -237,11 +237,11 @@ func (m *Manager) RecordFactionDecrease(factionID int32) {
}
// GetStatistics returns faction system statistics
func (m *Manager) GetStatistics() map[string]interface{} {
func (m *Manager) GetStatistics() map[string]any {
m.mutex.RLock()
defer m.mutex.RUnlock()
stats := make(map[string]interface{})
stats := make(map[string]any)
stats["total_factions"] = m.masterFactionList.GetFactionCount()
stats["total_faction_changes"] = m.totalFactionChanges
stats["faction_increases"] = m.factionIncreases

View File

@ -0,0 +1,562 @@
package ground_spawn
import (
"testing"
)
// Mock implementations are in test_utils.go
// Benchmark GroundSpawn operations
func BenchmarkGroundSpawn(b *testing.B) {
config := GroundSpawnConfig{
GroundSpawnID: 1,
CollectionSkill: SkillGathering,
NumberHarvests: 10,
AttemptsPerHarvest: 2,
RandomizeHeading: true,
Location: SpawnLocation{
X: 100.0, Y: 200.0, Z: 300.0, Heading: 45.0, GridID: 1,
},
Name: "Benchmark Node",
Description: "A benchmark harvestable node",
}
gs := NewGroundSpawn(config)
b.Run("GetNumberHarvests", func(b *testing.B) {
for b.Loop() {
_ = gs.GetNumberHarvests()
}
})
b.Run("SetNumberHarvests", func(b *testing.B) {
b.ResetTimer()
for i := 0; b.Loop(); i++ {
gs.SetNumberHarvests(int8(i % 10))
}
})
b.Run("GetCollectionSkill", func(b *testing.B) {
b.ResetTimer()
for b.Loop() {
_ = gs.GetCollectionSkill()
}
})
b.Run("SetCollectionSkill", func(b *testing.B) {
skills := []string{SkillGathering, SkillMining, SkillFishing, SkillTrapping}
b.ResetTimer()
for i := 0; b.Loop(); i++ {
gs.SetCollectionSkill(skills[i%len(skills)])
}
})
b.Run("IsAvailable", func(b *testing.B) {
b.ResetTimer()
for b.Loop() {
_ = gs.IsAvailable()
}
})
b.Run("IsDepleted", func(b *testing.B) {
b.ResetTimer()
for b.Loop() {
_ = gs.IsDepleted()
}
})
b.Run("GetHarvestMessageName", func(b *testing.B) {
b.ResetTimer()
for i := 0; b.Loop(); i++ {
_ = gs.GetHarvestMessageName(i%2 == 0, i%4 == 0)
}
})
b.Run("GetHarvestSpellType", func(b *testing.B) {
b.ResetTimer()
for b.Loop() {
_ = gs.GetHarvestSpellType()
}
})
b.Run("Copy", func(b *testing.B) {
b.ResetTimer()
for b.Loop() {
copy := gs.Copy()
_ = copy
}
})
b.Run("Respawn", func(b *testing.B) {
b.ResetTimer()
for b.Loop() {
gs.Respawn()
}
})
}
// Benchmark Manager operations
func BenchmarkManager(b *testing.B) {
manager := NewManager(nil, &mockLogger{})
// Pre-populate with ground spawns
for i := int32(1); i <= 100; i++ {
config := GroundSpawnConfig{
GroundSpawnID: i,
CollectionSkill: SkillGathering,
NumberHarvests: 5,
AttemptsPerHarvest: 1,
Location: SpawnLocation{
X: float32(i * 10), Y: float32(i * 20), Z: float32(i * 30),
Heading: float32(i * 45), GridID: 1,
},
Name: "Benchmark Node",
Description: "Benchmark node",
}
gs := manager.CreateGroundSpawn(config)
_ = gs
}
b.Run("GetGroundSpawn", func(b *testing.B) {
b.ResetTimer()
for i := 0; b.Loop(); i++ {
spawnID := int32((i % 100) + 1)
_ = manager.GetGroundSpawn(spawnID)
}
})
b.Run("CreateGroundSpawn", func(b *testing.B) {
b.ResetTimer()
for i := 0; b.Loop(); i++ {
config := GroundSpawnConfig{
GroundSpawnID: int32(2000 + i),
CollectionSkill: SkillMining,
NumberHarvests: 3,
AttemptsPerHarvest: 1,
Location: SpawnLocation{
X: float32(i), Y: float32(i * 2), Z: float32(i * 3),
Heading: 0, GridID: 1,
},
Name: "New Node",
Description: "New benchmark node",
}
_ = manager.CreateGroundSpawn(config)
}
})
b.Run("GetGroundSpawnsByZone", func(b *testing.B) {
b.ResetTimer()
for b.Loop() {
_ = manager.GetGroundSpawnsByZone(1)
}
})
b.Run("GetGroundSpawnCount", func(b *testing.B) {
b.ResetTimer()
for b.Loop() {
_ = manager.GetGroundSpawnCount()
}
})
b.Run("GetActiveGroundSpawns", func(b *testing.B) {
b.ResetTimer()
for b.Loop() {
_ = manager.GetActiveGroundSpawns()
}
})
b.Run("GetDepletedGroundSpawns", func(b *testing.B) {
b.ResetTimer()
for b.Loop() {
_ = manager.GetDepletedGroundSpawns()
}
})
b.Run("GetStatistics", func(b *testing.B) {
b.ResetTimer()
for b.Loop() {
_ = manager.GetStatistics()
}
})
b.Run("ResetStatistics", func(b *testing.B) {
b.ResetTimer()
for b.Loop() {
manager.ResetStatistics()
}
})
b.Run("ProcessRespawns", func(b *testing.B) {
b.ResetTimer()
for b.Loop() {
manager.ProcessRespawns()
}
})
}
// Benchmark concurrent operations
func BenchmarkConcurrentOperations(b *testing.B) {
b.Run("GroundSpawnConcurrentReads", func(b *testing.B) {
config := GroundSpawnConfig{
GroundSpawnID: 1,
CollectionSkill: SkillGathering,
NumberHarvests: 10,
AttemptsPerHarvest: 1,
Location: SpawnLocation{
X: 100, Y: 200, Z: 300, Heading: 45, GridID: 1,
},
Name: "Concurrent Node",
Description: "Concurrent benchmark node",
}
gs := NewGroundSpawn(config)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
i := 0
for pb.Next() {
switch i % 4 {
case 0:
_ = gs.GetNumberHarvests()
case 1:
_ = gs.GetCollectionSkill()
case 2:
_ = gs.IsAvailable()
case 3:
_ = gs.GetHarvestSpellType()
}
i++
}
})
})
b.Run("GroundSpawnConcurrentWrites", func(b *testing.B) {
config := GroundSpawnConfig{
GroundSpawnID: 1,
CollectionSkill: SkillGathering,
NumberHarvests: 10,
AttemptsPerHarvest: 1,
Location: SpawnLocation{
X: 100, Y: 200, Z: 300, Heading: 45, GridID: 1,
},
Name: "Concurrent Node",
Description: "Concurrent benchmark node",
}
gs := NewGroundSpawn(config)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
i := 0
for pb.Next() {
switch i % 3 {
case 0:
gs.SetNumberHarvests(int8(i % 10))
case 1:
gs.SetCollectionSkill(SkillMining)
case 2:
gs.SetRandomizeHeading(i%2 == 0)
}
i++
}
})
})
b.Run("ManagerConcurrentOperations", func(b *testing.B) {
manager := NewManager(nil, &mockLogger{})
// Pre-populate
for i := int32(1); i <= 10; i++ {
config := GroundSpawnConfig{
GroundSpawnID: i,
CollectionSkill: SkillGathering,
NumberHarvests: 5,
AttemptsPerHarvest: 1,
Location: SpawnLocation{
X: float32(i * 10), Y: float32(i * 20), Z: float32(i * 30),
Heading: 0, GridID: 1,
},
Name: "Manager Node",
Description: "Manager benchmark node",
}
manager.CreateGroundSpawn(config)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
i := 0
for pb.Next() {
spawnID := int32((i % 10) + 1)
switch i % 5 {
case 0:
_ = manager.GetGroundSpawn(spawnID)
case 1:
_ = manager.GetGroundSpawnsByZone(1)
case 2:
_ = manager.GetStatistics()
case 3:
_ = manager.GetActiveGroundSpawns()
case 4:
manager.ProcessRespawns()
}
i++
}
})
})
}
// Memory allocation benchmarks
func BenchmarkMemoryAllocations(b *testing.B) {
b.Run("GroundSpawnCreation", func(b *testing.B) {
config := GroundSpawnConfig{
GroundSpawnID: 1,
CollectionSkill: SkillGathering,
NumberHarvests: 5,
AttemptsPerHarvest: 1,
Location: SpawnLocation{
X: 100, Y: 200, Z: 300, Heading: 45, GridID: 1,
},
Name: "Memory Test Node",
Description: "Memory test node",
}
b.ReportAllocs()
b.ResetTimer()
for b.Loop() {
_ = NewGroundSpawn(config)
}
})
b.Run("ManagerCreation", func(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for b.Loop() {
_ = NewManager(nil, &mockLogger{})
}
})
b.Run("GroundSpawnCopy", func(b *testing.B) {
config := GroundSpawnConfig{
GroundSpawnID: 1,
CollectionSkill: SkillGathering,
NumberHarvests: 5,
AttemptsPerHarvest: 1,
Location: SpawnLocation{
X: 100, Y: 200, Z: 300, Heading: 45, GridID: 1,
},
Name: "Copy Test Node",
Description: "Copy test node",
}
gs := NewGroundSpawn(config)
b.ReportAllocs()
b.ResetTimer()
for b.Loop() {
_ = gs.Copy()
}
})
b.Run("StatisticsGeneration", func(b *testing.B) {
manager := NewManager(nil, &mockLogger{})
// Add some data for meaningful statistics
for i := int32(1); i <= 10; i++ {
config := GroundSpawnConfig{
GroundSpawnID: i,
CollectionSkill: SkillGathering,
NumberHarvests: 5,
AttemptsPerHarvest: 1,
Location: SpawnLocation{
X: float32(i * 10), Y: float32(i * 20), Z: float32(i * 30),
Heading: 0, GridID: 1,
},
Name: "Stats Node",
Description: "Stats test node",
}
manager.CreateGroundSpawn(config)
}
// Add some harvest statistics
manager.mutex.Lock()
manager.totalHarvests = 1000
manager.successfulHarvests = 850
manager.rareItemsHarvested = 50
manager.skillUpsGenerated = 200
manager.harvestsBySkill[SkillGathering] = 600
manager.harvestsBySkill[SkillMining] = 400
manager.mutex.Unlock()
b.ReportAllocs()
b.ResetTimer()
for b.Loop() {
_ = manager.GetStatistics()
}
})
}
// Contention benchmarks
func BenchmarkContention(b *testing.B) {
b.Run("HighContentionReads", func(b *testing.B) {
config := GroundSpawnConfig{
GroundSpawnID: 1,
CollectionSkill: SkillGathering,
NumberHarvests: 10,
AttemptsPerHarvest: 1,
Location: SpawnLocation{
X: 100, Y: 200, Z: 300, Heading: 45, GridID: 1,
},
Name: "Contention Node",
Description: "Contention test node",
}
gs := NewGroundSpawn(config)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = gs.GetNumberHarvests()
}
})
})
b.Run("HighContentionWrites", func(b *testing.B) {
config := GroundSpawnConfig{
GroundSpawnID: 1,
CollectionSkill: SkillGathering,
NumberHarvests: 10,
AttemptsPerHarvest: 1,
Location: SpawnLocation{
X: 100, Y: 200, Z: 300, Heading: 45, GridID: 1,
},
Name: "Contention Node",
Description: "Contention test node",
}
gs := NewGroundSpawn(config)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
i := 0
for pb.Next() {
gs.SetNumberHarvests(int8(i % 10))
i++
}
})
})
b.Run("MixedReadWrite", func(b *testing.B) {
config := GroundSpawnConfig{
GroundSpawnID: 1,
CollectionSkill: SkillGathering,
NumberHarvests: 10,
AttemptsPerHarvest: 1,
Location: SpawnLocation{
X: 100, Y: 200, Z: 300, Heading: 45, GridID: 1,
},
Name: "Mixed Node",
Description: "Mixed operations test node",
}
gs := NewGroundSpawn(config)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
i := 0
for pb.Next() {
if i%10 == 0 {
// 10% writes
gs.SetNumberHarvests(int8(i % 5))
} else {
// 90% reads
_ = gs.GetNumberHarvests()
}
i++
}
})
})
b.Run("ManagerHighContention", func(b *testing.B) {
manager := NewManager(nil, &mockLogger{})
// Pre-populate
for i := int32(1); i <= 5; i++ {
config := GroundSpawnConfig{
GroundSpawnID: i,
CollectionSkill: SkillGathering,
NumberHarvests: 5,
AttemptsPerHarvest: 1,
Location: SpawnLocation{
X: float32(i * 10), Y: float32(i * 20), Z: float32(i * 30),
Heading: 0, GridID: 1,
},
Name: "Contention Node",
Description: "Manager contention test",
}
manager.CreateGroundSpawn(config)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = manager.GetGroundSpawn(1)
}
})
})
}
// Scalability benchmarks
func BenchmarkScalability(b *testing.B) {
sizes := []int{10, 100, 1000}
for _, size := range sizes {
b.Run("GroundSpawnLookup_"+string(rune(size)), func(b *testing.B) {
manager := NewManager(nil, &mockLogger{})
// Pre-populate with varying sizes
for i := int32(1); i <= int32(size); i++ {
config := GroundSpawnConfig{
GroundSpawnID: i,
CollectionSkill: SkillGathering,
NumberHarvests: 5,
AttemptsPerHarvest: 1,
Location: SpawnLocation{
X: float32(i * 10), Y: float32(i * 20), Z: float32(i * 30),
Heading: 0, GridID: 1,
},
Name: "Scale Node",
Description: "Scalability test node",
}
manager.CreateGroundSpawn(config)
}
b.ResetTimer()
for i := 0; b.Loop(); i++ {
spawnID := int32((i % size) + 1)
_ = manager.GetGroundSpawn(spawnID)
}
})
}
for _, size := range sizes {
b.Run("ZoneLookup_"+string(rune(size)), func(b *testing.B) {
manager := NewManager(nil, &mockLogger{})
// Pre-populate with varying sizes
for i := int32(1); i <= int32(size); i++ {
config := GroundSpawnConfig{
GroundSpawnID: i,
CollectionSkill: SkillGathering,
NumberHarvests: 5,
AttemptsPerHarvest: 1,
Location: SpawnLocation{
X: float32(i * 10), Y: float32(i * 20), Z: float32(i * 30),
Heading: 0, GridID: 1,
},
Name: "Zone Node",
Description: "Zone scalability test",
}
manager.CreateGroundSpawn(config)
}
b.ResetTimer()
for b.Loop() {
_ = manager.GetGroundSpawnsByZone(1)
}
})
}
}

View File

@ -0,0 +1,523 @@
package ground_spawn
import (
"sync"
"testing"
"time"
)
// Mock implementations are in test_utils.go
// Stress test GroundSpawn with concurrent operations
func TestGroundSpawnConcurrency(t *testing.T) {
config := GroundSpawnConfig{
GroundSpawnID: 1,
CollectionSkill: SkillGathering,
NumberHarvests: 10,
AttemptsPerHarvest: 2,
RandomizeHeading: true,
Location: SpawnLocation{
X: 100.0, Y: 200.0, Z: 300.0, Heading: 45.0, GridID: 1,
},
Name: "Test Node",
Description: "A test harvestable node",
}
gs := NewGroundSpawn(config)
const numGoroutines = 100
const operationsPerGoroutine = 100
var wg sync.WaitGroup
t.Run("ConcurrentGetterSetterOperations", func(t *testing.T) {
wg.Add(numGoroutines)
for i := range numGoroutines {
go func(goroutineID int) {
defer wg.Done()
for j := range operationsPerGoroutine {
switch j % 8 {
case 0:
gs.SetNumberHarvests(int8(goroutineID % 10))
case 1:
_ = gs.GetNumberHarvests()
case 2:
gs.SetAttemptsPerHarvest(int8(goroutineID % 5))
case 3:
_ = gs.GetAttemptsPerHarvest()
case 4:
gs.SetCollectionSkill(SkillMining)
case 5:
_ = gs.GetCollectionSkill()
case 6:
gs.SetRandomizeHeading(goroutineID%2 == 0)
case 7:
_ = gs.GetRandomizeHeading()
}
}
}(i)
}
wg.Wait()
})
t.Run("ConcurrentStateChecks", func(t *testing.T) {
wg.Add(numGoroutines)
for i := range numGoroutines {
go func(goroutineID int) {
defer wg.Done()
for j := range operationsPerGoroutine {
switch j % 4 {
case 0:
_ = gs.IsDepleted()
case 1:
_ = gs.IsAvailable()
case 2:
_ = gs.GetHarvestMessageName(true, false)
case 3:
_ = gs.GetHarvestSpellType()
}
}
}(i)
}
wg.Wait()
})
t.Run("ConcurrentCopyOperations", func(t *testing.T) {
wg.Add(numGoroutines)
for i := range numGoroutines {
go func(goroutineID int) {
defer wg.Done()
for j := range operationsPerGoroutine {
// Test concurrent copying while modifying
if j%2 == 0 {
copy := gs.Copy()
if copy == nil {
t.Errorf("Goroutine %d: Copy returned nil", goroutineID)
}
} else {
gs.SetNumberHarvests(int8(goroutineID % 5))
}
}
}(i)
}
wg.Wait()
})
t.Run("ConcurrentRespawnOperations", func(t *testing.T) {
wg.Add(numGoroutines)
for i := range numGoroutines {
go func(goroutineID int) {
defer wg.Done()
for j := range operationsPerGoroutine {
if j%10 == 0 {
gs.Respawn()
} else {
// Mix of reads and writes during respawn
switch j % 4 {
case 0:
_ = gs.GetNumberHarvests()
case 1:
gs.SetNumberHarvests(int8(goroutineID % 3))
case 2:
_ = gs.IsAvailable()
case 3:
_ = gs.IsDepleted()
}
}
}
}(i)
}
wg.Wait()
})
}
// Stress test Manager with concurrent operations
func TestManagerConcurrency(t *testing.T) {
manager := NewManager(nil, &mockLogger{})
// Pre-populate with some ground spawns
for i := int32(1); i <= 10; i++ {
config := GroundSpawnConfig{
GroundSpawnID: i,
CollectionSkill: SkillGathering,
NumberHarvests: 5,
AttemptsPerHarvest: 1,
Location: SpawnLocation{
X: float32(i * 10), Y: float32(i * 20), Z: float32(i * 30),
Heading: float32(i * 45), GridID: 1,
},
Name: "Test Node",
Description: "Test node",
}
gs := manager.CreateGroundSpawn(config)
if gs == nil {
t.Fatalf("Failed to create ground spawn %d", i)
}
}
const numGoroutines = 100
const operationsPerGoroutine = 100
var wg sync.WaitGroup
t.Run("ConcurrentGroundSpawnAccess", func(t *testing.T) {
wg.Add(numGoroutines)
for i := range numGoroutines {
go func(goroutineID int) {
defer wg.Done()
for j := range operationsPerGoroutine {
spawnID := int32((goroutineID % 10) + 1)
switch j % 5 {
case 0:
_ = manager.GetGroundSpawn(spawnID)
case 1:
_ = manager.GetGroundSpawnsByZone(1)
case 2:
_ = manager.GetGroundSpawnCount()
case 3:
_ = manager.GetActiveGroundSpawns()
case 4:
_ = manager.GetDepletedGroundSpawns()
}
}
}(i)
}
wg.Wait()
})
t.Run("ConcurrentStatisticsOperations", func(t *testing.T) {
wg.Add(numGoroutines)
for i := range numGoroutines {
go func(goroutineID int) {
defer wg.Done()
for j := range operationsPerGoroutine {
switch j % 3 {
case 0:
_ = manager.GetStatistics()
case 1:
manager.ResetStatistics()
case 2:
// Simulate harvest statistics updates
manager.mutex.Lock()
manager.totalHarvests++
manager.successfulHarvests++
skill := SkillGathering
manager.harvestsBySkill[skill]++
manager.mutex.Unlock()
}
}
}(i)
}
wg.Wait()
// Verify statistics consistency
stats := manager.GetStatistics()
if stats.TotalHarvests < 0 || stats.SuccessfulHarvests < 0 {
t.Errorf("Invalid statistics: total=%d, successful=%d",
stats.TotalHarvests, stats.SuccessfulHarvests)
}
})
t.Run("ConcurrentGroundSpawnModification", func(t *testing.T) {
wg.Add(numGoroutines)
for i := range numGoroutines {
go func(goroutineID int) {
defer wg.Done()
for j := range operationsPerGoroutine {
// Use a more unique ID generation strategy to avoid conflicts
// Start at 10000 and use goroutine*1000 + iteration to ensure uniqueness
newID := int32(10000 + goroutineID*1000 + j)
config := GroundSpawnConfig{
GroundSpawnID: newID,
CollectionSkill: SkillMining,
NumberHarvests: 3,
AttemptsPerHarvest: 1,
Location: SpawnLocation{
X: float32(j), Y: float32(j * 2), Z: float32(j * 3),
Heading: float32(j * 10), GridID: 1,
},
Name: "Concurrent Node",
Description: "Concurrent test node",
}
// Add ground spawn - note that CreateGroundSpawn overwrites the ID
gs := manager.CreateGroundSpawn(config)
if gs == nil {
t.Errorf("Goroutine %d: Failed to create ground spawn", goroutineID)
continue
}
// Since CreateGroundSpawn assigns its own ID, we need to get the actual ID
actualID := gs.GetID()
// Verify it was added with the manager-assigned ID
retrieved := manager.GetGroundSpawn(actualID)
if retrieved == nil {
t.Errorf("Goroutine %d: Failed to retrieve ground spawn %d", goroutineID, actualID)
}
}
}(i)
}
wg.Wait()
})
t.Run("ConcurrentRespawnProcessing", func(t *testing.T) {
wg.Add(numGoroutines)
for i := range numGoroutines {
go func(goroutineID int) {
defer wg.Done()
for j := range operationsPerGoroutine {
if j%50 == 0 {
// Process respawns occasionally
manager.ProcessRespawns()
} else {
// Schedule respawns
spawnID := int32((goroutineID % 10) + 1)
if gs := manager.GetGroundSpawn(spawnID); gs != nil {
if gs.IsDepleted() {
manager.scheduleRespawn(gs)
}
}
}
}
}(i)
}
wg.Wait()
})
}
// Test for potential deadlocks
func TestDeadlockPrevention(t *testing.T) {
manager := NewManager(nil, &mockLogger{})
// Create test ground spawns
for i := int32(1); i <= 10; i++ {
config := GroundSpawnConfig{
GroundSpawnID: i,
CollectionSkill: SkillGathering,
NumberHarvests: 5,
AttemptsPerHarvest: 1,
Location: SpawnLocation{
X: float32(i * 10), Y: float32(i * 20), Z: float32(i * 30),
Heading: 0, GridID: 1,
},
Name: "Deadlock Test Node",
Description: "Test node",
}
gs := manager.CreateGroundSpawn(config)
if gs == nil {
t.Fatalf("Failed to create ground spawn %d", i)
}
}
const numGoroutines = 50
var wg sync.WaitGroup
// Test potential deadlock scenarios
t.Run("MixedOperations", func(t *testing.T) {
done := make(chan bool, 1)
// Set a timeout to detect deadlocks
go func() {
time.Sleep(10 * time.Second)
select {
case <-done:
return
default:
t.Error("Potential deadlock detected - test timed out")
}
}()
wg.Add(numGoroutines)
for i := range numGoroutines {
go func(goroutineID int) {
defer wg.Done()
for j := range 100 {
spawnID := int32((goroutineID % 10) + 1)
// Mix operations that could potentially deadlock
switch j % 8 {
case 0:
gs := manager.GetGroundSpawn(spawnID)
if gs != nil {
_ = gs.GetNumberHarvests()
}
case 1:
_ = manager.GetStatistics()
case 2:
_ = manager.GetGroundSpawnsByZone(1)
case 3:
gs := manager.GetGroundSpawn(spawnID)
if gs != nil {
gs.SetNumberHarvests(int8(j % 5))
}
case 4:
manager.ProcessRespawns()
case 5:
_ = manager.GetActiveGroundSpawns()
case 6:
gs := manager.GetGroundSpawn(spawnID)
if gs != nil {
_ = gs.Copy()
}
case 7:
gs := manager.GetGroundSpawn(spawnID)
if gs != nil && gs.IsDepleted() {
manager.scheduleRespawn(gs)
}
}
}
}(i)
}
wg.Wait()
done <- true
})
}
// Race condition detection test - run with -race flag
func TestRaceConditions(t *testing.T) {
if testing.Short() {
t.Skip("Skipping race condition test in short mode")
}
manager := NewManager(nil, &mockLogger{})
// Rapid concurrent operations to trigger race conditions
const numGoroutines = 200
const operationsPerGoroutine = 50
var wg sync.WaitGroup
wg.Add(numGoroutines)
for i := range numGoroutines {
go func(goroutineID int) {
defer wg.Done()
for j := range operationsPerGoroutine {
// Use unique IDs to avoid conflicts in rapid creation
uniqueID := int32(20000 + goroutineID*1000 + j)
// Rapid-fire operations
config := GroundSpawnConfig{
GroundSpawnID: uniqueID,
CollectionSkill: SkillGathering,
NumberHarvests: 3,
AttemptsPerHarvest: 1,
Location: SpawnLocation{
X: float32(j), Y: float32(j * 2), Z: float32(j * 3),
Heading: 0, GridID: 1,
},
Name: "Race Test Node",
Description: "Race test",
}
gs := manager.CreateGroundSpawn(config)
if gs != nil {
actualID := gs.GetID() // Get the manager-assigned ID
gs.SetNumberHarvests(int8(j%5 + 1))
_ = gs.GetNumberHarvests()
_ = gs.IsAvailable()
copy := gs.Copy()
if copy != nil {
copy.SetCollectionSkill(SkillMining)
}
_ = manager.GetGroundSpawn(actualID)
}
_ = manager.GetStatistics()
manager.ProcessRespawns()
}
}(i)
}
wg.Wait()
}
// Specific test for Copy() method mutex safety
func TestCopyMutexSafety(t *testing.T) {
config := GroundSpawnConfig{
GroundSpawnID: 1,
CollectionSkill: SkillGathering,
NumberHarvests: 5,
AttemptsPerHarvest: 1,
Location: SpawnLocation{
X: 100, Y: 200, Z: 300, Heading: 45, GridID: 1,
},
Name: "Copy Test Node",
Description: "Test node for copy safety",
}
original := NewGroundSpawn(config)
const numGoroutines = 100
var wg sync.WaitGroup
wg.Add(numGoroutines)
// Test copying while modifying
for i := range numGoroutines {
go func(goroutineID int) {
defer wg.Done()
for j := range 100 {
if j%2 == 0 {
// Copy operations
copy := original.Copy()
if copy == nil {
t.Errorf("Goroutine %d: Copy returned nil", goroutineID)
continue
}
// Verify copy is independent by setting a unique value
expectedValue := int8(goroutineID%5 + 1) // Ensure non-zero value
copy.SetNumberHarvests(expectedValue)
// Verify the copy has the value we set
if copy.GetNumberHarvests() != expectedValue {
t.Errorf("Goroutine %d: Copy failed to set value correctly, expected %d got %d",
goroutineID, expectedValue, copy.GetNumberHarvests())
}
// Copy independence is verified by the fact that we can set different values
// We don't check against original since other goroutines are modifying it concurrently
} else {
// Modify original
original.SetNumberHarvests(int8(goroutineID % 10))
original.SetCollectionSkill(SkillMining)
_ = original.GetRandomizeHeading()
}
}
}(i)
}
wg.Wait()
}

View File

@ -0,0 +1,333 @@
package ground_spawn
import (
"sync"
"testing"
"time"
)
// Mock implementations are in test_utils.go
// Test core GroundSpawn concurrency patterns without dependencies
func TestGroundSpawnCoreConcurrency(t *testing.T) {
config := GroundSpawnConfig{
GroundSpawnID: 1,
CollectionSkill: SkillGathering,
NumberHarvests: 10,
AttemptsPerHarvest: 2,
RandomizeHeading: true,
Location: SpawnLocation{
X: 100.0, Y: 200.0, Z: 300.0, Heading: 45.0, GridID: 1,
},
Name: "Test Node",
Description: "A test harvestable node",
}
gs := NewGroundSpawn(config)
const numGoroutines = 100
const operationsPerGoroutine = 100
var wg sync.WaitGroup
t.Run("ConcurrentAccessors", func(t *testing.T) {
wg.Add(numGoroutines)
for i := 0; i < numGoroutines; i++ {
go func(goroutineID int) {
defer wg.Done()
for j := 0; j < operationsPerGoroutine; j++ {
switch j % 8 {
case 0:
gs.SetNumberHarvests(int8(goroutineID % 10))
case 1:
_ = gs.GetNumberHarvests()
case 2:
gs.SetAttemptsPerHarvest(int8(goroutineID % 5))
case 3:
_ = gs.GetAttemptsPerHarvest()
case 4:
gs.SetCollectionSkill(SkillMining)
case 5:
_ = gs.GetCollectionSkill()
case 6:
gs.SetRandomizeHeading(goroutineID%2 == 0)
case 7:
_ = gs.GetRandomizeHeading()
}
}
}(i)
}
wg.Wait()
})
t.Run("ConcurrentStateChecks", func(t *testing.T) {
wg.Add(numGoroutines)
for i := 0; i < numGoroutines; i++ {
go func(goroutineID int) {
defer wg.Done()
for j := 0; j < operationsPerGoroutine; j++ {
switch j % 4 {
case 0:
_ = gs.IsDepleted()
case 1:
_ = gs.IsAvailable()
case 2:
_ = gs.GetHarvestMessageName(true, false)
case 3:
_ = gs.GetHarvestSpellType()
}
}
}(i)
}
wg.Wait()
})
}
// Test Manager core concurrency patterns
func TestManagerCoreConcurrency(t *testing.T) {
manager := NewManager(nil, &mockLogger{})
// Pre-populate with some ground spawns
for i := int32(1); i <= 10; i++ {
config := GroundSpawnConfig{
GroundSpawnID: i,
CollectionSkill: SkillGathering,
NumberHarvests: 5,
AttemptsPerHarvest: 1,
Location: SpawnLocation{
X: float32(i * 10), Y: float32(i * 20), Z: float32(i * 30),
Heading: float32(i * 45), GridID: 1,
},
Name: "Test Node",
Description: "Test node",
}
gs := manager.CreateGroundSpawn(config)
if gs == nil {
t.Fatalf("Failed to create ground spawn %d", i)
}
}
const numGoroutines = 50
const operationsPerGoroutine = 50
var wg sync.WaitGroup
t.Run("ConcurrentAccess", func(t *testing.T) {
wg.Add(numGoroutines)
for i := 0; i < numGoroutines; i++ {
go func(goroutineID int) {
defer wg.Done()
for j := 0; j < operationsPerGoroutine; j++ {
spawnID := int32((goroutineID % 10) + 1)
switch j % 5 {
case 0:
_ = manager.GetGroundSpawn(spawnID)
case 1:
_ = manager.GetGroundSpawnsByZone(1)
case 2:
_ = manager.GetGroundSpawnCount()
case 3:
_ = manager.GetActiveGroundSpawns()
case 4:
_ = manager.GetDepletedGroundSpawns()
}
}
}(i)
}
wg.Wait()
})
t.Run("ConcurrentStatistics", func(t *testing.T) {
wg.Add(numGoroutines)
for i := 0; i < numGoroutines; i++ {
go func(goroutineID int) {
defer wg.Done()
for j := 0; j < operationsPerGoroutine; j++ {
switch j % 3 {
case 0:
_ = manager.GetStatistics()
case 1:
manager.ResetStatistics()
case 2:
// Simulate statistics updates
manager.mutex.Lock()
manager.totalHarvests++
manager.successfulHarvests++
skill := SkillGathering
manager.harvestsBySkill[skill]++
manager.mutex.Unlock()
}
}
}(i)
}
wg.Wait()
// Verify statistics consistency
stats := manager.GetStatistics()
if stats.TotalHarvests < 0 || stats.SuccessfulHarvests < 0 {
t.Errorf("Invalid statistics: total=%d, successful=%d",
stats.TotalHarvests, stats.SuccessfulHarvests)
}
})
}
// Test Copy() method thread safety
func TestCopyThreadSafety(t *testing.T) {
config := GroundSpawnConfig{
GroundSpawnID: 1,
CollectionSkill: SkillGathering,
NumberHarvests: 5,
AttemptsPerHarvest: 1,
Location: SpawnLocation{
X: 100, Y: 200, Z: 300, Heading: 45, GridID: 1,
},
Name: "Copy Test Node",
Description: "Test node for copy safety",
}
original := NewGroundSpawn(config)
const numGoroutines = 50
var wg sync.WaitGroup
wg.Add(numGoroutines)
// Test copying while modifying
for i := 0; i < numGoroutines; i++ {
go func(goroutineID int) {
defer wg.Done()
for j := 0; j < 100; j++ {
if j%2 == 0 {
// Copy operations
copy := original.Copy()
if copy == nil {
t.Errorf("Goroutine %d: Copy returned nil", goroutineID)
continue
}
// Verify copy is independent by setting different values
newValue := int8(goroutineID%5 + 1) // Ensure non-zero value
copy.SetNumberHarvests(newValue)
// Copy should have the new value we just set
if copy.GetNumberHarvests() != newValue {
t.Errorf("Goroutine %d: Copy failed to set value correctly, expected %d got %d",
goroutineID, newValue, copy.GetNumberHarvests())
}
// Note: We can't reliably test that original is unchanged due to concurrent modifications
} else {
// Modify original
original.SetNumberHarvests(int8(goroutineID % 10))
original.SetCollectionSkill(SkillMining)
_ = original.GetRandomizeHeading()
}
}
}(i)
}
wg.Wait()
}
// Test core deadlock prevention
func TestCoreDeadlockPrevention(t *testing.T) {
manager := NewManager(nil, &mockLogger{})
// Create test ground spawns
for i := int32(1); i <= 5; i++ {
config := GroundSpawnConfig{
GroundSpawnID: i,
CollectionSkill: SkillGathering,
NumberHarvests: 5,
AttemptsPerHarvest: 1,
Location: SpawnLocation{
X: float32(i * 10), Y: float32(i * 20), Z: float32(i * 30),
Heading: 0, GridID: 1,
},
Name: "Deadlock Test Node",
Description: "Test node",
}
gs := manager.CreateGroundSpawn(config)
if gs == nil {
t.Fatalf("Failed to create ground spawn %d", i)
}
}
const numGoroutines = 25
var wg sync.WaitGroup
// Test potential deadlock scenarios
t.Run("MixedOperations", func(t *testing.T) {
done := make(chan bool, 1)
// Set a timeout to detect deadlocks
go func() {
time.Sleep(5 * time.Second)
select {
case <-done:
return
default:
t.Error("Potential deadlock detected - test timed out")
}
}()
wg.Add(numGoroutines)
for i := 0; i < numGoroutines; i++ {
go func(goroutineID int) {
defer wg.Done()
for j := 0; j < 50; j++ {
spawnID := int32((goroutineID % 5) + 1)
// Mix operations that could potentially deadlock
switch j % 8 {
case 0:
gs := manager.GetGroundSpawn(spawnID)
if gs != nil {
_ = gs.GetNumberHarvests()
}
case 1:
_ = manager.GetStatistics()
case 2:
_ = manager.GetGroundSpawnsByZone(1)
case 3:
gs := manager.GetGroundSpawn(spawnID)
if gs != nil {
gs.SetNumberHarvests(int8(j % 5))
}
case 4:
manager.ProcessRespawns()
case 5:
_ = manager.GetActiveGroundSpawns()
case 6:
gs := manager.GetGroundSpawn(spawnID)
if gs != nil {
_ = gs.Copy()
}
case 7:
gs := manager.GetGroundSpawn(spawnID)
if gs != nil && gs.IsDepleted() {
manager.scheduleRespawn(gs)
}
}
}
}(i)
}
wg.Wait()
done <- true
})
}

View File

@ -0,0 +1,249 @@
package ground_spawn
import (
"fmt"
"eq2emu/internal/database"
)
// DatabaseAdapter implements the Database interface using the internal database wrapper
type DatabaseAdapter struct {
db *database.DB
}
// NewDatabaseAdapter creates a new database adapter using the database wrapper
func NewDatabaseAdapter(db *database.DB) *DatabaseAdapter {
return &DatabaseAdapter{
db: db,
}
}
// LoadGroundSpawnEntries loads harvest entries for a ground spawn
func (da *DatabaseAdapter) LoadGroundSpawnEntries(groundspawnID int32) ([]*GroundSpawnEntry, error) {
query := `
SELECT min_skill_level, min_adventure_level, bonus_table, harvest_1, harvest_3,
harvest_5, harvest_imbue, harvest_rare, harvest_10, harvest_coin
FROM groundspawn_entries
WHERE groundspawn_id = ?
ORDER BY min_skill_level ASC
`
var entries []*GroundSpawnEntry
err := da.db.Query(query, func(row *database.Row) error {
entry := &GroundSpawnEntry{}
var bonusTable int32
entry.MinSkillLevel = int16(row.Int(0))
entry.MinAdventureLevel = int16(row.Int(1))
bonusTable = int32(row.Int(2))
entry.Harvest1 = float32(row.Float(3))
entry.Harvest3 = float32(row.Float(4))
entry.Harvest5 = float32(row.Float(5))
entry.HarvestImbue = float32(row.Float(6))
entry.HarvestRare = float32(row.Float(7))
entry.Harvest10 = float32(row.Float(8))
entry.HarvestCoin = float32(row.Float(9))
entry.BonusTable = bonusTable == 1
entries = append(entries, entry)
return nil
}, groundspawnID)
if err != nil {
return nil, fmt.Errorf("failed to query groundspawn entries: %w", err)
}
return entries, nil
}
// LoadGroundSpawnItems loads harvest items for a ground spawn
func (da *DatabaseAdapter) LoadGroundSpawnItems(groundspawnID int32) ([]*GroundSpawnEntryItem, error) {
query := `
SELECT item_id, is_rare, grid_id, quantity
FROM groundspawn_items
WHERE groundspawn_id = ?
ORDER BY item_id ASC
`
var items []*GroundSpawnEntryItem
err := da.db.Query(query, func(row *database.Row) error {
item := &GroundSpawnEntryItem{}
item.ItemID = int32(row.Int(0))
item.IsRare = int8(row.Int(1))
item.GridID = int32(row.Int(2))
item.Quantity = int16(row.Int(3))
items = append(items, item)
return nil
}, groundspawnID)
if err != nil {
return nil, fmt.Errorf("failed to query groundspawn items: %w", err)
}
return items, nil
}
// SaveGroundSpawn saves a ground spawn to the database
func (da *DatabaseAdapter) SaveGroundSpawn(gs *GroundSpawn) error {
if gs == nil {
return fmt.Errorf("ground spawn cannot be nil")
}
query := `
INSERT OR REPLACE INTO groundspawns (
id, name, x, y, z, heading, respawn_timer, collection_skill,
number_harvests, attempts_per_harvest, groundspawn_entry_id,
randomize_heading, zone_id, created_at, updated_at
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))
`
randomizeHeading := 0
if gs.GetRandomizeHeading() {
randomizeHeading = 1
}
// TODO: Get actual zone ID from spawn
zoneID := int32(1)
err := da.db.Exec(query,
gs.GetID(),
gs.GetName(),
gs.GetX(),
gs.GetY(),
gs.GetZ(),
int16(gs.GetHeading()),
300, // Default 5 minutes respawn timer
gs.GetCollectionSkill(),
gs.GetNumberHarvests(),
gs.GetAttemptsPerHarvest(),
gs.GetGroundSpawnEntryID(),
randomizeHeading,
zoneID,
)
if err != nil {
return fmt.Errorf("failed to save ground spawn %d: %w", gs.GetID(), err)
}
return nil
}
// LoadAllGroundSpawns loads all ground spawns from the database
func (da *DatabaseAdapter) LoadAllGroundSpawns() ([]*GroundSpawn, error) {
query := `
SELECT id, name, x, y, z, heading, collection_skill, number_harvests,
attempts_per_harvest, groundspawn_entry_id, randomize_heading
FROM groundspawns
WHERE zone_id = ?
ORDER BY id ASC
`
// TODO: Support multiple zones
zoneID := int32(1)
var groundSpawns []*GroundSpawn
err := da.db.Query(query, func(row *database.Row) error {
id := int32(row.Int(0))
name := row.Text(1)
x := float32(row.Float(2))
y := float32(row.Float(3))
z := float32(row.Float(4))
heading := float32(row.Float(5))
collectionSkill := row.Text(6)
numberHarvests := int8(row.Int(7))
attemptsPerHarvest := int8(row.Int(8))
groundspawnEntryID := int32(row.Int(9))
randomizeHeading := int32(row.Int(10))
config := GroundSpawnConfig{
GroundSpawnID: groundspawnEntryID,
CollectionSkill: collectionSkill,
NumberHarvests: numberHarvests,
AttemptsPerHarvest: attemptsPerHarvest,
RandomizeHeading: randomizeHeading == 1,
Location: SpawnLocation{
X: x,
Y: y,
Z: z,
Heading: heading,
GridID: 1, // TODO: Load from database
},
Name: name,
Description: fmt.Sprintf("A %s node", collectionSkill),
}
gs := NewGroundSpawn(config)
gs.SetID(id)
groundSpawns = append(groundSpawns, gs)
return nil
}, zoneID)
if err != nil {
return nil, fmt.Errorf("failed to query groundspawns: %w", err)
}
return groundSpawns, nil
}
// DeleteGroundSpawn deletes a ground spawn from the database
func (da *DatabaseAdapter) DeleteGroundSpawn(id int32) error {
query := `DELETE FROM groundspawns WHERE id = ?`
err := da.db.Exec(query, id)
if err != nil {
return fmt.Errorf("failed to delete ground spawn %d: %w", id, err)
}
return nil
}
// LoadPlayerHarvestStatistics loads harvest statistics for a player
func (da *DatabaseAdapter) LoadPlayerHarvestStatistics(playerID int32) (map[string]int64, error) {
query := `
SELECT skill_name, harvest_count
FROM player_harvest_stats
WHERE player_id = ?
`
stats := make(map[string]int64)
err := da.db.Query(query, func(row *database.Row) error {
skillName := row.Text(0)
harvestCount := row.Int64(1)
stats[skillName] = harvestCount
return nil
}, playerID)
if err != nil {
return nil, fmt.Errorf("failed to query player harvest stats: %w", err)
}
return stats, nil
}
// SavePlayerHarvestStatistic saves a player's harvest statistic
func (da *DatabaseAdapter) SavePlayerHarvestStatistic(playerID int32, skillName string, count int64) error {
query := `
INSERT OR REPLACE INTO player_harvest_stats (player_id, skill_name, harvest_count, updated_at)
VALUES (?, ?, ?, datetime('now'))
`
err := da.db.Exec(query, playerID, skillName, count)
if err != nil {
return fmt.Errorf("failed to save player harvest stat: %w", err)
}
return nil
}

View File

@ -24,18 +24,20 @@ func NewGroundSpawn(config GroundSpawnConfig) *GroundSpawn {
// Configure base spawn properties
gs.SetName(config.Name)
gs.SetSpawnType(DefaultSpawnType)
gs.SetDifficulty(DefaultDifficulty)
gs.SetState(DefaultState)
// Note: SetDifficulty and SetState methods not available in spawn interface
// Set position
gs.SetX(config.Location.X)
gs.SetY(config.Location.Y)
gs.SetY(config.Location.Y, false)
gs.SetZ(config.Location.Z)
if config.RandomizeHeading {
gs.SetHeading(rand.Float32() * 360.0)
// Convert float32 to int16 for heading
heading := int16(rand.Float32() * 360.0)
gs.SetHeading(heading, heading)
} else {
gs.SetHeading(config.Location.Heading)
heading := int16(config.Location.Heading)
gs.SetHeading(heading, heading)
}
return gs
@ -47,12 +49,14 @@ func (gs *GroundSpawn) Copy() *GroundSpawn {
defer gs.harvestMutex.Unlock()
newSpawn := &GroundSpawn{
Spawn: gs.Spawn.Copy().(*spawn.Spawn),
Spawn: gs.Spawn, // TODO: Implement proper copy when spawn.Copy() is available
numberHarvests: gs.numberHarvests,
numAttemptsPerHarvest: gs.numAttemptsPerHarvest,
groundspawnID: gs.groundspawnID,
collectionSkill: gs.collectionSkill,
randomizeHeading: gs.randomizeHeading,
// Reset mutexes in the copy to avoid sharing the same mutex instances
// harvestMutex and harvestUseMutex are zero-initialized (correct behavior)
}
return newSpawn
@ -252,14 +256,14 @@ func (gs *GroundSpawn) ProcessHarvest(context *HarvestContext) (*HarvestResult,
}
// Validate harvest data
if context.GroundSpawnEntries == nil || len(context.GroundSpawnEntries) == 0 {
if len(context.GroundSpawnEntries) == 0 {
return &HarvestResult{
Success: false,
MessageText: fmt.Sprintf("Error: No groundspawn entries assigned to groundspawn id: %d", gs.groundspawnID),
}, nil
}
if context.GroundSpawnItems == nil || len(context.GroundSpawnItems) == 0 {
if len(context.GroundSpawnItems) == 0 {
return &HarvestResult{
Success: false,
MessageText: fmt.Sprintf("Error: No groundspawn items assigned to groundspawn id: %d", gs.groundspawnID),
@ -351,7 +355,7 @@ func (gs *GroundSpawn) filterHarvestTables(context *HarvestContext) []*GroundSpa
}
// Check level requirement for bonus tables
if entry.BonusTable && context.Player.GetLevel() < entry.MinAdventureLevel {
if entry.BonusTable && (*context.Player).GetLevel() < entry.MinAdventureLevel {
continue
}
@ -441,9 +445,9 @@ func (gs *GroundSpawn) awardHarvestItems(harvestType int8, availableItems []*Gro
var items []*HarvestedItem
// Filter items based on harvest type and player location
normalItems := gs.filterItems(availableItems, ItemRarityNormal, player.GetLocation())
rareItems := gs.filterItems(availableItems, ItemRarityRare, player.GetLocation())
imbueItems := gs.filterItems(availableItems, ItemRarityImbue, player.GetLocation())
normalItems := gs.filterItems(availableItems, ItemRarityNormal, (*player).GetLocation())
rareItems := gs.filterItems(availableItems, ItemRarityRare, (*player).GetLocation())
imbueItems := gs.filterItems(availableItems, ItemRarityImbue, (*player).GetLocation())
switch harvestType {
case HarvestType1Item:
@ -522,7 +526,7 @@ func (gs *GroundSpawn) handleSkillProgression(context *HarvestContext, table *Gr
// Check if player skill is already at max for this node
maxSkillAllowed := int16(float32(context.MaxSkillRequired) * 1.0) // TODO: Use skill multiplier rule
if context.PlayerSkill.GetCurrentValue() >= maxSkillAllowed {
if (*context.PlayerSkill).GetCurrentValue() >= maxSkillAllowed {
return false
}
@ -579,7 +583,7 @@ func (gs *GroundSpawn) handleHarvestUse(client Client) error {
if client.GetLogger() != nil {
client.GetLogger().LogDebug("Player %s attempting to harvest %s using spell %s",
client.GetPlayer().GetName(), gs.GetName(), spellName)
(*client.GetPlayer()).GetName(), gs.GetName(), spellName)
}
return nil
@ -595,7 +599,7 @@ func (gs *GroundSpawn) handleCommandUse(client Client, command string) error {
if client.GetLogger() != nil {
client.GetLogger().LogDebug("Player %s using command %s on %s",
client.GetPlayer().GetName(), command, gs.GetName())
(*client.GetPlayer()).GetName(), command, gs.GetName())
}
return nil
@ -603,8 +607,9 @@ func (gs *GroundSpawn) handleCommandUse(client Client, command string) error {
// Serialize creates a packet representation of the ground spawn
func (gs *GroundSpawn) Serialize(player *Player, version int16) ([]byte, error) {
// Use base spawn serialization
return gs.Spawn.Serialize(player, version)
// TODO: Implement proper ground spawn serialization when spawn.Serialize is available
// For now, return empty packet as placeholder
return make([]byte, 0), nil
}
// Respawn resets the ground spawn to harvestable state
@ -617,9 +622,24 @@ func (gs *GroundSpawn) Respawn() {
// Randomize heading if configured
if gs.randomizeHeading {
gs.SetHeading(rand.Float32() * 360.0)
heading := int16(rand.Float32() * 360.0)
gs.SetHeading(heading, heading)
}
// Mark as alive
gs.SetAlive(true)
}
// MeetsSpawnAccessRequirements checks if a player can access this ground spawn
func (gs *GroundSpawn) MeetsSpawnAccessRequirements(player *Player) bool {
// TODO: Implement proper access requirements checking
// For now, allow all players to access ground spawns
return player != nil
}
// HasCommandIcon returns true if this ground spawn has command interactions
func (gs *GroundSpawn) HasCommandIcon() bool {
// TODO: Implement command icon checking based on spawn configuration
// For now, ground spawns don't have command icons (only harvest)
return false
}

View File

@ -11,10 +11,10 @@ type Database interface {
// Logger interface for ground spawn logging
type Logger interface {
LogInfo(message string, args ...interface{})
LogError(message string, args ...interface{})
LogDebug(message string, args ...interface{})
LogWarning(message string, args ...interface{})
LogInfo(message string, args ...any)
LogError(message string, args ...any)
LogDebug(message string, args ...any)
LogWarning(message string, args ...any)
}
// Player interface for ground spawn interactions
@ -35,7 +35,7 @@ type Client interface {
GetVersion() int16
GetLogger() Logger
GetCurrentZoneID() int32
Message(channel int32, message string, args ...interface{})
Message(channel int32, message string, args ...any)
SimpleMessage(channel int32, message string)
SendPopupMessage(type_ int32, message string, sound string, duration float32, r, g, b int32)
AddItem(item *Item, itemDeleted *bool) error
@ -126,8 +126,8 @@ type SkillProvider interface {
// SpawnProvider interface for spawn system integration
type SpawnProvider interface {
CreateSpawn() interface{}
GetSpawn(id int32) interface{}
CreateSpawn() any
GetSpawn(id int32) any
RegisterGroundSpawn(gs *GroundSpawn) error
UnregisterGroundSpawn(id int32) error
}
@ -168,7 +168,7 @@ func (pgsa *PlayerGroundSpawnAdapter) CanHarvest(gs *GroundSpawn) bool {
}
// Check if player has required skill
skill := pgsa.player.GetSkillByName(gs.GetCollectionSkill())
skill := (*pgsa.player).GetSkillByName(gs.GetCollectionSkill())
if skill == nil {
return false
}
@ -184,7 +184,7 @@ func (pgsa *PlayerGroundSpawnAdapter) GetHarvestSkill(skillName string) *Skill {
return nil
}
return pgsa.player.GetSkillByName(skillName)
return (*pgsa.player).GetSkillByName(skillName)
}
// GetHarvestModifiers returns harvest modifiers for the player
@ -207,7 +207,7 @@ func (pgsa *PlayerGroundSpawnAdapter) OnHarvestResult(result *HarvestResult) {
if result.Success && len(result.ItemsAwarded) > 0 {
if pgsa.logger != nil {
pgsa.logger.LogDebug("Player %s successfully harvested %d items",
pgsa.player.GetName(), len(result.ItemsAwarded))
(*pgsa.player).GetName(), len(result.ItemsAwarded))
}
}
}
@ -227,7 +227,7 @@ func NewHarvestEventAdapter(handler HarvestHandler, logger Logger) *HarvestEvent
}
// ProcessHarvestEvent processes a harvest event
func (hea *HarvestEventAdapter) ProcessHarvestEvent(eventType string, gs *GroundSpawn, player *Player, data interface{}) {
func (hea *HarvestEventAdapter) ProcessHarvestEvent(eventType string, gs *GroundSpawn, player *Player, data any) {
if hea.handler == nil {
return
}

View File

@ -13,8 +13,11 @@ func NewManager(database Database, logger Logger) *Manager {
entriesByID: make(map[int32][]*GroundSpawnEntry),
itemsByID: make(map[int32][]*GroundSpawnEntryItem),
respawnQueue: make(map[int32]time.Time),
activeSpawns: make(map[int32]*GroundSpawn),
depletedSpawns: make(map[int32]*GroundSpawn),
database: database,
logger: logger,
nextSpawnID: 1,
harvestsBySkill: make(map[string]int64),
}
}
@ -41,8 +44,22 @@ func (m *Manager) Initialize() error {
m.mutex.Lock()
defer m.mutex.Unlock()
var maxID int32
for _, gs := range groundSpawns {
m.groundSpawns[gs.GetID()] = gs
spawnID := gs.GetID()
m.groundSpawns[spawnID] = gs
// Track max ID for nextSpawnID initialization
if spawnID > maxID {
maxID = spawnID
}
// Populate active/depleted caches based on current state
if gs.IsAvailable() {
m.activeSpawns[spawnID] = gs
} else if gs.IsDepleted() {
m.depletedSpawns[spawnID] = gs
}
// Group by zone (placeholder - zone ID would come from spawn location)
zoneID := int32(1) // TODO: Get actual zone ID from spawn
@ -50,10 +67,13 @@ func (m *Manager) Initialize() error {
// Load harvest entries and items
if err := m.loadGroundSpawnData(gs); err != nil && m.logger != nil {
m.logger.LogWarning("Failed to load data for ground spawn %d: %v", gs.GetID(), err)
m.logger.LogWarning("Failed to load data for ground spawn %d: %v", spawnID, err)
}
}
// Set nextSpawnID to avoid collisions
m.nextSpawnID = maxID + 1
if m.logger != nil {
m.logger.LogInfo("Loaded %d ground spawns from database", len(groundSpawns))
}
@ -89,15 +109,24 @@ func (m *Manager) CreateGroundSpawn(config GroundSpawnConfig) *GroundSpawn {
m.mutex.Lock()
defer m.mutex.Unlock()
// Generate ID (placeholder implementation)
newID := int32(len(m.groundSpawns) + 1)
// Use efficient ID counter instead of len()
newID := m.nextSpawnID
m.nextSpawnID++
gs.SetID(newID)
// Store ground spawn
m.groundSpawns[newID] = gs
// Group by zone
// Add to active cache (new spawns are typically active)
if gs.IsAvailable() {
m.activeSpawns[newID] = gs
}
// Group by zone - pre-allocate zone slice if needed
zoneID := int32(1) // TODO: Get actual zone ID from config.Location
if m.spawnsByZone[zoneID] == nil {
m.spawnsByZone[zoneID] = make([]*GroundSpawn, 0, 16) // Pre-allocate with reasonable capacity
}
m.spawnsByZone[zoneID] = append(m.spawnsByZone[zoneID], gs)
if m.logger != nil {
@ -121,13 +150,13 @@ func (m *Manager) GetGroundSpawnsByZone(zoneID int32) []*GroundSpawn {
defer m.mutex.RUnlock()
spawns := m.spawnsByZone[zoneID]
if spawns == nil {
if len(spawns) == 0 {
return []*GroundSpawn{}
}
// Return a copy to prevent external modification
result := make([]*GroundSpawn, len(spawns))
copy(result, spawns)
// Return a copy to prevent external modification - use append for better performance
result := make([]*GroundSpawn, 0, len(spawns))
result = append(result, spawns...)
return result
}
@ -179,8 +208,11 @@ func (m *Manager) ProcessHarvest(gs *GroundSpawn, player *Player) (*HarvestResul
m.mutex.Unlock()
}
// Handle respawn if depleted
// Handle respawn if depleted and update cache
if gs.IsDepleted() {
m.mutex.Lock()
m.updateSpawnStateCache(gs)
m.mutex.Unlock()
m.scheduleRespawn(gs)
}
@ -196,11 +228,11 @@ func (m *Manager) buildHarvestContext(gs *GroundSpawn, player *Player) (*Harvest
items := m.itemsByID[groundspawnID]
m.mutex.RUnlock()
if entries == nil || len(entries) == 0 {
if len(entries) == 0 {
return nil, fmt.Errorf("no harvest entries found for groundspawn %d", groundspawnID)
}
if items == nil || len(items) == 0 {
if len(items) == 0 {
return nil, fmt.Errorf("no harvest items found for groundspawn %d", groundspawnID)
}
@ -210,13 +242,13 @@ func (m *Manager) buildHarvestContext(gs *GroundSpawn, player *Player) (*Harvest
skillName = SkillGathering // Collections use gathering skill
}
playerSkill := player.GetSkillByName(skillName)
playerSkill := (*player).GetSkillByName(skillName)
if playerSkill == nil {
return nil, fmt.Errorf("player lacks required skill: %s", skillName)
}
// Calculate total skill (base + bonuses)
totalSkill := playerSkill.GetCurrentValue()
totalSkill := (*playerSkill).GetCurrentValue()
// TODO: Add stat bonuses when stat system is integrated
// Find max skill required
@ -276,6 +308,12 @@ func (m *Manager) ProcessRespawns() {
for _, spawnID := range toRespawn {
if gs := m.GetGroundSpawn(spawnID); gs != nil {
gs.Respawn()
// Update cache after respawn
m.mutex.Lock()
m.updateSpawnStateCache(gs)
m.mutex.Unlock()
if m.logger != nil {
m.logger.LogDebug("Ground spawn %d respawned", spawnID)
}
@ -365,6 +403,8 @@ func (m *Manager) RemoveGroundSpawn(id int32) bool {
delete(m.groundSpawns, id)
delete(m.respawnQueue, id)
delete(m.activeSpawns, id)
delete(m.depletedSpawns, id)
// Remove from zone list
// TODO: Get actual zone ID from ground spawn
@ -401,12 +441,11 @@ func (m *Manager) GetActiveGroundSpawns() []*GroundSpawn {
m.mutex.RLock()
defer m.mutex.RUnlock()
var active []*GroundSpawn
for _, gs := range m.groundSpawns {
if gs.IsAvailable() {
// Use cached active spawns for O(1) performance instead of O(N) iteration
active := make([]*GroundSpawn, 0, len(m.activeSpawns))
for _, gs := range m.activeSpawns {
active = append(active, gs)
}
}
return active
}
@ -416,16 +455,32 @@ func (m *Manager) GetDepletedGroundSpawns() []*GroundSpawn {
m.mutex.RLock()
defer m.mutex.RUnlock()
var depleted []*GroundSpawn
for _, gs := range m.groundSpawns {
if gs.IsDepleted() {
// Use cached depleted spawns for O(1) performance instead of O(N) iteration
depleted := make([]*GroundSpawn, 0, len(m.depletedSpawns))
for _, gs := range m.depletedSpawns {
depleted = append(depleted, gs)
}
}
return depleted
}
// updateSpawnStateCache updates the active/depleted caches when a spawn's state changes
// IMPORTANT: This method must be called while holding the manager's mutex
func (m *Manager) updateSpawnStateCache(gs *GroundSpawn) {
spawnID := gs.GetID()
// Remove from both caches first
delete(m.activeSpawns, spawnID)
delete(m.depletedSpawns, spawnID)
// Add to appropriate cache based on current state
if gs.IsAvailable() {
m.activeSpawns[spawnID] = gs
} else if gs.IsDepleted() {
m.depletedSpawns[spawnID] = gs
}
}
// ProcessCommand handles ground spawn management commands
func (m *Manager) ProcessCommand(command string, args []string) (string, error) {
switch command {
@ -536,7 +591,7 @@ func (m *Manager) handleInfoCommand(args []string) (string, error) {
return fmt.Sprintf("Ground spawn %d not found.", spawnID), nil
}
result := fmt.Sprintf("Ground Spawn Information:\n")
result := "Ground Spawn Information:\n"
result += fmt.Sprintf("ID: %d\n", gs.GetID())
result += fmt.Sprintf("Name: %s\n", gs.GetName())
result += fmt.Sprintf("Collection Skill: %s\n", gs.GetCollectionSkill())
@ -550,7 +605,7 @@ func (m *Manager) handleInfoCommand(args []string) (string, error) {
}
// handleReloadCommand reloads ground spawns from database
func (m *Manager) handleReloadCommand(args []string) (string, error) {
func (m *Manager) handleReloadCommand(_ []string) (string, error) {
if m.database == nil {
return "", fmt.Errorf("no database available")
}
@ -562,6 +617,9 @@ func (m *Manager) handleReloadCommand(args []string) (string, error) {
m.entriesByID = make(map[int32][]*GroundSpawnEntry)
m.itemsByID = make(map[int32][]*GroundSpawnEntryItem)
m.respawnQueue = make(map[int32]time.Time)
m.activeSpawns = make(map[int32]*GroundSpawn)
m.depletedSpawns = make(map[int32]*GroundSpawn)
m.nextSpawnID = 1 // Reset ID counter
m.mutex.Unlock()
// Reload from database
@ -588,4 +646,7 @@ func (m *Manager) Shutdown() {
m.entriesByID = make(map[int32][]*GroundSpawnEntry)
m.itemsByID = make(map[int32][]*GroundSpawnEntryItem)
m.respawnQueue = make(map[int32]time.Time)
m.activeSpawns = make(map[int32]*GroundSpawn)
m.depletedSpawns = make(map[int32]*GroundSpawn)
m.nextSpawnID = 1
}

View File

@ -0,0 +1,8 @@
package ground_spawn
type mockLogger struct{}
func (l *mockLogger) LogInfo(message string, args ...any) {}
func (l *mockLogger) LogError(message string, args ...any) {}
func (l *mockLogger) LogDebug(message string, args ...any) {}
func (l *mockLogger) LogWarning(message string, args ...any) {}

View File

@ -111,10 +111,15 @@ type Manager struct {
itemsByID map[int32][]*GroundSpawnEntryItem // Harvest items by groundspawn ID
respawnQueue map[int32]time.Time // Respawn timestamps
// Performance optimization: cache active/depleted spawns to avoid O(N) scans
activeSpawns map[int32]*GroundSpawn // Cache of active spawns for O(1) lookups
depletedSpawns map[int32]*GroundSpawn // Cache of depleted spawns for O(1) lookups
database Database // Database interface
logger Logger // Logging interface
mutex sync.RWMutex // Thread safety
nextSpawnID int32 // Efficient ID counter to avoid len() calls
// Statistics
totalHarvests int64 // Total harvest attempts

View File

@ -275,7 +275,7 @@ type MyGroupPacketHandler struct {
// client connection management
}
func (ph *MyGroupPacketHandler) SendGroupUpdate(members []*groups.GroupMemberInfo, excludeClient interface{}) error {
func (ph *MyGroupPacketHandler) SendGroupUpdate(members []*groups.GroupMemberInfo, excludeClient any) error {
// Send group update packets to clients
return nil
}

View File

@ -316,12 +316,12 @@ func (g *Group) Disband() {
}
// SendGroupUpdate sends an update to all group members
func (g *Group) SendGroupUpdate(excludeClient interface{}, forceRaidUpdate bool) {
func (g *Group) SendGroupUpdate(excludeClient any, forceRaidUpdate bool) {
g.sendGroupUpdate(excludeClient, forceRaidUpdate)
}
// sendGroupUpdate internal method to send group updates
func (g *Group) sendGroupUpdate(excludeClient interface{}, forceRaidUpdate bool) {
func (g *Group) sendGroupUpdate(excludeClient any, forceRaidUpdate bool) {
update := NewGroupUpdate(GROUP_UPDATE_FLAG_MEMBER_LIST, g.id)
update.ExcludeClient = excludeClient
update.ForceRaidUpdate = forceRaidUpdate
@ -430,7 +430,7 @@ func (g *Group) GetLeaderName() string {
}
// ShareQuestWithGroup shares a quest with all group members
func (g *Group) ShareQuestWithGroup(questSharer interface{}, quest interface{}) bool {
func (g *Group) ShareQuestWithGroup(questSharer any, quest any) bool {
// TODO: Implement quest sharing
// This would require integration with the quest system
return false

View File

@ -38,7 +38,7 @@ type GroupManagerInterface interface {
RemoveGroupMemberByName(groupID int32, name string, isClient bool, charID int32) error
// Group updates
SendGroupUpdate(groupID int32, excludeClient interface{}, forceRaidUpdate bool)
SendGroupUpdate(groupID int32, excludeClient any, forceRaidUpdate bool)
// Invitations
Invite(leader entity.Entity, member entity.Entity) int8
@ -141,9 +141,9 @@ type GroupDatabase interface {
// GroupPacketHandler interface for handling group-related packets
type GroupPacketHandler interface {
// Group update packets
SendGroupUpdate(members []*GroupMemberInfo, excludeClient interface{}) error
SendGroupMemberUpdate(member *GroupMemberInfo, excludeClient interface{}) error
SendGroupOptionsUpdate(groupID int32, options *GroupOptions, excludeClient interface{}) error
SendGroupUpdate(members []*GroupMemberInfo, excludeClient any) error
SendGroupMemberUpdate(member *GroupMemberInfo, excludeClient any) error
SendGroupOptionsUpdate(groupID int32, options *GroupOptions, excludeClient any) error
// Group invitation packets
SendGroupInvite(inviter, invitee entity.Entity) error
@ -154,16 +154,16 @@ type GroupPacketHandler interface {
SendGroupChatMessage(members []*GroupMemberInfo, from string, message string, channel int16, language int32) error
// Raid packets
SendRaidUpdate(raidGroups []*Group, excludeClient interface{}) error
SendRaidUpdate(raidGroups []*Group, excludeClient any) error
SendRaidInvite(leaderGroup, targetGroup *Group) error
SendRaidInviteResponse(leaderGroup, targetGroup *Group, accepted bool) error
// Group UI packets
SendGroupWindowUpdate(client interface{}, group *Group) error
SendRaidWindowUpdate(client interface{}, raidGroups []*Group) error
SendGroupWindowUpdate(client any, group *Group) error
SendRaidWindowUpdate(client any, raidGroups []*Group) error
// Group member packets
SendGroupMemberStats(member *GroupMemberInfo, excludeClient interface{}) error
SendGroupMemberStats(member *GroupMemberInfo, excludeClient any) error
SendGroupMemberZoneChange(member *GroupMemberInfo, oldZoneID, newZoneID int32) error
}
@ -244,8 +244,8 @@ type GroupStatistics interface {
RecordGroupMemoryUsage(groups int32, members int32)
// Statistics retrieval
GetGroupStatistics(groupID int32) map[string]interface{}
GetOverallStatistics() map[string]interface{}
GetGroupStatistics(groupID int32) map[string]any
GetOverallStatistics() map[string]any
GetStatisticsSummary() *GroupManagerStats
}

View File

@ -204,7 +204,7 @@ func (gm *GroupManager) RemoveGroupMemberByName(groupID int32, name string, isCl
}
// SendGroupUpdate sends an update to all members of a group
func (gm *GroupManager) SendGroupUpdate(groupID int32, excludeClient interface{}, forceRaidUpdate bool) {
func (gm *GroupManager) SendGroupUpdate(groupID int32, excludeClient any, forceRaidUpdate bool) {
group := gm.GetGroup(groupID)
if group != nil {
group.SendGroupUpdate(excludeClient, forceRaidUpdate)

View File

@ -59,7 +59,7 @@ type GroupMemberInfo struct {
Member entity.Entity `json:"-"`
// Client reference (players only) - interface to avoid circular deps
Client interface{} `json:"-"`
Client any `json:"-"`
// Timestamps
JoinTime time.Time `json:"join_time"`
@ -108,7 +108,7 @@ type GroupMessage struct {
FromName string `json:"from_name"`
Language int32 `json:"language"`
Timestamp time.Time `json:"timestamp"`
ExcludeClient interface{} `json:"-"`
ExcludeClient any `json:"-"`
}
// GroupUpdate represents a group update event
@ -119,7 +119,7 @@ type GroupUpdate struct {
Options *GroupOptions `json:"options,omitempty"`
RaidGroups []int32 `json:"raid_groups,omitempty"`
ForceRaidUpdate bool `json:"force_raid_update"`
ExcludeClient interface{} `json:"-"`
ExcludeClient any `json:"-"`
Timestamp time.Time `json:"timestamp"`
}

View File

@ -182,16 +182,16 @@ type GuildEventHandler interface {
// LogHandler provides logging functionality
type LogHandler interface {
// LogDebug logs debug messages
LogDebug(category, message string, args ...interface{})
LogDebug(category, message string, args ...any)
// LogInfo logs informational messages
LogInfo(category, message string, args ...interface{})
LogInfo(category, message string, args ...any)
// LogError logs error messages
LogError(category, message string, args ...interface{})
LogError(category, message string, args ...any)
// LogWarning logs warning messages
LogWarning(category, message string, args ...interface{})
LogWarning(category, message string, args ...any)
}
// PlayerInfo contains basic player information
@ -308,10 +308,10 @@ type NotificationManager interface {
NotifyMemberLogout(guild *Guild, member *GuildMember)
// NotifyGuildMessage sends a message to all guild members
NotifyGuildMessage(guild *Guild, eventType int8, message string, args ...interface{})
NotifyGuildMessage(guild *Guild, eventType int8, message string, args ...any)
// NotifyOfficers sends a message to officers only
NotifyOfficers(guild *Guild, message string, args ...interface{})
NotifyOfficers(guild *Guild, message string, args ...any)
// NotifyGuildUpdate notifies guild members of guild changes
NotifyGuildUpdate(guild *Guild)

View File

@ -117,10 +117,10 @@ type PlayerManager interface {
// LogHandler defines the interface for logging operations
type LogHandler interface {
LogDebug(system, format string, args ...interface{})
LogInfo(system, format string, args ...interface{})
LogWarning(system, format string, args ...interface{})
LogError(system, format string, args ...interface{})
LogDebug(system, format string, args ...any)
LogInfo(system, format string, args ...any)
LogWarning(system, format string, args ...any)
LogError(system, format string, args ...any)
}
// TimerManager defines the interface for timer management
@ -139,8 +139,8 @@ type TimerManager interface {
// CacheManager defines the interface for caching operations
type CacheManager interface {
// Cache operations
Set(key string, value interface{}, expiration time.Duration) error
Get(key string) (interface{}, bool)
Set(key string, value any, expiration time.Duration) error
Get(key string) (any, bool)
Delete(key string) error
Clear() error
@ -213,6 +213,6 @@ type StatisticsCollector interface {
type ConfigManager interface {
GetHOConfig() *HeroicOPConfig
UpdateHOConfig(config *HeroicOPConfig) error
GetConfigValue(key string) interface{}
SetConfigValue(key string, value interface{}) error
GetConfigValue(key string) any
SetConfigValue(key string, value any) error
}

View File

@ -426,11 +426,11 @@ func (mhol *MasterHeroicOPList) SearchWheels(criteria HeroicOPSearchCriteria) []
}
// GetStatistics returns usage statistics for the HO system
func (mhol *MasterHeroicOPList) GetStatistics() map[string]interface{} {
func (mhol *MasterHeroicOPList) GetStatistics() map[string]any {
mhol.mu.RLock()
defer mhol.mu.RUnlock()
stats := make(map[string]interface{})
stats := make(map[string]any)
// Basic counts
stats["total_starters"] = mhol.getStarterCountNoLock()

View File

@ -159,10 +159,10 @@ type ZoneManager interface {
// LogHandler defines the interface for logging operations
type LogHandler interface {
LogDebug(system, format string, args ...interface{})
LogInfo(system, format string, args ...interface{})
LogWarning(system, format string, args ...interface{})
LogError(system, format string, args ...interface{})
LogDebug(system, format string, args ...any)
LogInfo(system, format string, args ...any)
LogWarning(system, format string, args ...any)
LogError(system, format string, args ...any)
}
// Additional integration interfaces
@ -282,8 +282,8 @@ type AccessManager interface {
type ConfigManager interface {
GetHousingConfig() *HousingConfig
UpdateHousingConfig(config *HousingConfig) error
GetConfigValue(key string) interface{}
SetConfigValue(key string, value interface{}) error
GetConfigValue(key string) any
SetConfigValue(key string, value any) error
}
// NotificationManager defines interface for housing notifications
@ -298,8 +298,8 @@ type NotificationManager interface {
// CacheManager defines interface for caching operations
type CacheManager interface {
// Cache operations
Set(key string, value interface{}, expiration time.Duration) error
Get(key string) (interface{}, bool)
Set(key string, value any, expiration time.Duration) error
Get(key string) (any, bool)
Delete(key string) error
Clear() error

View File

@ -573,7 +573,7 @@ func (isa *ItemSystemAdapter) CraftItem(playerID uint32, itemID int32, quality i
}
// GetPlayerItemStats returns statistics about a player's items
func (isa *ItemSystemAdapter) GetPlayerItemStats(playerID uint32) (map[string]interface{}, error) {
func (isa *ItemSystemAdapter) GetPlayerItemStats(playerID uint32) (map[string]any, error) {
inventory, err := isa.GetPlayerInventory(playerID)
if err != nil {
return nil, err
@ -587,7 +587,7 @@ func (isa *ItemSystemAdapter) GetPlayerItemStats(playerID uint32) (map[string]in
// Calculate equipment bonuses
bonuses := equipment.CalculateEquipmentBonuses()
return map[string]interface{}{
return map[string]any{
"player_id": playerID,
"total_items": inventory.GetNumberOfItems(),
"equipped_items": equipment.GetNumberOfItems(),
@ -601,13 +601,13 @@ func (isa *ItemSystemAdapter) GetPlayerItemStats(playerID uint32) (map[string]in
}
// GetSystemStats returns comprehensive statistics about the item system
func (isa *ItemSystemAdapter) GetSystemStats() map[string]interface{} {
func (isa *ItemSystemAdapter) GetSystemStats() map[string]any {
isa.mutex.RLock()
defer isa.mutex.RUnlock()
masterStats := isa.masterList.GetStats()
return map[string]interface{}{
return map[string]any{
"total_item_templates": masterStats.TotalItems,
"items_by_type": masterStats.ItemsByType,
"items_by_tier": masterStats.ItemsByTier,

View File

@ -73,7 +73,7 @@ type PlayerService interface {
// ZoneService interface for zone-related operations
type ZoneService interface {
GetZoneRule(zoneID int32, ruleName string) (interface{}, error)
GetZoneRule(zoneID int32, ruleName string) (any, error)
SpawnObjectInZone(zoneID int32, appearanceID int32, x, y, z, heading float32, name string, commands []string) (int32, error)
RemoveObjectFromZone(zoneID int32, objectID int32) error
GetDistanceBetweenPoints(x1, y1, z1, x2, y2, z2 float32) float32
@ -410,7 +410,7 @@ func (cs *ChestService) handleDisarmChest(chest *TreasureChest, playerID uint32)
}
// Roll for success
roll := float32(time.Now().UnixNano()%100) // Simple random
roll := float32(time.Now().UnixNano() % 100) // Simple random
if roll > successChance {
// Failed disarm - could trigger trap effects here
return &ChestInteractionResult{
@ -460,7 +460,7 @@ func (cs *ChestService) handleLockpickChest(chest *TreasureChest, playerID uint3
}
// Roll for success
roll := float32(time.Now().UnixNano()%100) // Simple random
roll := float32(time.Now().UnixNano() % 100) // Simple random
if roll > successChance {
return &ChestInteractionResult{
Success: false,

View File

@ -587,8 +587,8 @@ func (ldb *LootDatabase) DeleteSpawnLoot(spawnID int32) error {
}
// GetLootStatistics returns database statistics
func (ldb *LootDatabase) GetLootStatistics() (map[string]interface{}, error) {
stats := make(map[string]interface{})
func (ldb *LootDatabase) GetLootStatistics() (map[string]any, error) {
stats := make(map[string]any)
// Count loot tables
if stmt := ldb.queries["count_loot_tables"]; stmt != nil {

View File

@ -168,8 +168,8 @@ func (ls *LootSystem) ShowChestToPlayer(chestID int32, playerID uint32) error {
}
// GetSystemStatistics returns comprehensive statistics about the loot system
func (ls *LootSystem) GetSystemStatistics() (map[string]interface{}, error) {
stats := make(map[string]interface{})
func (ls *LootSystem) GetSystemStatistics() (map[string]any, error) {
stats := make(map[string]any)
// Database statistics
if dbStats, err := ls.Database.GetLootStatistics(); err == nil {

View File

@ -106,18 +106,18 @@ func (m *MockPlayerService) SetInventorySpace(playerID uint32, space int) {
// MockZoneService implements ZoneService for testing
type MockZoneService struct {
rules map[int32]map[string]interface{}
objects map[int32]map[int32]interface{} // zoneID -> objectID
rules map[int32]map[string]any
objects map[int32]map[int32]any // zoneID -> objectID
}
func NewMockZoneService() *MockZoneService {
return &MockZoneService{
rules: make(map[int32]map[string]interface{}),
objects: make(map[int32]map[int32]interface{}),
rules: make(map[int32]map[string]any),
objects: make(map[int32]map[int32]any),
}
}
func (m *MockZoneService) GetZoneRule(zoneID int32, ruleName string) (interface{}, error) {
func (m *MockZoneService) GetZoneRule(zoneID int32, ruleName string) (any, error) {
if rules, exists := m.rules[zoneID]; exists {
return rules[ruleName], nil
}
@ -127,7 +127,7 @@ func (m *MockZoneService) GetZoneRule(zoneID int32, ruleName string) (interface{
func (m *MockZoneService) SpawnObjectInZone(zoneID int32, appearanceID int32, x, y, z, heading float32, name string, commands []string) (int32, error) {
objectID := int32(len(m.objects[zoneID]) + 1)
if m.objects[zoneID] == nil {
m.objects[zoneID] = make(map[int32]interface{})
m.objects[zoneID] = make(map[int32]any)
}
m.objects[zoneID][objectID] = struct{}{}
return objectID, nil

View File

@ -14,10 +14,10 @@ type Database interface {
// Logger interface for language logging
type Logger interface {
LogInfo(message string, args ...interface{})
LogError(message string, args ...interface{})
LogDebug(message string, args ...interface{})
LogWarning(message string, args ...interface{})
LogInfo(message string, args ...any)
LogError(message string, args ...any)
LogDebug(message string, args ...any)
LogWarning(message string, args ...any)
}
// Player interface for language-related player operations
@ -372,7 +372,7 @@ func NewLanguageEventAdapter(handler LanguageHandler, logger Logger) *LanguageEv
}
// ProcessLanguageEvent processes a language-related event
func (lea *LanguageEventAdapter) ProcessLanguageEvent(eventType string, player *Player, languageID int32, data interface{}) {
func (lea *LanguageEventAdapter) ProcessLanguageEvent(eventType string, player *Player, languageID int32, data any) {
if lea.handler == nil {
return
}

View File

@ -7,10 +7,10 @@ import (
// Logger interface for AI logging
type Logger interface {
LogInfo(message string, args ...interface{})
LogError(message string, args ...interface{})
LogDebug(message string, args ...interface{})
LogWarning(message string, args ...interface{})
LogInfo(message string, args ...any)
LogError(message string, args ...any)
LogDebug(message string, args ...any)
LogWarning(message string, args ...any)
}
// NPC interface defines the required NPC functionality for AI
@ -144,7 +144,7 @@ type LuaInterface interface {
type Zone interface {
GetSpawnByID(int32) Spawn
ProcessSpell(spell Spell, caster NPC, target Spawn) error
CallSpawnScript(npc NPC, scriptType string, args ...interface{}) error
CallSpawnScript(npc NPC, scriptType string, args ...any) error
}
// AIManager provides high-level management of the AI system
@ -209,7 +209,7 @@ func (am *AIManager) GetBrain(npcID int32) Brain {
}
// CreateBrainForNPC creates and adds the appropriate brain for an NPC
func (am *AIManager) CreateBrainForNPC(npc NPC, brainType int8, options ...interface{}) error {
func (am *AIManager) CreateBrainForNPC(npc NPC, brainType int8, options ...any) error {
if npc == nil {
return fmt.Errorf("NPC cannot be nil")
}

View File

@ -289,7 +289,7 @@ func (dfpb *DumbFirePetBrain) ExtendExpireTime(durationMS int32) {
// Brain factory functions
// CreateBrain creates the appropriate brain type for an NPC
func CreateBrain(npc NPC, brainType int8, logger Logger, options ...interface{}) Brain {
func CreateBrain(npc NPC, brainType int8, logger Logger, options ...any) Brain {
switch brainType {
case BrainTypeCombatPet:
return NewCombatPetBrain(npc, logger)

View File

@ -1,5 +1,7 @@
package npc
import "fmt"
// Database interface for NPC persistence
type Database interface {
LoadAllNPCs() ([]*NPC, error)
@ -13,10 +15,10 @@ type Database interface {
// Logger interface for NPC logging
type Logger interface {
LogInfo(message string, args ...interface{})
LogError(message string, args ...interface{})
LogDebug(message string, args ...interface{})
LogWarning(message string, args ...interface{})
LogInfo(message string, args ...any)
LogError(message string, args ...any)
LogDebug(message string, args ...any)
LogWarning(message string, args ...any)
}
// Client interface for NPC-related client operations
@ -50,15 +52,15 @@ type Zone interface {
RemoveNPC(npcID int32) error
GetPlayersInRange(x, y, z, radius float32) []Player
ProcessEntityCommand(command string, client Client, target *NPC) error
CallSpawnScript(npc *NPC, scriptType string, args ...interface{}) error
CallSpawnScript(npc *NPC, scriptType string, args ...any) error
}
// SpellManager interface for spell system integration
type SpellManager interface {
GetSpell(spellID int32, tier int8) Spell
CastSpell(caster *NPC, target interface{}, spell Spell) error
GetSpellEffect(entity interface{}, spellID int32) SpellEffect
ProcessSpell(spell Spell, caster *NPC, target interface{}) error
CastSpell(caster *NPC, target any, spell Spell) error
GetSpellEffect(entity any, spellID int32) SpellEffect
ProcessSpell(spell Spell, caster *NPC, target any) error
}
// Spell interface for spell data
@ -88,8 +90,8 @@ type SpellEffect interface {
type SkillManager interface {
GetSkill(skillID int32) MasterSkill
GetSkillByName(name string) MasterSkill
ApplySkillBonus(entity interface{}, skillID int32, bonus float32) error
RemoveSkillBonus(entity interface{}, skillID int32, bonus float32) error
ApplySkillBonus(entity any, skillID int32, bonus float32) error
RemoveSkillBonus(entity any, skillID int32, bonus float32) error
}
// MasterSkill interface for skill definitions
@ -127,11 +129,11 @@ type MovementManager interface {
// CombatManager interface for combat system integration
type CombatManager interface {
StartCombat(npc *NPC, target interface{}) error
StartCombat(npc *NPC, target any) error
EndCombat(npc *NPC) error
ProcessCombatRound(npc *NPC) error
CalculateDamage(attacker *NPC, target interface{}) int32
ApplyDamage(target interface{}, damage int32) error
CalculateDamage(attacker *NPC, target any) int32
ApplyDamage(target any, damage int32) error
}
// NPCAware interface for entities that can interact with NPCs
@ -287,7 +289,7 @@ func NewSpellCasterAdapter(npc *NPC, spellManager SpellManager, logger Logger) *
}
// GetNextSpell selects the next spell to cast based on AI strategy
func (sca *SpellCasterAdapter) GetNextSpell(target interface{}, distance float32) Spell {
func (sca *SpellCasterAdapter) GetNextSpell(target any, distance float32) Spell {
if sca.npc == nil || sca.spellManager == nil {
return nil
}
@ -307,7 +309,7 @@ func (sca *SpellCasterAdapter) GetNextSpell(target interface{}, distance float32
}
// GetNextBuffSpell selects the next buff spell to cast
func (sca *SpellCasterAdapter) GetNextBuffSpell(target interface{}) Spell {
func (sca *SpellCasterAdapter) GetNextBuffSpell(target any) Spell {
if sca.npc == nil || sca.spellManager == nil {
return nil
}
@ -347,7 +349,7 @@ func (sca *SpellCasterAdapter) GetNextBuffSpell(target interface{}) Spell {
}
// CastSpell attempts to cast a spell
func (sca *SpellCasterAdapter) CastSpell(target interface{}, spell Spell) error {
func (sca *SpellCasterAdapter) CastSpell(target any, spell Spell) error {
if sca.npc == nil || sca.spellManager == nil || spell == nil {
return fmt.Errorf("invalid parameters for spell casting")
}
@ -371,7 +373,7 @@ func (sca *SpellCasterAdapter) CastSpell(target interface{}, spell Spell) error
}
// getNextCastOnAggroSpell selects cast-on-aggro spells
func (sca *SpellCasterAdapter) getNextCastOnAggroSpell(target interface{}) Spell {
func (sca *SpellCasterAdapter) getNextCastOnAggroSpell(target any) Spell {
castOnSpells := sca.npc.castOnSpells[CastOnAggro]
for _, npcSpell := range castOnSpells {
spell := sca.spellManager.GetSpell(npcSpell.GetSpellID(), npcSpell.GetTier())
@ -386,7 +388,7 @@ func (sca *SpellCasterAdapter) getNextCastOnAggroSpell(target interface{}) Spell
}
// getNextSpellByStrategy selects spells based on AI strategy
func (sca *SpellCasterAdapter) getNextSpellByStrategy(target interface{}, distance float32, strategy int8) Spell {
func (sca *SpellCasterAdapter) getNextSpellByStrategy(target any, distance float32, strategy int8) Spell {
// TODO: Implement more sophisticated spell selection based on strategy
for _, npcSpell := range sca.npc.spells {
@ -446,7 +448,7 @@ func NewCombatAdapter(npc *NPC, combatManager CombatManager, logger Logger) *Com
}
// EnterCombat handles entering combat state
func (ca *CombatAdapter) EnterCombat(target interface{}) error {
func (ca *CombatAdapter) EnterCombat(target any) error {
if ca.npc == nil {
return fmt.Errorf("NPC is nil")
}

View File

@ -114,8 +114,8 @@ func (os *ObjectSpawn) ShowsCommandIcon() bool {
}
// GetObjectInfo returns comprehensive information about the object spawn
func (os *ObjectSpawn) GetObjectInfo() map[string]interface{} {
info := make(map[string]interface{})
func (os *ObjectSpawn) GetObjectInfo() map[string]any {
info := make(map[string]any)
// Add spawn info
info["spawn_id"] = os.GetID()
@ -246,7 +246,7 @@ func ConvertSpawnToObject(spawn *spawn.Spawn) *ObjectSpawn {
// LoadObjectSpawnFromData loads object spawn data from database/config
// This would be called when loading spawns from the database
func LoadObjectSpawnFromData(spawnData map[string]interface{}) *ObjectSpawn {
func LoadObjectSpawnFromData(spawnData map[string]any) *ObjectSpawn {
objectSpawn := NewObjectSpawn()
// Load basic spawn data

View File

@ -315,11 +315,11 @@ func (om *ObjectManager) ClearZone(zoneName string) int {
}
// GetStatistics returns statistics about objects in the manager
func (om *ObjectManager) GetStatistics() map[string]interface{} {
func (om *ObjectManager) GetStatistics() map[string]any {
om.mutex.RLock()
defer om.mutex.RUnlock()
stats := make(map[string]interface{})
stats := make(map[string]any)
stats["total_objects"] = len(om.objects)
stats["zones_with_objects"] = len(om.objectsByZone)
stats["interactive_objects"] = len(om.interactiveObjects)

View File

@ -581,11 +581,11 @@ func (o *Object) ShowsCommandIcon() bool {
}
// GetObjectInfo returns comprehensive information about the object
func (o *Object) GetObjectInfo() map[string]interface{} {
func (o *Object) GetObjectInfo() map[string]any {
o.mutex.RLock()
defer o.mutex.RUnlock()
info := make(map[string]interface{})
info := make(map[string]any)
info["clickable"] = o.clickable
info["zone_name"] = o.zoneName
info["device_id"] = o.deviceID

View File

@ -40,10 +40,10 @@ type PlayerManager interface {
GetPlayersInZone(zoneID int32) []*Player
// SendToAll sends a message to all players
SendToAll(message interface{}) error
SendToAll(message any) error
// SendToZone sends a message to all players in a zone
SendToZone(zoneID int32, message interface{}) error
SendToZone(zoneID int32, message any) error
}
// PlayerDatabase interface for database operations
@ -85,13 +85,13 @@ type PlayerDatabase interface {
// PlayerPacketHandler interface for handling player packets
type PlayerPacketHandler interface {
// HandlePacket handles a packet from a player
HandlePacket(player *Player, packet interface{}) error
HandlePacket(player *Player, packet any) error
// SendPacket sends a packet to a player
SendPacket(player *Player, packet interface{}) error
SendPacket(player *Player, packet any) error
// BroadcastPacket broadcasts a packet to multiple players
BroadcastPacket(players []*Player, packet interface{}) error
BroadcastPacket(players []*Player, packet any) error
}
// PlayerEventHandler interface for player events
@ -181,7 +181,7 @@ type PlayerStatistics interface {
RecordSpellCast(player *Player, spell *spells.Spell)
// GetStatistics returns player statistics
GetStatistics(playerID int32) map[string]interface{}
GetStatistics(playerID int32) map[string]any
}
// PlayerNotifier interface for player notifications

View File

@ -274,7 +274,7 @@ func (m *Manager) GetPlayersInZone(zoneID int32) []*Player {
}
// SendToAll sends a message to all players
func (m *Manager) SendToAll(message interface{}) error {
func (m *Manager) SendToAll(message any) error {
if m.packetHandler == nil {
return fmt.Errorf("no packet handler configured")
}
@ -284,7 +284,7 @@ func (m *Manager) SendToAll(message interface{}) error {
}
// SendToZone sends a message to all players in a zone
func (m *Manager) SendToZone(zoneID int32, message interface{}) error {
func (m *Manager) SendToZone(zoneID int32, message any) error {
if m.packetHandler == nil {
return fmt.Errorf("no packet handler configured")
}

View File

@ -114,12 +114,12 @@ type Packet interface {
// PacketStruct interface defines packet structure functionality
type PacketStruct interface {
// Packet building methods
SetDataByName(name string, value interface{}, index ...int)
SetDataByName(name string, value any, index ...int)
SetArrayLengthByName(name string, length int)
SetArrayDataByName(name string, value interface{}, index int)
SetArrayDataByName(name string, value any, index int)
SetSubArrayLengthByName(name string, length int, index int)
SetSubArrayDataByName(name string, value interface{}, index1, index2 int)
SetSubstructArrayDataByName(substruct, name string, value interface{}, index int)
SetSubArrayDataByName(name string, value any, index1, index2 int)
SetSubstructArrayDataByName(substruct, name string, value any, index int)
SetItemArrayDataByName(name string, item Item, player Player, index int, flag ...int)
Serialize() Packet
GetVersion() int16
@ -197,10 +197,10 @@ type Database interface {
// Logger interface defines logging functionality for quest system
type Logger interface {
LogInfo(message string, args ...interface{})
LogError(message string, args ...interface{})
LogDebug(message string, args ...interface{})
LogWarning(message string, args ...interface{})
LogInfo(message string, args ...any)
LogError(message string, args ...any)
LogDebug(message string, args ...any)
LogWarning(message string, args ...any)
}
// Configuration interface defines quest system configuration

View File

@ -32,7 +32,7 @@ func NewRaceIntegration() *RaceIntegration {
}
// ValidateEntityRace validates an entity's race and provides detailed information
func (ri *RaceIntegration) ValidateEntityRace(entity RaceAware) (bool, string, map[string]interface{}) {
func (ri *RaceIntegration) ValidateEntityRace(entity RaceAware) (bool, string, map[string]any) {
raceID := entity.GetRace()
if !ri.races.IsValidRaceID(raceID) {
@ -63,8 +63,8 @@ func (ri *RaceIntegration) ApplyRacialBonuses(entity RaceAware, stats map[string
}
// GetEntityRaceInfo returns comprehensive race information for an entity
func (ri *RaceIntegration) GetEntityRaceInfo(entity EntityWithRace) map[string]interface{} {
info := make(map[string]interface{})
func (ri *RaceIntegration) GetEntityRaceInfo(entity EntityWithRace) map[string]any {
info := make(map[string]any)
// Basic entity info
info["entity_id"] = entity.GetID()
@ -230,12 +230,12 @@ func (ri *RaceIntegration) GetRaceStartingStats(raceID int8) map[string]int16 {
}
// CreateRaceSpecificEntity creates entity data with race-specific properties
func (ri *RaceIntegration) CreateRaceSpecificEntity(raceID int8) map[string]interface{} {
func (ri *RaceIntegration) CreateRaceSpecificEntity(raceID int8) map[string]any {
if !ri.races.IsValidRaceID(raceID) {
return nil
}
entityData := make(map[string]interface{})
entityData := make(map[string]any)
// Basic race info
entityData["race_id"] = raceID
@ -255,15 +255,15 @@ func (ri *RaceIntegration) CreateRaceSpecificEntity(raceID int8) map[string]inte
}
// GetRaceSelectionData returns data for race selection UI
func (ri *RaceIntegration) GetRaceSelectionData() map[string]interface{} {
data := make(map[string]interface{})
func (ri *RaceIntegration) GetRaceSelectionData() map[string]any {
data := make(map[string]any)
// All available races
allRaces := ri.races.GetAllRaces()
raceList := make([]map[string]interface{}, 0, len(allRaces))
raceList := make([]map[string]any, 0, len(allRaces))
for raceID, friendlyName := range allRaces {
raceData := map[string]interface{}{
raceData := map[string]any{
"id": raceID,
"name": friendlyName,
"alignment": ri.races.GetRaceAlignment(raceID),

View File

@ -267,8 +267,8 @@ func (rm *RaceManager) handleSearchCommand(args []string) (string, error) {
}
// ValidateEntityRaces validates races for a collection of entities
func (rm *RaceManager) ValidateEntityRaces(entities []RaceAware) map[string]interface{} {
validationResults := make(map[string]interface{})
func (rm *RaceManager) ValidateEntityRaces(entities []RaceAware) map[string]any {
validationResults := make(map[string]any)
validCount := 0
invalidCount := 0
@ -288,11 +288,11 @@ func (rm *RaceManager) ValidateEntityRaces(entities []RaceAware) map[string]inte
// Track invalid entities
if !isValid {
if validationResults["invalid_entities"] == nil {
validationResults["invalid_entities"] = make([]map[string]interface{}, 0)
validationResults["invalid_entities"] = make([]map[string]any, 0)
}
invalidList := validationResults["invalid_entities"].([]map[string]interface{})
invalidList = append(invalidList, map[string]interface{}{
invalidList := validationResults["invalid_entities"].([]map[string]any)
invalidList = append(invalidList, map[string]any{
"index": i,
"race_id": raceID,
})
@ -309,7 +309,7 @@ func (rm *RaceManager) ValidateEntityRaces(entities []RaceAware) map[string]inte
}
// GetRaceRecommendations returns race recommendations for character creation
func (rm *RaceManager) GetRaceRecommendations(preferences map[string]interface{}) []int8 {
func (rm *RaceManager) GetRaceRecommendations(preferences map[string]any) []int8 {
recommendations := make([]int8, 0)
// Check for alignment preference

View File

@ -351,11 +351,11 @@ func (r *Races) GetEvilRaceCount() int {
}
// GetRaceInfo returns comprehensive information about a race
func (r *Races) GetRaceInfo(raceID int8) map[string]interface{} {
func (r *Races) GetRaceInfo(raceID int8) map[string]any {
r.mutex.RLock()
defer r.mutex.RUnlock()
info := make(map[string]interface{})
info := make(map[string]any)
if !r.IsValidRaceID(raceID) {
info["valid"] = false

View File

@ -321,8 +321,8 @@ func (ru *RaceUtils) GetRaceAliases(raceID int8) []string {
}
// GetRaceStatistics returns statistics about the race system
func (ru *RaceUtils) GetRaceStatistics() map[string]interface{} {
stats := make(map[string]interface{})
func (ru *RaceUtils) GetRaceStatistics() map[string]any {
stats := make(map[string]any)
stats["total_races"] = ru.races.GetRaceCount()
stats["good_races"] = ru.races.GetGoodRaceCount()

View File

@ -320,11 +320,11 @@ func (r *Recipe) GetComponentQuantityForSlot(slot int8) int16 {
}
// GetInfo returns comprehensive information about the recipe
func (r *Recipe) GetInfo() map[string]interface{} {
func (r *Recipe) GetInfo() map[string]any {
r.mutex.RLock()
defer r.mutex.RUnlock()
info := make(map[string]interface{})
info := make(map[string]any)
info["id"] = r.ID
info["soe_id"] = r.SoeID

View File

@ -87,8 +87,8 @@ type RuleValidator interface {
// ValidationRule defines validation criteria for a rule
type ValidationRule struct {
Required bool // Whether the rule is required
MinValue interface{} // Minimum allowed value (for numeric types)
MaxValue interface{} // Maximum allowed value (for numeric types)
MinValue any // Minimum allowed value (for numeric types)
MaxValue any // Maximum allowed value (for numeric types)
ValidValues []string // List of valid string values
Pattern string // Regex pattern for validation
Description string // Description of the rule

View File

@ -21,7 +21,7 @@ type Client interface {
GetCurrentZone() Zone
SetTemporaryTransportID(id int32)
SimpleMessage(channel int32, message string)
Message(channel int32, format string, args ...interface{})
Message(channel int32, format string, args ...any)
CheckZoneAccess(zoneName string) bool
TryZoneInstance(zoneID int32, useDefaults bool) bool
Zone(zoneName string, useDefaults bool) error
@ -66,10 +66,10 @@ type EntityCommand struct {
// Logger interface for sign logging
type Logger interface {
LogInfo(message string, args ...interface{})
LogError(message string, args ...interface{})
LogDebug(message string, args ...interface{})
LogWarning(message string, args ...interface{})
LogInfo(message string, args ...any)
LogError(message string, args ...any)
LogDebug(message string, args ...any)
LogWarning(message string, args ...any)
}
// SignSpawn provides sign functionality for spawn entities

View File

@ -265,11 +265,11 @@ func (m *Manager) HandleSignUse(sign *Sign, client Client, command string) error
}
// GetStatistics returns sign system statistics
func (m *Manager) GetStatistics() map[string]interface{} {
func (m *Manager) GetStatistics() map[string]any {
m.mutex.RLock()
defer m.mutex.RUnlock()
stats := make(map[string]interface{})
stats := make(map[string]any)
stats["total_signs"] = m.totalSigns
stats["sign_interactions"] = m.signInteractions
stats["zone_transports"] = m.zoneTransports

View File

@ -27,9 +27,9 @@ type Database interface {
// Logger interface for skill system logging
type Logger interface {
LogInfo(message string, args ...interface{})
LogError(message string, args ...interface{})
LogDebug(message string, args ...interface{})
LogInfo(message string, args ...any)
LogError(message string, args ...any)
LogDebug(message string, args ...any)
}
// EntitySkillAdapter provides skill functionality for entities

View File

@ -73,11 +73,11 @@ func (m *Manager) RecordSkillUp(skillID int32, skillType int32) {
}
// GetStatistics returns skill system statistics
func (m *Manager) GetStatistics() map[string]interface{} {
func (m *Manager) GetStatistics() map[string]any {
m.mutex.RLock()
defer m.mutex.RUnlock()
stats := make(map[string]interface{})
stats := make(map[string]any)
stats["total_skill_ups"] = m.totalSkillUps
stats["players_with_skills"] = m.playersWithSkills
stats["total_skills_in_master"] = m.masterSkillList.GetSkillCount()

View File

@ -1074,7 +1074,7 @@ func (s *Spawn) SetBasicInfo(info *BasicInfoStruct) {
}
// GetClient returns the client associated with this spawn (overridden by Player)
func (s *Spawn) GetClient() interface{} {
func (s *Spawn) GetClient() any {
return nil // Base spawns have no client
}

View File

@ -555,13 +555,13 @@ func (sm *SpellManager) CanCastSpell(casterID, targetID, spellID int32) (bool, s
}
// GetSpellInfo returns comprehensive information about a spell
func (sm *SpellManager) GetSpellInfo(spellID int32) map[string]interface{} {
func (sm *SpellManager) GetSpellInfo(spellID int32) map[string]any {
spell := sm.masterList.GetSpell(spellID)
if spell == nil {
return nil
}
info := make(map[string]interface{})
info := make(map[string]any)
// Basic spell info
info["id"] = spell.GetSpellID()

View File

@ -416,8 +416,8 @@ func (st *SpellTargeting) RequiresTarget(spell *Spell) bool {
}
// GetTargetingInfo returns information about spell targeting requirements
func (st *SpellTargeting) GetTargetingInfo(spell *Spell) map[string]interface{} {
info := make(map[string]interface{})
func (st *SpellTargeting) GetTargetingInfo(spell *Spell) map[string]any {
info := make(map[string]any)
if spell == nil {
return info

View File

@ -487,7 +487,7 @@ func (mtl *MasterTitlesList) SaveToDatabase(db *database.DB) error {
// Insert all current titles
for _, title := range mtl.titles {
var achievementID interface{}
var achievementID any
if title.AchievementID != 0 {
achievementID = title.AchievementID
}

View File

@ -499,12 +499,12 @@ func (ptl *PlayerTitlesList) SaveToDatabase(db *database.DB) error {
// Insert all current titles
for _, playerTitle := range ptl.titles {
var achievementID interface{}
var achievementID any
if playerTitle.AchievementID != 0 {
achievementID = playerTitle.AchievementID
}
var expirationDate interface{}
var expirationDate any
if !playerTitle.ExpiresAt.IsZero() {
expirationDate = playerTitle.ExpiresAt.Unix()
}

View File

@ -319,11 +319,11 @@ func (tm *TitleManager) cleanupExpiredTitles() {
}
// GetStatistics returns title system statistics
func (tm *TitleManager) GetStatistics() map[string]interface{} {
func (tm *TitleManager) GetStatistics() map[string]any {
tm.mutex.RLock()
defer tm.mutex.RUnlock()
return map[string]interface{}{
return map[string]any{
"total_titles": tm.masterList.GetTitleCount(),
"total_players": len(tm.playerLists),
"titles_granted": tm.totalTitlesGranted,

View File

@ -146,7 +146,7 @@ func (ts *TradeService) CancelTrade(entityID int32) error {
}
// GetTradeInfo returns comprehensive information about a trade
func (ts *TradeService) GetTradeInfo(entityID int32) (map[string]interface{}, error) {
func (ts *TradeService) GetTradeInfo(entityID int32) (map[string]any, error) {
trade := ts.tradeManager.GetTrade(entityID)
if trade == nil {
return nil, fmt.Errorf("entity is not in a trade")
@ -174,8 +174,8 @@ func (ts *TradeService) ProcessTrades() {
}
// GetTradeStatistics returns statistics about trade activity
func (ts *TradeService) GetTradeStatistics() map[string]interface{} {
stats := make(map[string]interface{})
func (ts *TradeService) GetTradeStatistics() map[string]any {
stats := make(map[string]any)
stats["active_trades"] = ts.tradeManager.GetActiveTradeCount()
stats["max_trade_duration_minutes"] = ts.maxTradeDuration.Minutes()

View File

@ -392,11 +392,11 @@ func (t *Trade) GetTraderSlot(entityID int32, slot int8) Item {
}
// GetTradeInfo returns comprehensive information about the trade
func (t *Trade) GetTradeInfo() map[string]interface{} {
func (t *Trade) GetTradeInfo() map[string]any {
t.mutex.RLock()
defer t.mutex.RUnlock()
info := make(map[string]interface{})
info := make(map[string]any)
info["state"] = t.state
info["start_time"] = t.startTime
info["trader1_id"] = t.trader1.EntityID

View File

@ -127,7 +127,7 @@ func IsValidTradeState(state TradeState, operation string) bool {
}
// GenerateTradeLogEntry creates a log entry for trade operations
func GenerateTradeLogEntry(tradeID string, operation string, entityID int32, details interface{}) string {
func GenerateTradeLogEntry(tradeID string, operation string, entityID int32, details any) string {
return fmt.Sprintf("[Trade:%s] %s by entity %d: %v", tradeID, operation, entityID, details)
}
@ -147,8 +147,8 @@ func CompareTradeItems(item1, item2 TradeItemInfo) bool {
// CalculateTradeValue estimates the total value of items and coins in a trade
// This is a helper function for trade balancing and analysis
func CalculateTradeValue(participant *TradeParticipant) map[string]interface{} {
value := make(map[string]interface{})
func CalculateTradeValue(participant *TradeParticipant) map[string]any {
value := make(map[string]any)
// Add coin value
value["coins"] = participant.Coins
@ -159,9 +159,9 @@ func CalculateTradeValue(participant *TradeParticipant) map[string]interface{} {
value["item_count"] = itemCount
if itemCount > 0 {
items := make([]map[string]interface{}, 0, itemCount)
items := make([]map[string]any, 0, itemCount)
for slot, itemInfo := range participant.Items {
itemData := make(map[string]interface{})
itemData := make(map[string]any)
itemData["slot"] = slot
itemData["quantity"] = itemInfo.Quantity
if itemInfo.Item != nil {

View File

@ -596,11 +596,11 @@ func (tsa *TradeskillSystemAdapter) updatePlayerRecipeStage(currentStage int8, c
}
// GetSystemStats returns comprehensive statistics about the tradeskill system.
func (tsa *TradeskillSystemAdapter) GetSystemStats() map[string]interface{} {
func (tsa *TradeskillSystemAdapter) GetSystemStats() map[string]any {
managerStats := tsa.manager.GetStats()
eventsStats := tsa.eventsList.GetStats()
return map[string]interface{}{
return map[string]any{
"active_sessions": managerStats.ActiveSessions,
"recent_completions": managerStats.RecentCompletions,
"average_session_time": managerStats.AverageSessionTime,

View File

@ -348,7 +348,7 @@ func (tsa *TraitSystemAdapter) IsPlayerAllowedTrait(playerID uint32, spellID uin
}
// GetPlayerTraitStats returns statistics about a player's trait selections.
func (tsa *TraitSystemAdapter) GetPlayerTraitStats(playerID uint32) (map[string]interface{}, error) {
func (tsa *TraitSystemAdapter) GetPlayerTraitStats(playerID uint32) (map[string]any, error) {
// Get player information
player, err := tsa.playerManager.GetPlayer(playerID)
if err != nil {
@ -381,7 +381,7 @@ func (tsa *TraitSystemAdapter) GetPlayerTraitStats(playerID uint32) (map[string]
tsa.masterList.getSpellCount(playerState, playerState.TraitLists.InnateRaceTraits, false)
focusEffects := tsa.masterList.getSpellCount(playerState, playerState.TraitLists.FocusEffects, false)
return map[string]interface{}{
return map[string]any{
"player_id": playerID,
"level": playerState.Level,
"character_traits": characterTraits,
@ -394,11 +394,11 @@ func (tsa *TraitSystemAdapter) GetPlayerTraitStats(playerID uint32) (map[string]
}
// GetSystemStats returns comprehensive statistics about the trait system.
func (tsa *TraitSystemAdapter) GetSystemStats() map[string]interface{} {
func (tsa *TraitSystemAdapter) GetSystemStats() map[string]any {
masterStats := tsa.masterList.GetStats()
managerStats := tsa.traitManager.GetManagerStats()
return map[string]interface{}{
return map[string]any{
"total_traits": masterStats.TotalTraits,
"traits_by_type": masterStats.TraitsByType,
"traits_by_group": masterStats.TraitsByGroup,

View File

@ -325,7 +325,7 @@ func (mtl *MasterTraitList) getClassicAvailability(levelLimits []int16, totalUse
}
// getSpellCount counts how many spells from a trait map the player has selected.
func (mtl *MasterTraitList) getSpellCount(playerState *PlayerTraitState, traitMap interface{}, onlyCharTraits bool) int16 {
func (mtl *MasterTraitList) getSpellCount(playerState *PlayerTraitState, traitMap any, onlyCharTraits bool) int16 {
count := int16(0)
switch tm := traitMap.(type) {

View File

@ -94,11 +94,11 @@ func (m *Manager) ReloadTransmutingTiers() error {
}
// GetStatistics returns transmutation statistics
func (m *Manager) GetStatistics() map[string]interface{} {
func (m *Manager) GetStatistics() map[string]any {
m.mutex.RLock()
defer m.mutex.RUnlock()
stats := make(map[string]interface{})
stats := make(map[string]any)
stats["total_transmutes"] = m.totalTransmutes
stats["successful_transmutes"] = m.successfulTransmutes
stats["failed_transmutes"] = m.failedTransmutes

View File

@ -71,7 +71,7 @@ type Client interface {
SetTransmuteID(id int32)
QueuePacket(packet []byte)
SimpleMessage(channel int32, message string)
Message(channel int32, format string, args ...interface{})
Message(channel int32, format string, args ...any)
AddItem(item Item, itemDeleted *bool) error
}

View File

@ -8,7 +8,7 @@ import (
type ClientInterface interface {
GetPlayer() *spawn.Spawn
SetTemporaryTransportID(id int32)
ProcessTeleport(widget *Widget, destinations []interface{}, transporterID int32)
ProcessTeleport(widget *Widget, destinations []any, transporterID int32)
GetVersion() int32
GetCurrentZone() ZoneInterface
}
@ -21,8 +21,8 @@ type ZoneInterface interface {
PlaySoundFile(unknown int32, soundFile string, x, y, z float32)
CallSpawnScript(s *spawn.Spawn, scriptType string, caller *spawn.Spawn, extra string, state bool) bool
GetSpawnByDatabaseID(id int32) *spawn.Spawn
GetTransporters(client ClientInterface, transporterID int32) []interface{}
ProcessEntityCommand(command interface{}, player *spawn.Spawn, target *spawn.Spawn)
GetTransporters(client ClientInterface, transporterID int32) []any
ProcessEntityCommand(command any, player *spawn.Spawn, target *spawn.Spawn)
GetInstanceID() int32
GetInstanceType() int32
SendHouseItems(client ClientInterface)

View File

@ -295,11 +295,11 @@ func (m *Manager) Clear() {
}
// GetStatistics returns widget statistics
func (m *Manager) GetStatistics() map[string]interface{} {
func (m *Manager) GetStatistics() map[string]any {
m.mutex.RLock()
defer m.mutex.RUnlock()
stats := make(map[string]interface{})
stats := make(map[string]any)
stats["total_widgets"] = len(m.widgets)
stats["door_count"] = len(m.GetDoorWidgets())
stats["lift_count"] = len(m.GetLiftWidgets())

View File

@ -97,8 +97,8 @@ type NPC interface {
SetRespawnTime(seconds int32)
GetSpawnGroupID() int32
SetSpawnGroupID(groupID int32)
GetRandomizedFeatures() map[string]interface{}
SetRandomizedFeatures(features map[string]interface{})
GetRandomizedFeatures() map[string]any
SetRandomizedFeatures(features map[string]any)
}
// Object interface represents an interactive world object