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 // Custom packet handler
type MyAAPacketHandler struct{} 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 // Build AA list packet for client
return []byte{}, nil 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 // Send AA update to client
return nil 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 // Handle AA purchase from client
return nil return nil
} }

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -164,7 +164,7 @@ func (am *AAManager) GetPlayerAAState(characterID int32) (*AAPlayerState, error)
// Need to load from database, use write lock to prevent race condition // Need to load from database, use write lock to prevent race condition
am.statesMutex.Lock() am.statesMutex.Lock()
defer am.statesMutex.Unlock() defer am.statesMutex.Unlock()
// Double-check pattern: another goroutine might have loaded it while we waited // Double-check pattern: another goroutine might have loaded it while we waited
if playerState, exists := am.playerStates[characterID]; exists { if playerState, exists := am.playerStates[characterID]; exists {
return playerState, nil return playerState, nil
@ -456,16 +456,16 @@ func (am *AAManager) GetSystemStats() *AAManagerStats {
} }
// GetPlayerStats returns player-specific statistics // 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) playerState := am.getPlayerState(characterID)
if playerState == nil { if playerState == nil {
return map[string]interface{}{"error": "player not found"} return map[string]any{"error": "player not found"}
} }
playerState.mutex.RLock() playerState.mutex.RLock()
defer playerState.mutex.RUnlock() defer playerState.mutex.RUnlock()
return map[string]interface{}{ return map[string]any{
"character_id": characterID, "character_id": characterID,
"total_points": playerState.TotalPoints, "total_points": playerState.TotalPoints,
"spent_points": playerState.SpentPoints, "spent_points": playerState.SpentPoints,

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -15,10 +15,10 @@ type Database interface {
// Logger interface for appearance logging // Logger interface for appearance logging
type Logger interface { type Logger interface {
LogInfo(message string, args ...interface{}) LogInfo(message string, args ...any)
LogError(message string, args ...interface{}) LogError(message string, args ...any)
LogDebug(message string, args ...interface{}) LogDebug(message string, args ...any)
LogWarning(message string, args ...interface{}) LogWarning(message string, args ...any)
} }
// AppearanceProvider interface for entities that provide appearances // 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 // GetStatistics returns appearance system statistics
func (m *Manager) GetStatistics() map[string]interface{} { func (m *Manager) GetStatistics() map[string]any {
m.mutex.RLock() m.mutex.RLock()
defer m.mutex.RUnlock() defer m.mutex.RUnlock()

View File

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

View File

@ -1185,7 +1185,7 @@ func TestGetClassSelectionData(t *testing.T) {
t.Error("Selection data should include statistics") 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 { if len(adventureClasses) == 0 {
t.Error("Selection data should include adventure classes") t.Error("Selection data should include adventure classes")
} }
@ -1442,7 +1442,7 @@ func TestGetClassRecommendations(t *testing.T) {
manager := NewClassManager() manager := NewClassManager()
// Test recommendations by class type // Test recommendations by class type
preferences := map[string]interface{}{ preferences := map[string]any{
"class_type": ClassTypeAdventure, "class_type": ClassTypeAdventure,
} }
@ -1459,7 +1459,7 @@ func TestGetClassRecommendations(t *testing.T) {
} }
// Test recommendations by base class // Test recommendations by base class
basePreferences := map[string]interface{}{ basePreferences := map[string]any{
"base_class": ClassFighter, "base_class": ClassFighter,
} }
@ -1469,7 +1469,7 @@ func TestGetClassRecommendations(t *testing.T) {
} }
// Test recommendations by preferred stats // Test recommendations by preferred stats
statPreferences := map[string]interface{}{ statPreferences := map[string]any{
"preferred_stats": []string{"strength", "stamina"}, "preferred_stats": []string{"strength", "stamina"},
} }
@ -1479,7 +1479,7 @@ func TestGetClassRecommendations(t *testing.T) {
} }
// Test empty preferences (should get defaults) // Test empty preferences (should get defaults)
emptyPreferences := map[string]interface{}{} emptyPreferences := map[string]any{}
defaultRecommendations := manager.GetClassRecommendations(emptyPreferences) defaultRecommendations := manager.GetClassRecommendations(emptyPreferences)
if len(defaultRecommendations) == 0 { if len(defaultRecommendations) == 0 {
t.Error("Should get default recommendations when no preferences given") 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 // 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() classID := entity.GetClass()
if !ci.classes.IsValidClassID(classID) { 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 // GetEntityClassInfo returns comprehensive class information for an entity
func (ci *ClassIntegration) GetEntityClassInfo(entity EntityWithClass) map[string]interface{} { func (ci *ClassIntegration) GetEntityClassInfo(entity EntityWithClass) map[string]any {
info := make(map[string]interface{}) info := make(map[string]any)
// Basic entity info // Basic entity info
info["entity_id"] = entity.GetID() 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 // 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) { if !ci.classes.IsValidClassID(classID) {
return nil return nil
} }
entityData := make(map[string]interface{}) entityData := make(map[string]any)
// Basic class info // Basic class info
entityData["class_id"] = classID entityData["class_id"] = classID
@ -310,16 +310,16 @@ func (ci *ClassIntegration) CreateClassSpecificEntity(classID int8) map[string]i
} }
// GetClassSelectionData returns data for class selection UI // GetClassSelectionData returns data for class selection UI
func (ci *ClassIntegration) GetClassSelectionData() map[string]interface{} { func (ci *ClassIntegration) GetClassSelectionData() map[string]any {
data := make(map[string]interface{}) data := make(map[string]any)
// All available adventure classes (exclude tradeskill for character creation) // All available adventure classes (exclude tradeskill for character creation)
allClasses := ci.classes.GetAllClasses() allClasses := ci.classes.GetAllClasses()
adventureClasses := make([]map[string]interface{}, 0) adventureClasses := make([]map[string]any, 0)
for classID, displayName := range allClasses { for classID, displayName := range allClasses {
if ci.classes.IsAdventureClass(classID) { if ci.classes.IsAdventureClass(classID) {
classData := map[string]interface{}{ classData := map[string]any{
"id": classID, "id": classID,
"name": displayName, "name": displayName,
"type": ci.classes.GetClassType(classID), "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 // ValidateEntityClasses validates classes for a collection of entities
func (cm *ClassManager) ValidateEntityClasses(entities []ClassAware) map[string]interface{} { func (cm *ClassManager) ValidateEntityClasses(entities []ClassAware) map[string]any {
validationResults := make(map[string]interface{}) validationResults := make(map[string]any)
validCount := 0 validCount := 0
invalidCount := 0 invalidCount := 0
@ -351,11 +351,11 @@ func (cm *ClassManager) ValidateEntityClasses(entities []ClassAware) map[string]
// Track invalid entities // Track invalid entities
if !isValid { if !isValid {
if validationResults["invalid_entities"] == nil { 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 := validationResults["invalid_entities"].([]map[string]any)
invalidList = append(invalidList, map[string]interface{}{ invalidList = append(invalidList, map[string]any{
"index": i, "index": i,
"class_id": classID, "class_id": classID,
}) })
@ -372,7 +372,7 @@ func (cm *ClassManager) ValidateEntityClasses(entities []ClassAware) map[string]
} }
// GetClassRecommendations returns class recommendations for character creation // 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) recommendations := make([]int8, 0)
// Check for class type preference // 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 // GetClassStatistics returns statistics about the class system
func (cu *ClassUtils) GetClassStatistics() map[string]interface{} { func (cu *ClassUtils) GetClassStatistics() map[string]any {
stats := make(map[string]interface{}) stats := make(map[string]any)
allClasses := cu.classes.GetAllClasses() allClasses := cu.classes.GetAllClasses()
stats["total_classes"] = len(allClasses) stats["total_classes"] = len(allClasses)

View File

@ -163,14 +163,14 @@ type CollectionEventHandler interface {
// LogHandler provides logging functionality // LogHandler provides logging functionality
type LogHandler interface { type LogHandler interface {
// LogDebug logs debug messages // LogDebug logs debug messages
LogDebug(category, message string, args ...interface{}) LogDebug(category, message string, args ...any)
// LogInfo logs informational messages // LogInfo logs informational messages
LogInfo(category, message string, args ...interface{}) LogInfo(category, message string, args ...any)
// LogError logs error messages // LogError logs error messages
LogError(category, message string, args ...interface{}) LogError(category, message string, args ...any)
// LogWarning logs warning messages // 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) // GetClient returns the client for this entity (overridden by Player)
func (e *Entity) GetClient() interface{} { func (e *Entity) GetClient() any {
return nil return nil
} }

View File

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

View File

@ -237,11 +237,11 @@ func (m *Manager) RecordFactionDecrease(factionID int32) {
} }
// GetStatistics returns faction system statistics // GetStatistics returns faction system statistics
func (m *Manager) GetStatistics() map[string]interface{} { func (m *Manager) GetStatistics() map[string]any {
m.mutex.RLock() m.mutex.RLock()
defer m.mutex.RUnlock() defer m.mutex.RUnlock()
stats := make(map[string]interface{}) stats := make(map[string]any)
stats["total_factions"] = m.masterFactionList.GetFactionCount() stats["total_factions"] = m.masterFactionList.GetFactionCount()
stats["total_faction_changes"] = m.totalFactionChanges stats["total_faction_changes"] = m.totalFactionChanges
stats["faction_increases"] = m.factionIncreases 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 // Configure base spawn properties
gs.SetName(config.Name) gs.SetName(config.Name)
gs.SetSpawnType(DefaultSpawnType) gs.SetSpawnType(DefaultSpawnType)
gs.SetDifficulty(DefaultDifficulty) // Note: SetDifficulty and SetState methods not available in spawn interface
gs.SetState(DefaultState)
// Set position // Set position
gs.SetX(config.Location.X) gs.SetX(config.Location.X)
gs.SetY(config.Location.Y) gs.SetY(config.Location.Y, false)
gs.SetZ(config.Location.Z) gs.SetZ(config.Location.Z)
if config.RandomizeHeading { 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 { } else {
gs.SetHeading(config.Location.Heading) heading := int16(config.Location.Heading)
gs.SetHeading(heading, heading)
} }
return gs return gs
@ -47,12 +49,14 @@ func (gs *GroundSpawn) Copy() *GroundSpawn {
defer gs.harvestMutex.Unlock() defer gs.harvestMutex.Unlock()
newSpawn := &GroundSpawn{ newSpawn := &GroundSpawn{
Spawn: gs.Spawn.Copy().(*spawn.Spawn), Spawn: gs.Spawn, // TODO: Implement proper copy when spawn.Copy() is available
numberHarvests: gs.numberHarvests, numberHarvests: gs.numberHarvests,
numAttemptsPerHarvest: gs.numAttemptsPerHarvest, numAttemptsPerHarvest: gs.numAttemptsPerHarvest,
groundspawnID: gs.groundspawnID, groundspawnID: gs.groundspawnID,
collectionSkill: gs.collectionSkill, collectionSkill: gs.collectionSkill,
randomizeHeading: gs.randomizeHeading, 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 return newSpawn
@ -252,14 +256,14 @@ func (gs *GroundSpawn) ProcessHarvest(context *HarvestContext) (*HarvestResult,
} }
// Validate harvest data // Validate harvest data
if context.GroundSpawnEntries == nil || len(context.GroundSpawnEntries) == 0 { if len(context.GroundSpawnEntries) == 0 {
return &HarvestResult{ return &HarvestResult{
Success: false, Success: false,
MessageText: fmt.Sprintf("Error: No groundspawn entries assigned to groundspawn id: %d", gs.groundspawnID), MessageText: fmt.Sprintf("Error: No groundspawn entries assigned to groundspawn id: %d", gs.groundspawnID),
}, nil }, nil
} }
if context.GroundSpawnItems == nil || len(context.GroundSpawnItems) == 0 { if len(context.GroundSpawnItems) == 0 {
return &HarvestResult{ return &HarvestResult{
Success: false, Success: false,
MessageText: fmt.Sprintf("Error: No groundspawn items assigned to groundspawn id: %d", gs.groundspawnID), 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 // Check level requirement for bonus tables
if entry.BonusTable && context.Player.GetLevel() < entry.MinAdventureLevel { if entry.BonusTable && (*context.Player).GetLevel() < entry.MinAdventureLevel {
continue continue
} }
@ -441,9 +445,9 @@ func (gs *GroundSpawn) awardHarvestItems(harvestType int8, availableItems []*Gro
var items []*HarvestedItem var items []*HarvestedItem
// Filter items based on harvest type and player location // Filter items based on harvest type and player location
normalItems := gs.filterItems(availableItems, ItemRarityNormal, player.GetLocation()) normalItems := gs.filterItems(availableItems, ItemRarityNormal, (*player).GetLocation())
rareItems := gs.filterItems(availableItems, ItemRarityRare, player.GetLocation()) rareItems := gs.filterItems(availableItems, ItemRarityRare, (*player).GetLocation())
imbueItems := gs.filterItems(availableItems, ItemRarityImbue, player.GetLocation()) imbueItems := gs.filterItems(availableItems, ItemRarityImbue, (*player).GetLocation())
switch harvestType { switch harvestType {
case HarvestType1Item: 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 // Check if player skill is already at max for this node
maxSkillAllowed := int16(float32(context.MaxSkillRequired) * 1.0) // TODO: Use skill multiplier rule maxSkillAllowed := int16(float32(context.MaxSkillRequired) * 1.0) // TODO: Use skill multiplier rule
if context.PlayerSkill.GetCurrentValue() >= maxSkillAllowed { if (*context.PlayerSkill).GetCurrentValue() >= maxSkillAllowed {
return false return false
} }
@ -579,7 +583,7 @@ func (gs *GroundSpawn) handleHarvestUse(client Client) error {
if client.GetLogger() != nil { if client.GetLogger() != nil {
client.GetLogger().LogDebug("Player %s attempting to harvest %s using spell %s", 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 return nil
@ -595,7 +599,7 @@ func (gs *GroundSpawn) handleCommandUse(client Client, command string) error {
if client.GetLogger() != nil { if client.GetLogger() != nil {
client.GetLogger().LogDebug("Player %s using command %s on %s", client.GetLogger().LogDebug("Player %s using command %s on %s",
client.GetPlayer().GetName(), command, gs.GetName()) (*client.GetPlayer()).GetName(), command, gs.GetName())
} }
return nil return nil
@ -603,8 +607,9 @@ func (gs *GroundSpawn) handleCommandUse(client Client, command string) error {
// Serialize creates a packet representation of the ground spawn // Serialize creates a packet representation of the ground spawn
func (gs *GroundSpawn) Serialize(player *Player, version int16) ([]byte, error) { func (gs *GroundSpawn) Serialize(player *Player, version int16) ([]byte, error) {
// Use base spawn serialization // TODO: Implement proper ground spawn serialization when spawn.Serialize is available
return gs.Spawn.Serialize(player, version) // For now, return empty packet as placeholder
return make([]byte, 0), nil
} }
// Respawn resets the ground spawn to harvestable state // Respawn resets the ground spawn to harvestable state
@ -617,9 +622,24 @@ func (gs *GroundSpawn) Respawn() {
// Randomize heading if configured // Randomize heading if configured
if gs.randomizeHeading { if gs.randomizeHeading {
gs.SetHeading(rand.Float32() * 360.0) heading := int16(rand.Float32() * 360.0)
gs.SetHeading(heading, heading)
} }
// Mark as alive // Mark as alive
gs.SetAlive(true) 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 // Logger interface for ground spawn logging
type Logger interface { type Logger interface {
LogInfo(message string, args ...interface{}) LogInfo(message string, args ...any)
LogError(message string, args ...interface{}) LogError(message string, args ...any)
LogDebug(message string, args ...interface{}) LogDebug(message string, args ...any)
LogWarning(message string, args ...interface{}) LogWarning(message string, args ...any)
} }
// Player interface for ground spawn interactions // Player interface for ground spawn interactions
@ -35,7 +35,7 @@ type Client interface {
GetVersion() int16 GetVersion() int16
GetLogger() Logger GetLogger() Logger
GetCurrentZoneID() int32 GetCurrentZoneID() int32
Message(channel int32, message string, args ...interface{}) Message(channel int32, message string, args ...any)
SimpleMessage(channel int32, message string) SimpleMessage(channel int32, message string)
SendPopupMessage(type_ int32, message string, sound string, duration float32, r, g, b int32) SendPopupMessage(type_ int32, message string, sound string, duration float32, r, g, b int32)
AddItem(item *Item, itemDeleted *bool) error AddItem(item *Item, itemDeleted *bool) error
@ -126,8 +126,8 @@ type SkillProvider interface {
// SpawnProvider interface for spawn system integration // SpawnProvider interface for spawn system integration
type SpawnProvider interface { type SpawnProvider interface {
CreateSpawn() interface{} CreateSpawn() any
GetSpawn(id int32) interface{} GetSpawn(id int32) any
RegisterGroundSpawn(gs *GroundSpawn) error RegisterGroundSpawn(gs *GroundSpawn) error
UnregisterGroundSpawn(id int32) error UnregisterGroundSpawn(id int32) error
} }
@ -168,7 +168,7 @@ func (pgsa *PlayerGroundSpawnAdapter) CanHarvest(gs *GroundSpawn) bool {
} }
// Check if player has required skill // Check if player has required skill
skill := pgsa.player.GetSkillByName(gs.GetCollectionSkill()) skill := (*pgsa.player).GetSkillByName(gs.GetCollectionSkill())
if skill == nil { if skill == nil {
return false return false
} }
@ -184,7 +184,7 @@ func (pgsa *PlayerGroundSpawnAdapter) GetHarvestSkill(skillName string) *Skill {
return nil return nil
} }
return pgsa.player.GetSkillByName(skillName) return (*pgsa.player).GetSkillByName(skillName)
} }
// GetHarvestModifiers returns harvest modifiers for the player // 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 result.Success && len(result.ItemsAwarded) > 0 {
if pgsa.logger != nil { if pgsa.logger != nil {
pgsa.logger.LogDebug("Player %s successfully harvested %d items", 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 // 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 { if hea.handler == nil {
return return
} }

View File

@ -13,8 +13,11 @@ func NewManager(database Database, logger Logger) *Manager {
entriesByID: make(map[int32][]*GroundSpawnEntry), entriesByID: make(map[int32][]*GroundSpawnEntry),
itemsByID: make(map[int32][]*GroundSpawnEntryItem), itemsByID: make(map[int32][]*GroundSpawnEntryItem),
respawnQueue: make(map[int32]time.Time), respawnQueue: make(map[int32]time.Time),
activeSpawns: make(map[int32]*GroundSpawn),
depletedSpawns: make(map[int32]*GroundSpawn),
database: database, database: database,
logger: logger, logger: logger,
nextSpawnID: 1,
harvestsBySkill: make(map[string]int64), harvestsBySkill: make(map[string]int64),
} }
} }
@ -41,8 +44,22 @@ func (m *Manager) Initialize() error {
m.mutex.Lock() m.mutex.Lock()
defer m.mutex.Unlock() defer m.mutex.Unlock()
var maxID int32
for _, gs := range groundSpawns { 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) // Group by zone (placeholder - zone ID would come from spawn location)
zoneID := int32(1) // TODO: Get actual zone ID from spawn zoneID := int32(1) // TODO: Get actual zone ID from spawn
@ -50,10 +67,13 @@ func (m *Manager) Initialize() error {
// Load harvest entries and items // Load harvest entries and items
if err := m.loadGroundSpawnData(gs); err != nil && m.logger != nil { 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 { if m.logger != nil {
m.logger.LogInfo("Loaded %d ground spawns from database", len(groundSpawns)) 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() m.mutex.Lock()
defer m.mutex.Unlock() defer m.mutex.Unlock()
// Generate ID (placeholder implementation) // Use efficient ID counter instead of len()
newID := int32(len(m.groundSpawns) + 1) newID := m.nextSpawnID
m.nextSpawnID++
gs.SetID(newID) gs.SetID(newID)
// Store ground spawn // Store ground spawn
m.groundSpawns[newID] = gs 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 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) m.spawnsByZone[zoneID] = append(m.spawnsByZone[zoneID], gs)
if m.logger != nil { if m.logger != nil {
@ -121,13 +150,13 @@ func (m *Manager) GetGroundSpawnsByZone(zoneID int32) []*GroundSpawn {
defer m.mutex.RUnlock() defer m.mutex.RUnlock()
spawns := m.spawnsByZone[zoneID] spawns := m.spawnsByZone[zoneID]
if spawns == nil { if len(spawns) == 0 {
return []*GroundSpawn{} return []*GroundSpawn{}
} }
// Return a copy to prevent external modification // Return a copy to prevent external modification - use append for better performance
result := make([]*GroundSpawn, len(spawns)) result := make([]*GroundSpawn, 0, len(spawns))
copy(result, spawns) result = append(result, spawns...)
return result return result
} }
@ -179,8 +208,11 @@ func (m *Manager) ProcessHarvest(gs *GroundSpawn, player *Player) (*HarvestResul
m.mutex.Unlock() m.mutex.Unlock()
} }
// Handle respawn if depleted // Handle respawn if depleted and update cache
if gs.IsDepleted() { if gs.IsDepleted() {
m.mutex.Lock()
m.updateSpawnStateCache(gs)
m.mutex.Unlock()
m.scheduleRespawn(gs) m.scheduleRespawn(gs)
} }
@ -196,11 +228,11 @@ func (m *Manager) buildHarvestContext(gs *GroundSpawn, player *Player) (*Harvest
items := m.itemsByID[groundspawnID] items := m.itemsByID[groundspawnID]
m.mutex.RUnlock() 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) 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) 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 skillName = SkillGathering // Collections use gathering skill
} }
playerSkill := player.GetSkillByName(skillName) playerSkill := (*player).GetSkillByName(skillName)
if playerSkill == nil { if playerSkill == nil {
return nil, fmt.Errorf("player lacks required skill: %s", skillName) return nil, fmt.Errorf("player lacks required skill: %s", skillName)
} }
// Calculate total skill (base + bonuses) // Calculate total skill (base + bonuses)
totalSkill := playerSkill.GetCurrentValue() totalSkill := (*playerSkill).GetCurrentValue()
// TODO: Add stat bonuses when stat system is integrated // TODO: Add stat bonuses when stat system is integrated
// Find max skill required // Find max skill required
@ -276,6 +308,12 @@ func (m *Manager) ProcessRespawns() {
for _, spawnID := range toRespawn { for _, spawnID := range toRespawn {
if gs := m.GetGroundSpawn(spawnID); gs != nil { if gs := m.GetGroundSpawn(spawnID); gs != nil {
gs.Respawn() gs.Respawn()
// Update cache after respawn
m.mutex.Lock()
m.updateSpawnStateCache(gs)
m.mutex.Unlock()
if m.logger != nil { if m.logger != nil {
m.logger.LogDebug("Ground spawn %d respawned", spawnID) 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.groundSpawns, id)
delete(m.respawnQueue, id) delete(m.respawnQueue, id)
delete(m.activeSpawns, id)
delete(m.depletedSpawns, id)
// Remove from zone list // Remove from zone list
// TODO: Get actual zone ID from ground spawn // TODO: Get actual zone ID from ground spawn
@ -401,11 +441,10 @@ func (m *Manager) GetActiveGroundSpawns() []*GroundSpawn {
m.mutex.RLock() m.mutex.RLock()
defer m.mutex.RUnlock() defer m.mutex.RUnlock()
var active []*GroundSpawn // Use cached active spawns for O(1) performance instead of O(N) iteration
for _, gs := range m.groundSpawns { active := make([]*GroundSpawn, 0, len(m.activeSpawns))
if gs.IsAvailable() { for _, gs := range m.activeSpawns {
active = append(active, gs) active = append(active, gs)
}
} }
return active return active
@ -416,16 +455,32 @@ func (m *Manager) GetDepletedGroundSpawns() []*GroundSpawn {
m.mutex.RLock() m.mutex.RLock()
defer m.mutex.RUnlock() defer m.mutex.RUnlock()
var depleted []*GroundSpawn // Use cached depleted spawns for O(1) performance instead of O(N) iteration
for _, gs := range m.groundSpawns { depleted := make([]*GroundSpawn, 0, len(m.depletedSpawns))
if gs.IsDepleted() { for _, gs := range m.depletedSpawns {
depleted = append(depleted, gs) depleted = append(depleted, gs)
}
} }
return depleted 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 // ProcessCommand handles ground spawn management commands
func (m *Manager) ProcessCommand(command string, args []string) (string, error) { func (m *Manager) ProcessCommand(command string, args []string) (string, error) {
switch command { switch command {
@ -536,7 +591,7 @@ func (m *Manager) handleInfoCommand(args []string) (string, error) {
return fmt.Sprintf("Ground spawn %d not found.", spawnID), nil 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("ID: %d\n", gs.GetID())
result += fmt.Sprintf("Name: %s\n", gs.GetName()) result += fmt.Sprintf("Name: %s\n", gs.GetName())
result += fmt.Sprintf("Collection Skill: %s\n", gs.GetCollectionSkill()) 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 // 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 { if m.database == nil {
return "", fmt.Errorf("no database available") 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.entriesByID = make(map[int32][]*GroundSpawnEntry)
m.itemsByID = make(map[int32][]*GroundSpawnEntryItem) m.itemsByID = make(map[int32][]*GroundSpawnEntryItem)
m.respawnQueue = make(map[int32]time.Time) 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() m.mutex.Unlock()
// Reload from database // Reload from database
@ -588,4 +646,7 @@ func (m *Manager) Shutdown() {
m.entriesByID = make(map[int32][]*GroundSpawnEntry) m.entriesByID = make(map[int32][]*GroundSpawnEntry)
m.itemsByID = make(map[int32][]*GroundSpawnEntryItem) m.itemsByID = make(map[int32][]*GroundSpawnEntryItem)
m.respawnQueue = make(map[int32]time.Time) 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 itemsByID map[int32][]*GroundSpawnEntryItem // Harvest items by groundspawn ID
respawnQueue map[int32]time.Time // Respawn timestamps 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 database Database // Database interface
logger Logger // Logging interface logger Logger // Logging interface
mutex sync.RWMutex // Thread safety mutex sync.RWMutex // Thread safety
nextSpawnID int32 // Efficient ID counter to avoid len() calls
// Statistics // Statistics
totalHarvests int64 // Total harvest attempts totalHarvests int64 // Total harvest attempts

View File

@ -275,7 +275,7 @@ type MyGroupPacketHandler struct {
// client connection management // 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 // Send group update packets to clients
return nil return nil
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -117,10 +117,10 @@ type PlayerManager interface {
// LogHandler defines the interface for logging operations // LogHandler defines the interface for logging operations
type LogHandler interface { type LogHandler interface {
LogDebug(system, format string, args ...interface{}) LogDebug(system, format string, args ...any)
LogInfo(system, format string, args ...interface{}) LogInfo(system, format string, args ...any)
LogWarning(system, format string, args ...interface{}) LogWarning(system, format string, args ...any)
LogError(system, format string, args ...interface{}) LogError(system, format string, args ...any)
} }
// TimerManager defines the interface for timer management // TimerManager defines the interface for timer management
@ -139,8 +139,8 @@ type TimerManager interface {
// CacheManager defines the interface for caching operations // CacheManager defines the interface for caching operations
type CacheManager interface { type CacheManager interface {
// Cache operations // Cache operations
Set(key string, value interface{}, expiration time.Duration) error Set(key string, value any, expiration time.Duration) error
Get(key string) (interface{}, bool) Get(key string) (any, bool)
Delete(key string) error Delete(key string) error
Clear() error Clear() error
@ -213,6 +213,6 @@ type StatisticsCollector interface {
type ConfigManager interface { type ConfigManager interface {
GetHOConfig() *HeroicOPConfig GetHOConfig() *HeroicOPConfig
UpdateHOConfig(config *HeroicOPConfig) error UpdateHOConfig(config *HeroicOPConfig) error
GetConfigValue(key string) interface{} GetConfigValue(key string) any
SetConfigValue(key string, value interface{}) error 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 // 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() mhol.mu.RLock()
defer mhol.mu.RUnlock() defer mhol.mu.RUnlock()
stats := make(map[string]interface{}) stats := make(map[string]any)
// Basic counts // Basic counts
stats["total_starters"] = mhol.getStarterCountNoLock() stats["total_starters"] = mhol.getStarterCountNoLock()

View File

@ -159,10 +159,10 @@ type ZoneManager interface {
// LogHandler defines the interface for logging operations // LogHandler defines the interface for logging operations
type LogHandler interface { type LogHandler interface {
LogDebug(system, format string, args ...interface{}) LogDebug(system, format string, args ...any)
LogInfo(system, format string, args ...interface{}) LogInfo(system, format string, args ...any)
LogWarning(system, format string, args ...interface{}) LogWarning(system, format string, args ...any)
LogError(system, format string, args ...interface{}) LogError(system, format string, args ...any)
} }
// Additional integration interfaces // Additional integration interfaces
@ -282,8 +282,8 @@ type AccessManager interface {
type ConfigManager interface { type ConfigManager interface {
GetHousingConfig() *HousingConfig GetHousingConfig() *HousingConfig
UpdateHousingConfig(config *HousingConfig) error UpdateHousingConfig(config *HousingConfig) error
GetConfigValue(key string) interface{} GetConfigValue(key string) any
SetConfigValue(key string, value interface{}) error SetConfigValue(key string, value any) error
} }
// NotificationManager defines interface for housing notifications // NotificationManager defines interface for housing notifications
@ -298,8 +298,8 @@ type NotificationManager interface {
// CacheManager defines interface for caching operations // CacheManager defines interface for caching operations
type CacheManager interface { type CacheManager interface {
// Cache operations // Cache operations
Set(key string, value interface{}, expiration time.Duration) error Set(key string, value any, expiration time.Duration) error
Get(key string) (interface{}, bool) Get(key string) (any, bool)
Delete(key string) error Delete(key string) error
Clear() 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 // 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) inventory, err := isa.GetPlayerInventory(playerID)
if err != nil { if err != nil {
return nil, err return nil, err
@ -587,7 +587,7 @@ func (isa *ItemSystemAdapter) GetPlayerItemStats(playerID uint32) (map[string]in
// Calculate equipment bonuses // Calculate equipment bonuses
bonuses := equipment.CalculateEquipmentBonuses() bonuses := equipment.CalculateEquipmentBonuses()
return map[string]interface{}{ return map[string]any{
"player_id": playerID, "player_id": playerID,
"total_items": inventory.GetNumberOfItems(), "total_items": inventory.GetNumberOfItems(),
"equipped_items": equipment.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 // 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() isa.mutex.RLock()
defer isa.mutex.RUnlock() defer isa.mutex.RUnlock()
masterStats := isa.masterList.GetStats() masterStats := isa.masterList.GetStats()
return map[string]interface{}{ return map[string]any{
"total_item_templates": masterStats.TotalItems, "total_item_templates": masterStats.TotalItems,
"items_by_type": masterStats.ItemsByType, "items_by_type": masterStats.ItemsByType,
"items_by_tier": masterStats.ItemsByTier, "items_by_tier": masterStats.ItemsByTier,

View File

@ -43,13 +43,13 @@ func (ci ChestInteraction) String() string {
// ChestInteractionResult represents the result of a chest interaction // ChestInteractionResult represents the result of a chest interaction
type ChestInteractionResult struct { type ChestInteractionResult struct {
Success bool `json:"success"` Success bool `json:"success"`
Result int8 `json:"result"` // ChestResult constant Result int8 `json:"result"` // ChestResult constant
Message string `json:"message"` // Message to display to player Message string `json:"message"` // Message to display to player
Items []*items.Item `json:"items"` // Items received Items []*items.Item `json:"items"` // Items received
Coins int32 `json:"coins"` // Coins received Coins int32 `json:"coins"` // Coins received
Experience int32 `json:"experience"` // Experience gained (for disarming/lockpicking) Experience int32 `json:"experience"` // Experience gained (for disarming/lockpicking)
ChestEmpty bool `json:"chest_empty"` // Whether chest is now empty ChestEmpty bool `json:"chest_empty"` // Whether chest is now empty
ChestClosed bool `json:"chest_closed"` // Whether chest should be closed ChestClosed bool `json:"chest_closed"` // Whether chest should be closed
} }
// ChestService handles treasure chest interactions and management // ChestService handles treasure chest interactions and management
@ -73,7 +73,7 @@ type PlayerService interface {
// ZoneService interface for zone-related operations // ZoneService interface for zone-related operations
type ZoneService interface { 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) SpawnObjectInZone(zoneID int32, appearanceID int32, x, y, z, heading float32, name string, commands []string) (int32, error)
RemoveObjectFromZone(zoneID int32, objectID int32) error RemoveObjectFromZone(zoneID int32, objectID int32) error
GetDistanceBetweenPoints(x1, y1, z1, x2, y2, z2 float32) float32 GetDistanceBetweenPoints(x1, y1, z1, x2, y2, z2 float32) float32
@ -123,7 +123,7 @@ func (cs *ChestService) CreateTreasureChestFromLoot(spawnID int32, zoneID int32,
// Don't create chest if no qualifying items and no coins // Don't create chest if no qualifying items and no coins
if filteredResult.IsEmpty() { if filteredResult.IsEmpty() {
log.Printf("%s No qualifying loot for treasure chest (tier >= %d) for spawn %d", log.Printf("%s No qualifying loot for treasure chest (tier >= %d) for spawn %d",
LogPrefixChest, LootTierCommon, spawnID) LogPrefixChest, LootTierCommon, spawnID)
return nil, nil return nil, nil
} }
@ -136,7 +136,7 @@ func (cs *ChestService) CreateTreasureChestFromLoot(spawnID int32, zoneID int32,
// Spawn the chest object in the zone // Spawn the chest object in the zone
chestCommands := []string{"loot", "disarm"} // TODO: Add "lockpick" if chest is locked chestCommands := []string{"loot", "disarm"} // TODO: Add "lockpick" if chest is locked
objectID, err := cs.zoneService.SpawnObjectInZone(zoneID, chest.AppearanceID, x, y, z, heading, objectID, err := cs.zoneService.SpawnObjectInZone(zoneID, chest.AppearanceID, x, y, z, heading,
"Treasure Chest", chestCommands) "Treasure Chest", chestCommands)
if err != nil { if err != nil {
log.Printf("%s Failed to spawn chest object in zone: %v", LogPrefixChest, err) log.Printf("%s Failed to spawn chest object in zone: %v", LogPrefixChest, err)
@ -149,7 +149,7 @@ func (cs *ChestService) CreateTreasureChestFromLoot(spawnID int32, zoneID int32,
} }
// HandleChestInteraction processes a player's interaction with a treasure chest // HandleChestInteraction processes a player's interaction with a treasure chest
func (cs *ChestService) HandleChestInteraction(chestID int32, playerID uint32, func (cs *ChestService) HandleChestInteraction(chestID int32, playerID uint32,
interaction ChestInteraction, itemUniqueID int64) *ChestInteractionResult { interaction ChestInteraction, itemUniqueID int64) *ChestInteractionResult {
result := &ChestInteractionResult{ result := &ChestInteractionResult{
@ -273,10 +273,10 @@ func (cs *ChestService) handleViewChest(chest *TreasureChest, playerID uint32) *
return &ChestInteractionResult{ return &ChestInteractionResult{
Success: true, Success: true,
Result: ChestResultSuccess, Result: ChestResultSuccess,
Message: fmt.Sprintf("The chest contains %d items and %d coins", Message: fmt.Sprintf("The chest contains %d items and %d coins",
len(chest.LootResult.GetItems()), chest.LootResult.GetCoins()), len(chest.LootResult.GetItems()), chest.LootResult.GetCoins()),
Items: chest.LootResult.GetItems(), Items: chest.LootResult.GetItems(),
Coins: chest.LootResult.GetCoins(), Coins: chest.LootResult.GetCoins(),
} }
} }
@ -400,7 +400,7 @@ func (cs *ChestService) handleDisarmChest(chest *TreasureChest, playerID uint32)
// Get player's disarm skill // Get player's disarm skill
disarmSkill := cs.playerService.GetPlayerSkillValue(playerID, "Disarm Trap") disarmSkill := cs.playerService.GetPlayerSkillValue(playerID, "Disarm Trap")
// Calculate success chance (simplified) // Calculate success chance (simplified)
successChance := float32(disarmSkill) - float32(chest.DisarmDifficulty) successChance := float32(disarmSkill) - float32(chest.DisarmDifficulty)
if successChance < 0 { if successChance < 0 {
@ -410,7 +410,7 @@ func (cs *ChestService) handleDisarmChest(chest *TreasureChest, playerID uint32)
} }
// Roll for success // Roll for success
roll := float32(time.Now().UnixNano()%100) // Simple random roll := float32(time.Now().UnixNano() % 100) // Simple random
if roll > successChance { if roll > successChance {
// Failed disarm - could trigger trap effects here // Failed disarm - could trigger trap effects here
return &ChestInteractionResult{ return &ChestInteractionResult{
@ -422,7 +422,7 @@ func (cs *ChestService) handleDisarmChest(chest *TreasureChest, playerID uint32)
// Success - disarm the trap // Success - disarm the trap
chest.IsDisarmable = false chest.IsDisarmable = false
// Give experience // Give experience
experience := int32(chest.DisarmDifficulty * 10) // 10 exp per difficulty point experience := int32(chest.DisarmDifficulty * 10) // 10 exp per difficulty point
cs.playerService.AddPlayerExperience(playerID, experience, "Disarm Trap") cs.playerService.AddPlayerExperience(playerID, experience, "Disarm Trap")
@ -450,7 +450,7 @@ func (cs *ChestService) handleLockpickChest(chest *TreasureChest, playerID uint3
// Get player's lockpicking skill // Get player's lockpicking skill
lockpickSkill := cs.playerService.GetPlayerSkillValue(playerID, "Pick Lock") lockpickSkill := cs.playerService.GetPlayerSkillValue(playerID, "Pick Lock")
// Calculate success chance (simplified) // Calculate success chance (simplified)
successChance := float32(lockpickSkill) - float32(chest.LockpickDifficulty) successChance := float32(lockpickSkill) - float32(chest.LockpickDifficulty)
if successChance < 0 { if successChance < 0 {
@ -460,7 +460,7 @@ func (cs *ChestService) handleLockpickChest(chest *TreasureChest, playerID uint3
} }
// Roll for success // Roll for success
roll := float32(time.Now().UnixNano()%100) // Simple random roll := float32(time.Now().UnixNano() % 100) // Simple random
if roll > successChance { if roll > successChance {
return &ChestInteractionResult{ return &ChestInteractionResult{
Success: false, Success: false,
@ -471,7 +471,7 @@ func (cs *ChestService) handleLockpickChest(chest *TreasureChest, playerID uint3
// Success - unlock the chest // Success - unlock the chest
chest.IsLocked = false chest.IsLocked = false
// Give experience // Give experience
experience := int32(chest.LockpickDifficulty * 10) // 10 exp per difficulty point experience := int32(chest.LockpickDifficulty * 10) // 10 exp per difficulty point
cs.playerService.AddPlayerExperience(playerID, experience, "Pick Lock") cs.playerService.AddPlayerExperience(playerID, experience, "Pick Lock")
@ -500,12 +500,12 @@ func (cs *ChestService) handleCloseChest(chest *TreasureChest, playerID uint32)
// CleanupEmptyChests removes empty chests from zones // CleanupEmptyChests removes empty chests from zones
func (cs *ChestService) CleanupEmptyChests(zoneID int32) { func (cs *ChestService) CleanupEmptyChests(zoneID int32) {
chests := cs.lootManager.GetZoneChests(zoneID) chests := cs.lootManager.GetZoneChests(zoneID)
for _, chest := range chests { for _, chest := range chests {
if chest.LootResult.IsEmpty() { if chest.LootResult.IsEmpty() {
// Remove from zone // Remove from zone
cs.zoneService.RemoveObjectFromZone(zoneID, chest.ID) cs.zoneService.RemoveObjectFromZone(zoneID, chest.ID)
// Remove from loot manager // Remove from loot manager
cs.lootManager.RemoveTreasureChest(chest.ID) cs.lootManager.RemoveTreasureChest(chest.ID)
} }
@ -515,4 +515,4 @@ func (cs *ChestService) CleanupEmptyChests(zoneID int32) {
// GetPlayerChestList returns a list of chests a player can access // GetPlayerChestList returns a list of chests a player can access
func (cs *ChestService) GetPlayerChestList(playerID uint32) []*TreasureChest { func (cs *ChestService) GetPlayerChestList(playerID uint32) []*TreasureChest {
return cs.lootManager.GetPlayerChests(playerID) return cs.lootManager.GetPlayerChests(playerID)
} }

View File

@ -10,12 +10,12 @@ import (
// LootDatabase handles all database operations for the loot system // LootDatabase handles all database operations for the loot system
type LootDatabase struct { type LootDatabase struct {
db *sql.DB db *sql.DB
queries map[string]*sql.Stmt queries map[string]*sql.Stmt
lootTables map[int32]*LootTable lootTables map[int32]*LootTable
spawnLoot map[int32][]int32 // spawn_id -> []loot_table_id spawnLoot map[int32][]int32 // spawn_id -> []loot_table_id
globalLoot []*GlobalLoot globalLoot []*GlobalLoot
mutex sync.RWMutex mutex sync.RWMutex
} }
// NewLootDatabase creates a new loot database manager // NewLootDatabase creates a new loot database manager
@ -42,88 +42,88 @@ func (ldb *LootDatabase) prepareQueries() {
FROM loottable FROM loottable
ORDER BY id ORDER BY id
`, `,
"load_loot_drops": ` "load_loot_drops": `
SELECT loot_table_id, item_id, item_charges, equip_item, probability, no_drop_quest_completed_id SELECT loot_table_id, item_id, item_charges, equip_item, probability, no_drop_quest_completed_id
FROM lootdrop FROM lootdrop
WHERE loot_table_id = ? WHERE loot_table_id = ?
ORDER BY probability DESC ORDER BY probability DESC
`, `,
"load_spawn_loot": ` "load_spawn_loot": `
SELECT spawn_id, loottable_id SELECT spawn_id, loottable_id
FROM spawn_loot FROM spawn_loot
ORDER BY spawn_id ORDER BY spawn_id
`, `,
"load_global_loot": ` "load_global_loot": `
SELECT type, loot_table, value1, value2, value3, value4 SELECT type, loot_table, value1, value2, value3, value4
FROM loot_global FROM loot_global
ORDER BY type, value1 ORDER BY type, value1
`, `,
"insert_loot_table": ` "insert_loot_table": `
INSERT INTO loottable (id, name, mincoin, maxcoin, maxlootitems, lootdrop_probability, coin_probability) INSERT INTO loottable (id, name, mincoin, maxcoin, maxlootitems, lootdrop_probability, coin_probability)
VALUES (?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?)
`, `,
"update_loot_table": ` "update_loot_table": `
UPDATE loottable UPDATE loottable
SET name = ?, mincoin = ?, maxcoin = ?, maxlootitems = ?, lootdrop_probability = ?, coin_probability = ? SET name = ?, mincoin = ?, maxcoin = ?, maxlootitems = ?, lootdrop_probability = ?, coin_probability = ?
WHERE id = ? WHERE id = ?
`, `,
"delete_loot_table": ` "delete_loot_table": `
DELETE FROM loottable WHERE id = ? DELETE FROM loottable WHERE id = ?
`, `,
"insert_loot_drop": ` "insert_loot_drop": `
INSERT INTO lootdrop (loot_table_id, item_id, item_charges, equip_item, probability, no_drop_quest_completed_id) INSERT INTO lootdrop (loot_table_id, item_id, item_charges, equip_item, probability, no_drop_quest_completed_id)
VALUES (?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?)
`, `,
"delete_loot_drops": ` "delete_loot_drops": `
DELETE FROM lootdrop WHERE loot_table_id = ? DELETE FROM lootdrop WHERE loot_table_id = ?
`, `,
"insert_spawn_loot": ` "insert_spawn_loot": `
INSERT OR REPLACE INTO spawn_loot (spawn_id, loottable_id) INSERT OR REPLACE INTO spawn_loot (spawn_id, loottable_id)
VALUES (?, ?) VALUES (?, ?)
`, `,
"delete_spawn_loot": ` "delete_spawn_loot": `
DELETE FROM spawn_loot WHERE spawn_id = ? DELETE FROM spawn_loot WHERE spawn_id = ?
`, `,
"insert_global_loot": ` "insert_global_loot": `
INSERT INTO loot_global (type, loot_table, value1, value2, value3, value4) INSERT INTO loot_global (type, loot_table, value1, value2, value3, value4)
VALUES (?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?)
`, `,
"delete_global_loot": ` "delete_global_loot": `
DELETE FROM loot_global WHERE type = ? DELETE FROM loot_global WHERE type = ?
`, `,
"get_loot_table": ` "get_loot_table": `
SELECT id, name, mincoin, maxcoin, maxlootitems, lootdrop_probability, coin_probability SELECT id, name, mincoin, maxcoin, maxlootitems, lootdrop_probability, coin_probability
FROM loottable FROM loottable
WHERE id = ? WHERE id = ?
`, `,
"get_spawn_loot_tables": ` "get_spawn_loot_tables": `
SELECT loottable_id SELECT loottable_id
FROM spawn_loot FROM spawn_loot
WHERE spawn_id = ? WHERE spawn_id = ?
`, `,
"count_loot_tables": ` "count_loot_tables": `
SELECT COUNT(*) FROM loottable SELECT COUNT(*) FROM loottable
`, `,
"count_loot_drops": ` "count_loot_drops": `
SELECT COUNT(*) FROM lootdrop SELECT COUNT(*) FROM lootdrop
`, `,
"count_spawn_loot": ` "count_spawn_loot": `
SELECT COUNT(*) FROM spawn_loot SELECT COUNT(*) FROM spawn_loot
`, `,
@ -141,36 +141,36 @@ func (ldb *LootDatabase) prepareQueries() {
// LoadAllLootData loads all loot data from the database // LoadAllLootData loads all loot data from the database
func (ldb *LootDatabase) LoadAllLootData() error { func (ldb *LootDatabase) LoadAllLootData() error {
log.Printf("%s Loading loot data from database...", LogPrefixDatabase) log.Printf("%s Loading loot data from database...", LogPrefixDatabase)
// Load loot tables first // Load loot tables first
if err := ldb.loadLootTables(); err != nil { if err := ldb.loadLootTables(); err != nil {
return fmt.Errorf("failed to load loot tables: %v", err) return fmt.Errorf("failed to load loot tables: %v", err)
} }
// Load loot drops for each table // Load loot drops for each table
if err := ldb.loadLootDrops(); err != nil { if err := ldb.loadLootDrops(); err != nil {
return fmt.Errorf("failed to load loot drops: %v", err) return fmt.Errorf("failed to load loot drops: %v", err)
} }
// Load spawn loot assignments // Load spawn loot assignments
if err := ldb.loadSpawnLoot(); err != nil { if err := ldb.loadSpawnLoot(); err != nil {
return fmt.Errorf("failed to load spawn loot: %v", err) return fmt.Errorf("failed to load spawn loot: %v", err)
} }
// Load global loot configuration // Load global loot configuration
if err := ldb.loadGlobalLoot(); err != nil { if err := ldb.loadGlobalLoot(); err != nil {
return fmt.Errorf("failed to load global loot: %v", err) return fmt.Errorf("failed to load global loot: %v", err)
} }
ldb.mutex.RLock() ldb.mutex.RLock()
tableCount := len(ldb.lootTables) tableCount := len(ldb.lootTables)
spawnCount := len(ldb.spawnLoot) spawnCount := len(ldb.spawnLoot)
globalCount := len(ldb.globalLoot) globalCount := len(ldb.globalLoot)
ldb.mutex.RUnlock() ldb.mutex.RUnlock()
log.Printf("%s Loaded %d loot tables, %d spawn assignments, %d global loot entries", log.Printf("%s Loaded %d loot tables, %d spawn assignments, %d global loot entries",
LogPrefixDatabase, tableCount, spawnCount, globalCount) LogPrefixDatabase, tableCount, spawnCount, globalCount)
return nil return nil
} }
@ -189,7 +189,7 @@ func (ldb *LootDatabase) loadLootTables() error {
ldb.mutex.Lock() ldb.mutex.Lock()
defer ldb.mutex.Unlock() defer ldb.mutex.Unlock()
// Clear existing tables // Clear existing tables
ldb.lootTables = make(map[int32]*LootTable) ldb.lootTables = make(map[int32]*LootTable)
@ -279,7 +279,7 @@ func (ldb *LootDatabase) loadSpawnLoot() error {
ldb.mutex.Lock() ldb.mutex.Lock()
defer ldb.mutex.Unlock() defer ldb.mutex.Unlock()
// Clear existing spawn loot // Clear existing spawn loot
ldb.spawnLoot = make(map[int32][]int32) ldb.spawnLoot = make(map[int32][]int32)
@ -313,7 +313,7 @@ func (ldb *LootDatabase) loadGlobalLoot() error {
ldb.mutex.Lock() ldb.mutex.Lock()
defer ldb.mutex.Unlock() defer ldb.mutex.Unlock()
// Clear existing global loot // Clear existing global loot
ldb.globalLoot = make([]*GlobalLoot, 0) ldb.globalLoot = make([]*GlobalLoot, 0)
@ -361,7 +361,7 @@ func (ldb *LootDatabase) loadGlobalLoot() error {
func (ldb *LootDatabase) GetLootTable(tableID int32) *LootTable { func (ldb *LootDatabase) GetLootTable(tableID int32) *LootTable {
ldb.mutex.RLock() ldb.mutex.RLock()
defer ldb.mutex.RUnlock() defer ldb.mutex.RUnlock()
return ldb.lootTables[tableID] return ldb.lootTables[tableID]
} }
@ -369,12 +369,12 @@ func (ldb *LootDatabase) GetLootTable(tableID int32) *LootTable {
func (ldb *LootDatabase) GetSpawnLootTables(spawnID int32) []int32 { func (ldb *LootDatabase) GetSpawnLootTables(spawnID int32) []int32 {
ldb.mutex.RLock() ldb.mutex.RLock()
defer ldb.mutex.RUnlock() defer ldb.mutex.RUnlock()
tables := ldb.spawnLoot[spawnID] tables := ldb.spawnLoot[spawnID]
if tables == nil { if tables == nil {
return nil return nil
} }
// Return a copy to prevent external modification // Return a copy to prevent external modification
result := make([]int32, len(tables)) result := make([]int32, len(tables))
copy(result, tables) copy(result, tables)
@ -385,9 +385,9 @@ func (ldb *LootDatabase) GetSpawnLootTables(spawnID int32) []int32 {
func (ldb *LootDatabase) GetGlobalLootTables(level int16, race int16, zoneID int32) []*GlobalLoot { func (ldb *LootDatabase) GetGlobalLootTables(level int16, race int16, zoneID int32) []*GlobalLoot {
ldb.mutex.RLock() ldb.mutex.RLock()
defer ldb.mutex.RUnlock() defer ldb.mutex.RUnlock()
var result []*GlobalLoot var result []*GlobalLoot
for _, global := range ldb.globalLoot { for _, global := range ldb.globalLoot {
switch global.Type { switch global.Type {
case GlobalLootTypeLevel: case GlobalLootTypeLevel:
@ -404,7 +404,7 @@ func (ldb *LootDatabase) GetGlobalLootTables(level int16, race int16, zoneID int
} }
} }
} }
return result return result
} }
@ -587,8 +587,8 @@ func (ldb *LootDatabase) DeleteSpawnLoot(spawnID int32) error {
} }
// GetLootStatistics returns database statistics // GetLootStatistics returns database statistics
func (ldb *LootDatabase) GetLootStatistics() (map[string]interface{}, error) { func (ldb *LootDatabase) GetLootStatistics() (map[string]any, error) {
stats := make(map[string]interface{}) stats := make(map[string]any)
// Count loot tables // Count loot tables
if stmt := ldb.queries["count_loot_tables"]; stmt != nil { if stmt := ldb.queries["count_loot_tables"]; stmt != nil {
@ -629,7 +629,7 @@ func (ldb *LootDatabase) GetLootStatistics() (map[string]interface{}, error) {
// ReloadLootData reloads all loot data from the database // ReloadLootData reloads all loot data from the database
func (ldb *LootDatabase) ReloadLootData() error { func (ldb *LootDatabase) ReloadLootData() error {
log.Printf("%s Reloading loot data from database...", LogPrefixDatabase) log.Printf("%s Reloading loot data from database...", LogPrefixDatabase)
return ldb.LoadAllLootData() return ldb.LoadAllLootData()
} }
@ -641,4 +641,4 @@ func (ldb *LootDatabase) Close() error {
} }
} }
return nil return nil
} }

View File

@ -18,13 +18,13 @@ type LootSystem struct {
// LootSystemConfig holds configuration for the loot system // LootSystemConfig holds configuration for the loot system
type LootSystemConfig struct { type LootSystemConfig struct {
DatabaseConnection *sql.DB DatabaseConnection *sql.DB
ItemMasterList items.MasterItemListService ItemMasterList items.MasterItemListService
PlayerService PlayerService PlayerService PlayerService
ZoneService ZoneService ZoneService ZoneService
ClientService ClientService ClientService ClientService
ItemPacketBuilder ItemPacketBuilder ItemPacketBuilder ItemPacketBuilder
StartCleanupTimer bool StartCleanupTimer bool
} }
// NewLootSystem creates a complete loot system with all components // NewLootSystem creates a complete loot system with all components
@ -78,7 +78,7 @@ func NewLootSystem(config *LootSystemConfig) (*LootSystem, error) {
} }
// GenerateAndCreateChest generates loot for a spawn and creates a treasure chest // GenerateAndCreateChest generates loot for a spawn and creates a treasure chest
func (ls *LootSystem) GenerateAndCreateChest(spawnID int32, zoneID int32, x, y, z, heading float32, func (ls *LootSystem) GenerateAndCreateChest(spawnID int32, zoneID int32, x, y, z, heading float32,
context *LootContext) (*TreasureChest, error) { context *LootContext) (*TreasureChest, error) {
if ls.ChestService == nil { if ls.ChestService == nil {
@ -98,7 +98,7 @@ func (ls *LootSystem) GenerateAndCreateChest(spawnID int32, zoneID int32, x, y,
} }
// Create treasure chest // Create treasure chest
chest, err := ls.ChestService.CreateTreasureChestFromLoot(spawnID, zoneID, x, y, z, heading, chest, err := ls.ChestService.CreateTreasureChestFromLoot(spawnID, zoneID, x, y, z, heading,
lootResult, context.GroupMembers) lootResult, context.GroupMembers)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create treasure chest: %v", err) return nil, fmt.Errorf("failed to create treasure chest: %v", err)
@ -108,7 +108,7 @@ func (ls *LootSystem) GenerateAndCreateChest(spawnID int32, zoneID int32, x, y,
} }
// HandlePlayerLootInteraction handles a player's interaction with a chest and sends appropriate packets // HandlePlayerLootInteraction handles a player's interaction with a chest and sends appropriate packets
func (ls *LootSystem) HandlePlayerLootInteraction(chestID int32, playerID uint32, func (ls *LootSystem) HandlePlayerLootInteraction(chestID int32, playerID uint32,
interaction ChestInteraction, itemUniqueID int64) error { interaction ChestInteraction, itemUniqueID int64) error {
if ls.ChestService == nil { if ls.ChestService == nil {
@ -141,7 +141,7 @@ func (ls *LootSystem) HandlePlayerLootInteraction(chestID int32, playerID uint32
} }
// Log the interaction // Log the interaction
log.Printf("%s Player %d %s chest %d: %s", log.Printf("%s Player %d %s chest %d: %s",
LogPrefixLoot, playerID, interaction.String(), chestID, result.Message) LogPrefixLoot, playerID, interaction.String(), chestID, result.Message)
return nil return nil
@ -168,8 +168,8 @@ func (ls *LootSystem) ShowChestToPlayer(chestID int32, playerID uint32) error {
} }
// GetSystemStatistics returns comprehensive statistics about the loot system // GetSystemStatistics returns comprehensive statistics about the loot system
func (ls *LootSystem) GetSystemStatistics() (map[string]interface{}, error) { func (ls *LootSystem) GetSystemStatistics() (map[string]any, error) {
stats := make(map[string]interface{}) stats := make(map[string]any)
// Database statistics // Database statistics
if dbStats, err := ls.Database.GetLootStatistics(); err == nil { if dbStats, err := ls.Database.GetLootStatistics(); err == nil {
@ -220,7 +220,7 @@ func (ls *LootSystem) AddLootTableWithDrops(table *LootTable) error {
} }
// CreateQuickLootTable creates a simple loot table with basic parameters // CreateQuickLootTable creates a simple loot table with basic parameters
func (ls *LootSystem) CreateQuickLootTable(tableID int32, name string, items []QuickLootItem, func (ls *LootSystem) CreateQuickLootTable(tableID int32, name string, items []QuickLootItem,
minCoin, maxCoin int32, maxItems int16) error { minCoin, maxCoin int32, maxItems int16) error {
table := &LootTable{ table := &LootTable{
@ -289,9 +289,9 @@ func (ls *LootSystem) CreateGlobalLevelLoot(minLevel, maxLevel int8, tableID int
ls.Database.globalLoot = append(ls.Database.globalLoot, global) ls.Database.globalLoot = append(ls.Database.globalLoot, global)
ls.Database.mutex.Unlock() ls.Database.mutex.Unlock()
log.Printf("%s Created global level loot for levels %d-%d using table %d", log.Printf("%s Created global level loot for levels %d-%d using table %d",
LogPrefixLoot, minLevel, maxLevel, tableID) LogPrefixLoot, minLevel, maxLevel, tableID)
return nil return nil
} }
@ -303,10 +303,10 @@ func (ls *LootSystem) GetActiveChestsInZone(zoneID int32) []*TreasureChest {
// CleanupZoneChests removes all chests from a specific zone // CleanupZoneChests removes all chests from a specific zone
func (ls *LootSystem) CleanupZoneChests(zoneID int32) { func (ls *LootSystem) CleanupZoneChests(zoneID int32) {
chests := ls.Manager.GetZoneChests(zoneID) chests := ls.Manager.GetZoneChests(zoneID)
for _, chest := range chests { for _, chest := range chests {
ls.Manager.RemoveTreasureChest(chest.ID) ls.Manager.RemoveTreasureChest(chest.ID)
// Remove from zone if chest service is available // Remove from zone if chest service is available
if ls.ChestService != nil { if ls.ChestService != nil {
ls.ChestService.zoneService.RemoveObjectFromZone(zoneID, chest.ID) ls.ChestService.zoneService.RemoveObjectFromZone(zoneID, chest.ID)
@ -356,17 +356,17 @@ type ValidationError struct {
func (ls *LootSystem) GetLootPreview(spawnID int32, context *LootContext) (*LootPreview, error) { func (ls *LootSystem) GetLootPreview(spawnID int32, context *LootContext) (*LootPreview, error) {
tableIDs := ls.Database.GetSpawnLootTables(spawnID) tableIDs := ls.Database.GetSpawnLootTables(spawnID)
globalLoot := ls.Database.GetGlobalLootTables(context.PlayerLevel, context.PlayerRace, context.ZoneID) globalLoot := ls.Database.GetGlobalLootTables(context.PlayerLevel, context.PlayerRace, context.ZoneID)
for _, global := range globalLoot { for _, global := range globalLoot {
tableIDs = append(tableIDs, global.TableID) tableIDs = append(tableIDs, global.TableID)
} }
preview := &LootPreview{ preview := &LootPreview{
SpawnID: spawnID, SpawnID: spawnID,
TableIDs: tableIDs, TableIDs: tableIDs,
PossibleItems: make([]*LootPreviewItem, 0), PossibleItems: make([]*LootPreviewItem, 0),
MinCoins: 0, MinCoins: 0,
MaxCoins: 0, MaxCoins: 0,
} }
for _, tableID := range tableIDs { for _, tableID := range tableIDs {
@ -400,11 +400,11 @@ func (ls *LootSystem) GetLootPreview(spawnID int32, context *LootContext) (*Loot
// LootPreview represents a preview of potential loot // LootPreview represents a preview of potential loot
type LootPreview struct { type LootPreview struct {
SpawnID int32 `json:"spawn_id"` SpawnID int32 `json:"spawn_id"`
TableIDs []int32 `json:"table_ids"` TableIDs []int32 `json:"table_ids"`
PossibleItems []*LootPreviewItem `json:"possible_items"` PossibleItems []*LootPreviewItem `json:"possible_items"`
MinCoins int32 `json:"min_coins"` MinCoins int32 `json:"min_coins"`
MaxCoins int32 `json:"max_coins"` MaxCoins int32 `json:"max_coins"`
} }
// LootPreviewItem represents a potential loot item in a preview // LootPreviewItem represents a potential loot item in a preview
@ -413,4 +413,4 @@ type LootPreviewItem struct {
ItemName string `json:"item_name"` ItemName string `json:"item_name"`
Probability float32 `json:"probability"` Probability float32 `json:"probability"`
Tier int8 `json:"tier"` Tier int8 `json:"tier"`
} }

View File

@ -106,18 +106,18 @@ func (m *MockPlayerService) SetInventorySpace(playerID uint32, space int) {
// MockZoneService implements ZoneService for testing // MockZoneService implements ZoneService for testing
type MockZoneService struct { type MockZoneService struct {
rules map[int32]map[string]interface{} rules map[int32]map[string]any
objects map[int32]map[int32]interface{} // zoneID -> objectID objects map[int32]map[int32]any // zoneID -> objectID
} }
func NewMockZoneService() *MockZoneService { func NewMockZoneService() *MockZoneService {
return &MockZoneService{ return &MockZoneService{
rules: make(map[int32]map[string]interface{}), rules: make(map[int32]map[string]any),
objects: make(map[int32]map[int32]interface{}), 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 { if rules, exists := m.rules[zoneID]; exists {
return rules[ruleName], nil 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) { 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) objectID := int32(len(m.objects[zoneID]) + 1)
if m.objects[zoneID] == nil { if m.objects[zoneID] == nil {
m.objects[zoneID] = make(map[int32]interface{}) m.objects[zoneID] = make(map[int32]any)
} }
m.objects[zoneID][objectID] = struct{}{} m.objects[zoneID][objectID] = struct{}{}
return objectID, nil return objectID, nil
@ -382,7 +382,7 @@ func TestTreasureChestCreation(t *testing.T) {
} }
if chest.AppearanceID != ChestAppearanceOrnate { if chest.AppearanceID != ChestAppearanceOrnate {
t.Errorf("Expected ornate chest appearance %d for legendary item, got %d", t.Errorf("Expected ornate chest appearance %d for legendary item, got %d",
ChestAppearanceOrnate, chest.AppearanceID) ChestAppearanceOrnate, chest.AppearanceID)
} }
@ -530,7 +530,7 @@ func TestChestAppearanceSelection(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
appearance := GetChestAppearance(tc.tier) appearance := GetChestAppearance(tc.tier)
if appearance.AppearanceID != tc.expected { if appearance.AppearanceID != tc.expected {
t.Errorf("For tier %d, expected appearance %d, got %d", t.Errorf("For tier %d, expected appearance %d, got %d",
tc.tier, tc.expected, appearance.AppearanceID) tc.tier, tc.expected, appearance.AppearanceID)
} }
} }
@ -667,4 +667,4 @@ func BenchmarkChestInteraction(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
chestService.HandleChestInteraction(chest.ID, 1, ChestInteractionView, 0) chestService.HandleChestInteraction(chest.ID, 1, ChestInteractionView, 0)
} }
} }

View File

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

View File

@ -7,10 +7,10 @@ import (
// Logger interface for AI logging // Logger interface for AI logging
type Logger interface { type Logger interface {
LogInfo(message string, args ...interface{}) LogInfo(message string, args ...any)
LogError(message string, args ...interface{}) LogError(message string, args ...any)
LogDebug(message string, args ...interface{}) LogDebug(message string, args ...any)
LogWarning(message string, args ...interface{}) LogWarning(message string, args ...any)
} }
// NPC interface defines the required NPC functionality for AI // NPC interface defines the required NPC functionality for AI
@ -144,7 +144,7 @@ type LuaInterface interface {
type Zone interface { type Zone interface {
GetSpawnByID(int32) Spawn GetSpawnByID(int32) Spawn
ProcessSpell(spell Spell, caster NPC, target Spawn) error 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 // 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 // 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 { if npc == nil {
return fmt.Errorf("NPC cannot be nil") return fmt.Errorf("NPC cannot be nil")
} }

View File

@ -289,7 +289,7 @@ func (dfpb *DumbFirePetBrain) ExtendExpireTime(durationMS int32) {
// Brain factory functions // Brain factory functions
// CreateBrain creates the appropriate brain type for an NPC // 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 { switch brainType {
case BrainTypeCombatPet: case BrainTypeCombatPet:
return NewCombatPetBrain(npc, logger) return NewCombatPetBrain(npc, logger)

View File

@ -1,5 +1,7 @@
package npc package npc
import "fmt"
// Database interface for NPC persistence // Database interface for NPC persistence
type Database interface { type Database interface {
LoadAllNPCs() ([]*NPC, error) LoadAllNPCs() ([]*NPC, error)
@ -13,10 +15,10 @@ type Database interface {
// Logger interface for NPC logging // Logger interface for NPC logging
type Logger interface { type Logger interface {
LogInfo(message string, args ...interface{}) LogInfo(message string, args ...any)
LogError(message string, args ...interface{}) LogError(message string, args ...any)
LogDebug(message string, args ...interface{}) LogDebug(message string, args ...any)
LogWarning(message string, args ...interface{}) LogWarning(message string, args ...any)
} }
// Client interface for NPC-related client operations // Client interface for NPC-related client operations
@ -50,15 +52,15 @@ type Zone interface {
RemoveNPC(npcID int32) error RemoveNPC(npcID int32) error
GetPlayersInRange(x, y, z, radius float32) []Player GetPlayersInRange(x, y, z, radius float32) []Player
ProcessEntityCommand(command string, client Client, target *NPC) error 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 // SpellManager interface for spell system integration
type SpellManager interface { type SpellManager interface {
GetSpell(spellID int32, tier int8) Spell GetSpell(spellID int32, tier int8) Spell
CastSpell(caster *NPC, target interface{}, spell Spell) error CastSpell(caster *NPC, target any, spell Spell) error
GetSpellEffect(entity interface{}, spellID int32) SpellEffect GetSpellEffect(entity any, spellID int32) SpellEffect
ProcessSpell(spell Spell, caster *NPC, target interface{}) error ProcessSpell(spell Spell, caster *NPC, target any) error
} }
// Spell interface for spell data // Spell interface for spell data
@ -88,8 +90,8 @@ type SpellEffect interface {
type SkillManager interface { type SkillManager interface {
GetSkill(skillID int32) MasterSkill GetSkill(skillID int32) MasterSkill
GetSkillByName(name string) MasterSkill GetSkillByName(name string) MasterSkill
ApplySkillBonus(entity interface{}, skillID int32, bonus float32) error ApplySkillBonus(entity any, skillID int32, bonus float32) error
RemoveSkillBonus(entity interface{}, skillID int32, bonus float32) error RemoveSkillBonus(entity any, skillID int32, bonus float32) error
} }
// MasterSkill interface for skill definitions // MasterSkill interface for skill definitions
@ -127,11 +129,11 @@ type MovementManager interface {
// CombatManager interface for combat system integration // CombatManager interface for combat system integration
type CombatManager interface { type CombatManager interface {
StartCombat(npc *NPC, target interface{}) error StartCombat(npc *NPC, target any) error
EndCombat(npc *NPC) error EndCombat(npc *NPC) error
ProcessCombatRound(npc *NPC) error ProcessCombatRound(npc *NPC) error
CalculateDamage(attacker *NPC, target interface{}) int32 CalculateDamage(attacker *NPC, target any) int32
ApplyDamage(target interface{}, damage int32) error ApplyDamage(target any, damage int32) error
} }
// NPCAware interface for entities that can interact with NPCs // 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 // 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 { if sca.npc == nil || sca.spellManager == nil {
return nil return nil
} }
@ -307,7 +309,7 @@ func (sca *SpellCasterAdapter) GetNextSpell(target interface{}, distance float32
} }
// GetNextBuffSpell selects the next buff spell to cast // 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 { if sca.npc == nil || sca.spellManager == nil {
return nil return nil
} }
@ -347,7 +349,7 @@ func (sca *SpellCasterAdapter) GetNextBuffSpell(target interface{}) Spell {
} }
// CastSpell attempts to cast a 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 { if sca.npc == nil || sca.spellManager == nil || spell == nil {
return fmt.Errorf("invalid parameters for spell casting") 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 // 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] castOnSpells := sca.npc.castOnSpells[CastOnAggro]
for _, npcSpell := range castOnSpells { for _, npcSpell := range castOnSpells {
spell := sca.spellManager.GetSpell(npcSpell.GetSpellID(), npcSpell.GetTier()) 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 // 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 // TODO: Implement more sophisticated spell selection based on strategy
for _, npcSpell := range sca.npc.spells { for _, npcSpell := range sca.npc.spells {
@ -446,7 +448,7 @@ func NewCombatAdapter(npc *NPC, combatManager CombatManager, logger Logger) *Com
} }
// EnterCombat handles entering combat state // EnterCombat handles entering combat state
func (ca *CombatAdapter) EnterCombat(target interface{}) error { func (ca *CombatAdapter) EnterCombat(target any) error {
if ca.npc == nil { if ca.npc == nil {
return fmt.Errorf("NPC is 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 // GetObjectInfo returns comprehensive information about the object spawn
func (os *ObjectSpawn) GetObjectInfo() map[string]interface{} { func (os *ObjectSpawn) GetObjectInfo() map[string]any {
info := make(map[string]interface{}) info := make(map[string]any)
// Add spawn info // Add spawn info
info["spawn_id"] = os.GetID() info["spawn_id"] = os.GetID()
@ -246,7 +246,7 @@ func ConvertSpawnToObject(spawn *spawn.Spawn) *ObjectSpawn {
// LoadObjectSpawnFromData loads object spawn data from database/config // LoadObjectSpawnFromData loads object spawn data from database/config
// This would be called when loading spawns from the database // 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() objectSpawn := NewObjectSpawn()
// Load basic spawn data // 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 // 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() om.mutex.RLock()
defer om.mutex.RUnlock() defer om.mutex.RUnlock()
stats := make(map[string]interface{}) stats := make(map[string]any)
stats["total_objects"] = len(om.objects) stats["total_objects"] = len(om.objects)
stats["zones_with_objects"] = len(om.objectsByZone) stats["zones_with_objects"] = len(om.objectsByZone)
stats["interactive_objects"] = len(om.interactiveObjects) stats["interactive_objects"] = len(om.interactiveObjects)

View File

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

View File

@ -40,10 +40,10 @@ type PlayerManager interface {
GetPlayersInZone(zoneID int32) []*Player GetPlayersInZone(zoneID int32) []*Player
// SendToAll sends a message to all players // 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 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 // PlayerDatabase interface for database operations
@ -85,13 +85,13 @@ type PlayerDatabase interface {
// PlayerPacketHandler interface for handling player packets // PlayerPacketHandler interface for handling player packets
type PlayerPacketHandler interface { type PlayerPacketHandler interface {
// HandlePacket handles a packet from a player // 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 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 broadcasts a packet to multiple players
BroadcastPacket(players []*Player, packet interface{}) error BroadcastPacket(players []*Player, packet any) error
} }
// PlayerEventHandler interface for player events // PlayerEventHandler interface for player events
@ -181,7 +181,7 @@ type PlayerStatistics interface {
RecordSpellCast(player *Player, spell *spells.Spell) RecordSpellCast(player *Player, spell *spells.Spell)
// GetStatistics returns player statistics // GetStatistics returns player statistics
GetStatistics(playerID int32) map[string]interface{} GetStatistics(playerID int32) map[string]any
} }
// PlayerNotifier interface for player notifications // 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 // 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 { if m.packetHandler == nil {
return fmt.Errorf("no packet handler configured") 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 // 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 { if m.packetHandler == nil {
return fmt.Errorf("no packet handler configured") return fmt.Errorf("no packet handler configured")
} }

View File

@ -114,12 +114,12 @@ type Packet interface {
// PacketStruct interface defines packet structure functionality // PacketStruct interface defines packet structure functionality
type PacketStruct interface { type PacketStruct interface {
// Packet building methods // Packet building methods
SetDataByName(name string, value interface{}, index ...int) SetDataByName(name string, value any, index ...int)
SetArrayLengthByName(name string, length 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) SetSubArrayLengthByName(name string, length int, index int)
SetSubArrayDataByName(name string, value interface{}, index1, index2 int) SetSubArrayDataByName(name string, value any, index1, index2 int)
SetSubstructArrayDataByName(substruct, name string, value interface{}, index int) SetSubstructArrayDataByName(substruct, name string, value any, index int)
SetItemArrayDataByName(name string, item Item, player Player, index int, flag ...int) SetItemArrayDataByName(name string, item Item, player Player, index int, flag ...int)
Serialize() Packet Serialize() Packet
GetVersion() int16 GetVersion() int16
@ -197,10 +197,10 @@ type Database interface {
// Logger interface defines logging functionality for quest system // Logger interface defines logging functionality for quest system
type Logger interface { type Logger interface {
LogInfo(message string, args ...interface{}) LogInfo(message string, args ...any)
LogError(message string, args ...interface{}) LogError(message string, args ...any)
LogDebug(message string, args ...interface{}) LogDebug(message string, args ...any)
LogWarning(message string, args ...interface{}) LogWarning(message string, args ...any)
} }
// Configuration interface defines quest system configuration // 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 // 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() raceID := entity.GetRace()
if !ri.races.IsValidRaceID(raceID) { 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 // GetEntityRaceInfo returns comprehensive race information for an entity
func (ri *RaceIntegration) GetEntityRaceInfo(entity EntityWithRace) map[string]interface{} { func (ri *RaceIntegration) GetEntityRaceInfo(entity EntityWithRace) map[string]any {
info := make(map[string]interface{}) info := make(map[string]any)
// Basic entity info // Basic entity info
info["entity_id"] = entity.GetID() 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 // 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) { if !ri.races.IsValidRaceID(raceID) {
return nil return nil
} }
entityData := make(map[string]interface{}) entityData := make(map[string]any)
// Basic race info // Basic race info
entityData["race_id"] = raceID entityData["race_id"] = raceID
@ -255,15 +255,15 @@ func (ri *RaceIntegration) CreateRaceSpecificEntity(raceID int8) map[string]inte
} }
// GetRaceSelectionData returns data for race selection UI // GetRaceSelectionData returns data for race selection UI
func (ri *RaceIntegration) GetRaceSelectionData() map[string]interface{} { func (ri *RaceIntegration) GetRaceSelectionData() map[string]any {
data := make(map[string]interface{}) data := make(map[string]any)
// All available races // All available races
allRaces := ri.races.GetAllRaces() 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 { for raceID, friendlyName := range allRaces {
raceData := map[string]interface{}{ raceData := map[string]any{
"id": raceID, "id": raceID,
"name": friendlyName, "name": friendlyName,
"alignment": ri.races.GetRaceAlignment(raceID), "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 // ValidateEntityRaces validates races for a collection of entities
func (rm *RaceManager) ValidateEntityRaces(entities []RaceAware) map[string]interface{} { func (rm *RaceManager) ValidateEntityRaces(entities []RaceAware) map[string]any {
validationResults := make(map[string]interface{}) validationResults := make(map[string]any)
validCount := 0 validCount := 0
invalidCount := 0 invalidCount := 0
@ -288,11 +288,11 @@ func (rm *RaceManager) ValidateEntityRaces(entities []RaceAware) map[string]inte
// Track invalid entities // Track invalid entities
if !isValid { if !isValid {
if validationResults["invalid_entities"] == nil { 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 := validationResults["invalid_entities"].([]map[string]any)
invalidList = append(invalidList, map[string]interface{}{ invalidList = append(invalidList, map[string]any{
"index": i, "index": i,
"race_id": raceID, "race_id": raceID,
}) })
@ -309,7 +309,7 @@ func (rm *RaceManager) ValidateEntityRaces(entities []RaceAware) map[string]inte
} }
// GetRaceRecommendations returns race recommendations for character creation // 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) recommendations := make([]int8, 0)
// Check for alignment preference // Check for alignment preference

View File

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

View File

@ -321,8 +321,8 @@ func (ru *RaceUtils) GetRaceAliases(raceID int8) []string {
} }
// GetRaceStatistics returns statistics about the race system // GetRaceStatistics returns statistics about the race system
func (ru *RaceUtils) GetRaceStatistics() map[string]interface{} { func (ru *RaceUtils) GetRaceStatistics() map[string]any {
stats := make(map[string]interface{}) stats := make(map[string]any)
stats["total_races"] = ru.races.GetRaceCount() stats["total_races"] = ru.races.GetRaceCount()
stats["good_races"] = ru.races.GetGoodRaceCount() 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 // GetInfo returns comprehensive information about the recipe
func (r *Recipe) GetInfo() map[string]interface{} { func (r *Recipe) GetInfo() map[string]any {
r.mutex.RLock() r.mutex.RLock()
defer r.mutex.RUnlock() defer r.mutex.RUnlock()
info := make(map[string]interface{}) info := make(map[string]any)
info["id"] = r.ID info["id"] = r.ID
info["soe_id"] = r.SoeID info["soe_id"] = r.SoeID

View File

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

View File

@ -21,7 +21,7 @@ type Client interface {
GetCurrentZone() Zone GetCurrentZone() Zone
SetTemporaryTransportID(id int32) SetTemporaryTransportID(id int32)
SimpleMessage(channel int32, message string) SimpleMessage(channel int32, message string)
Message(channel int32, format string, args ...interface{}) Message(channel int32, format string, args ...any)
CheckZoneAccess(zoneName string) bool CheckZoneAccess(zoneName string) bool
TryZoneInstance(zoneID int32, useDefaults bool) bool TryZoneInstance(zoneID int32, useDefaults bool) bool
Zone(zoneName string, useDefaults bool) error Zone(zoneName string, useDefaults bool) error
@ -66,10 +66,10 @@ type EntityCommand struct {
// Logger interface for sign logging // Logger interface for sign logging
type Logger interface { type Logger interface {
LogInfo(message string, args ...interface{}) LogInfo(message string, args ...any)
LogError(message string, args ...interface{}) LogError(message string, args ...any)
LogDebug(message string, args ...interface{}) LogDebug(message string, args ...any)
LogWarning(message string, args ...interface{}) LogWarning(message string, args ...any)
} }
// SignSpawn provides sign functionality for spawn entities // 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 // GetStatistics returns sign system statistics
func (m *Manager) GetStatistics() map[string]interface{} { func (m *Manager) GetStatistics() map[string]any {
m.mutex.RLock() m.mutex.RLock()
defer m.mutex.RUnlock() defer m.mutex.RUnlock()
stats := make(map[string]interface{}) stats := make(map[string]any)
stats["total_signs"] = m.totalSigns stats["total_signs"] = m.totalSigns
stats["sign_interactions"] = m.signInteractions stats["sign_interactions"] = m.signInteractions
stats["zone_transports"] = m.zoneTransports stats["zone_transports"] = m.zoneTransports

View File

@ -27,9 +27,9 @@ type Database interface {
// Logger interface for skill system logging // Logger interface for skill system logging
type Logger interface { type Logger interface {
LogInfo(message string, args ...interface{}) LogInfo(message string, args ...any)
LogError(message string, args ...interface{}) LogError(message string, args ...any)
LogDebug(message string, args ...interface{}) LogDebug(message string, args ...any)
} }
// EntitySkillAdapter provides skill functionality for entities // 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 // GetStatistics returns skill system statistics
func (m *Manager) GetStatistics() map[string]interface{} { func (m *Manager) GetStatistics() map[string]any {
m.mutex.RLock() m.mutex.RLock()
defer m.mutex.RUnlock() defer m.mutex.RUnlock()
stats := make(map[string]interface{}) stats := make(map[string]any)
stats["total_skill_ups"] = m.totalSkillUps stats["total_skill_ups"] = m.totalSkillUps
stats["players_with_skills"] = m.playersWithSkills stats["players_with_skills"] = m.playersWithSkills
stats["total_skills_in_master"] = m.masterSkillList.GetSkillCount() 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) // 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 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 // 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) spell := sm.masterList.GetSpell(spellID)
if spell == nil { if spell == nil {
return nil return nil
} }
info := make(map[string]interface{}) info := make(map[string]any)
// Basic spell info // Basic spell info
info["id"] = spell.GetSpellID() info["id"] = spell.GetSpellID()

View File

@ -155,7 +155,7 @@ func (st *SpellTargeting) getAETargets(luaSpell *LuaSpell, result *TargetingResu
// For now, implement basic logic // For now, implement basic logic
_ = luaSpell.Spell.GetSpellData() // TODO: Use spell data when needed _ = luaSpell.Spell.GetSpellData() // TODO: Use spell data when needed
maxTargets := int32(10) // TODO: Use spellData.AOENodeNumber when field exists maxTargets := int32(10) // TODO: Use spellData.AOENodeNumber when field exists
if maxTargets <= 0 { if maxTargets <= 0 {
maxTargets = 10 // Default limit maxTargets = 10 // Default limit
} }
@ -416,8 +416,8 @@ func (st *SpellTargeting) RequiresTarget(spell *Spell) bool {
} }
// GetTargetingInfo returns information about spell targeting requirements // GetTargetingInfo returns information about spell targeting requirements
func (st *SpellTargeting) GetTargetingInfo(spell *Spell) map[string]interface{} { func (st *SpellTargeting) GetTargetingInfo(spell *Spell) map[string]any {
info := make(map[string]interface{}) info := make(map[string]any)
if spell == nil { if spell == nil {
return info return info

View File

@ -339,11 +339,11 @@ func (mtl *MasterTitlesList) UpdateTitle(title *Title) error {
categorySlice := mtl.categorized[existing.Category] categorySlice := mtl.categorized[existing.Category]
mtl.removeFromSlice(&categorySlice, existing) mtl.removeFromSlice(&categorySlice, existing)
mtl.categorized[existing.Category] = categorySlice mtl.categorized[existing.Category] = categorySlice
sourceSlice := mtl.bySource[existing.Source] sourceSlice := mtl.bySource[existing.Source]
mtl.removeFromSlice(&sourceSlice, existing) mtl.removeFromSlice(&sourceSlice, existing)
mtl.bySource[existing.Source] = sourceSlice mtl.bySource[existing.Source] = sourceSlice
raritySlice := mtl.byRarity[existing.Rarity] raritySlice := mtl.byRarity[existing.Rarity]
mtl.removeFromSlice(&raritySlice, existing) mtl.removeFromSlice(&raritySlice, existing)
mtl.byRarity[existing.Rarity] = raritySlice mtl.byRarity[existing.Rarity] = raritySlice
@ -487,7 +487,7 @@ func (mtl *MasterTitlesList) SaveToDatabase(db *database.DB) error {
// Insert all current titles // Insert all current titles
for _, title := range mtl.titles { for _, title := range mtl.titles {
var achievementID interface{} var achievementID any
if title.AchievementID != 0 { if title.AchievementID != 0 {
achievementID = title.AchievementID achievementID = title.AchievementID
} }
@ -495,9 +495,9 @@ func (mtl *MasterTitlesList) SaveToDatabase(db *database.DB) error {
err := txDB.Exec(` err := txDB.Exec(`
INSERT INTO titles (id, name, description, category, position, source, rarity, flags, achievement_id) INSERT INTO titles (id, name, description, category, position, source, rarity, flags, achievement_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
`, title.ID, title.Name, title.Description, title.Category, `, title.ID, title.Name, title.Description, title.Category,
int(title.Position), int(title.Source), int(title.Rarity), int(title.Position), int(title.Source), int(title.Rarity),
int64(title.Flags), achievementID) int64(title.Flags), achievementID)
if err != nil { if err != nil {
return fmt.Errorf("failed to insert title %d: %w", title.ID, err) return fmt.Errorf("failed to insert title %d: %w", title.ID, err)

View File

@ -451,7 +451,7 @@ func (ptl *PlayerTitlesList) LoadFromDatabase(db *database.DB) error {
} }
// Load all titles for this player // Load all titles for this player
err := db.Query("SELECT title_id, achievement_id, granted_date, expiration_date, is_active FROM player_titles WHERE player_id = ?", err := db.Query("SELECT title_id, achievement_id, granted_date, expiration_date, is_active FROM player_titles WHERE player_id = ?",
func(row *database.Row) error { func(row *database.Row) error {
playerTitle := &PlayerTitle{ playerTitle := &PlayerTitle{
TitleID: int32(row.Int64(0)), TitleID: int32(row.Int64(0)),
@ -499,12 +499,12 @@ func (ptl *PlayerTitlesList) SaveToDatabase(db *database.DB) error {
// Insert all current titles // Insert all current titles
for _, playerTitle := range ptl.titles { for _, playerTitle := range ptl.titles {
var achievementID interface{} var achievementID any
if playerTitle.AchievementID != 0 { if playerTitle.AchievementID != 0 {
achievementID = playerTitle.AchievementID achievementID = playerTitle.AchievementID
} }
var expirationDate interface{} var expirationDate any
if !playerTitle.ExpiresAt.IsZero() { if !playerTitle.ExpiresAt.IsZero() {
expirationDate = playerTitle.ExpiresAt.Unix() expirationDate = playerTitle.ExpiresAt.Unix()
} }
@ -519,8 +519,8 @@ func (ptl *PlayerTitlesList) SaveToDatabase(db *database.DB) error {
err := txDB.Exec(` err := txDB.Exec(`
INSERT INTO player_titles (player_id, title_id, achievement_id, granted_date, expiration_date, is_active) INSERT INTO player_titles (player_id, title_id, achievement_id, granted_date, expiration_date, is_active)
VALUES (?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?)
`, ptl.playerID, playerTitle.TitleID, achievementID, `, ptl.playerID, playerTitle.TitleID, achievementID,
playerTitle.EarnedDate.Unix(), expirationDate, isActive) playerTitle.EarnedDate.Unix(), expirationDate, isActive)
if err != nil { if err != nil {
return fmt.Errorf("failed to insert player title %d: %w", playerTitle.TitleID, err) return fmt.Errorf("failed to insert player title %d: %w", playerTitle.TitleID, err)

View File

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

View File

@ -146,7 +146,7 @@ func (ts *TradeService) CancelTrade(entityID int32) error {
} }
// GetTradeInfo returns comprehensive information about a trade // 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) trade := ts.tradeManager.GetTrade(entityID)
if trade == nil { if trade == nil {
return nil, fmt.Errorf("entity is not in a trade") return nil, fmt.Errorf("entity is not in a trade")
@ -174,8 +174,8 @@ func (ts *TradeService) ProcessTrades() {
} }
// GetTradeStatistics returns statistics about trade activity // GetTradeStatistics returns statistics about trade activity
func (ts *TradeService) GetTradeStatistics() map[string]interface{} { func (ts *TradeService) GetTradeStatistics() map[string]any {
stats := make(map[string]interface{}) stats := make(map[string]any)
stats["active_trades"] = ts.tradeManager.GetActiveTradeCount() stats["active_trades"] = ts.tradeManager.GetActiveTradeCount()
stats["max_trade_duration_minutes"] = ts.maxTradeDuration.Minutes() 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 // GetTradeInfo returns comprehensive information about the trade
func (t *Trade) GetTradeInfo() map[string]interface{} { func (t *Trade) GetTradeInfo() map[string]any {
t.mutex.RLock() t.mutex.RLock()
defer t.mutex.RUnlock() defer t.mutex.RUnlock()
info := make(map[string]interface{}) info := make(map[string]any)
info["state"] = t.state info["state"] = t.state
info["start_time"] = t.startTime info["start_time"] = t.startTime
info["trader1_id"] = t.trader1.EntityID 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 // 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) 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 // CalculateTradeValue estimates the total value of items and coins in a trade
// This is a helper function for trade balancing and analysis // This is a helper function for trade balancing and analysis
func CalculateTradeValue(participant *TradeParticipant) map[string]interface{} { func CalculateTradeValue(participant *TradeParticipant) map[string]any {
value := make(map[string]interface{}) value := make(map[string]any)
// Add coin value // Add coin value
value["coins"] = participant.Coins value["coins"] = participant.Coins
@ -159,9 +159,9 @@ func CalculateTradeValue(participant *TradeParticipant) map[string]interface{} {
value["item_count"] = itemCount value["item_count"] = itemCount
if itemCount > 0 { if itemCount > 0 {
items := make([]map[string]interface{}, 0, itemCount) items := make([]map[string]any, 0, itemCount)
for slot, itemInfo := range participant.Items { for slot, itemInfo := range participant.Items {
itemData := make(map[string]interface{}) itemData := make(map[string]any)
itemData["slot"] = slot itemData["slot"] = slot
itemData["quantity"] = itemInfo.Quantity itemData["quantity"] = itemInfo.Quantity
if itemInfo.Item != nil { 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. // 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() managerStats := tsa.manager.GetStats()
eventsStats := tsa.eventsList.GetStats() eventsStats := tsa.eventsList.GetStats()
return map[string]interface{}{ return map[string]any{
"active_sessions": managerStats.ActiveSessions, "active_sessions": managerStats.ActiveSessions,
"recent_completions": managerStats.RecentCompletions, "recent_completions": managerStats.RecentCompletions,
"average_session_time": managerStats.AverageSessionTime, "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. // 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 // Get player information
player, err := tsa.playerManager.GetPlayer(playerID) player, err := tsa.playerManager.GetPlayer(playerID)
if err != nil { if err != nil {
@ -381,7 +381,7 @@ func (tsa *TraitSystemAdapter) GetPlayerTraitStats(playerID uint32) (map[string]
tsa.masterList.getSpellCount(playerState, playerState.TraitLists.InnateRaceTraits, false) tsa.masterList.getSpellCount(playerState, playerState.TraitLists.InnateRaceTraits, false)
focusEffects := tsa.masterList.getSpellCount(playerState, playerState.TraitLists.FocusEffects, false) focusEffects := tsa.masterList.getSpellCount(playerState, playerState.TraitLists.FocusEffects, false)
return map[string]interface{}{ return map[string]any{
"player_id": playerID, "player_id": playerID,
"level": playerState.Level, "level": playerState.Level,
"character_traits": characterTraits, "character_traits": characterTraits,
@ -394,11 +394,11 @@ func (tsa *TraitSystemAdapter) GetPlayerTraitStats(playerID uint32) (map[string]
} }
// GetSystemStats returns comprehensive statistics about the trait system. // 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() masterStats := tsa.masterList.GetStats()
managerStats := tsa.traitManager.GetManagerStats() managerStats := tsa.traitManager.GetManagerStats()
return map[string]interface{}{ return map[string]any{
"total_traits": masterStats.TotalTraits, "total_traits": masterStats.TotalTraits,
"traits_by_type": masterStats.TraitsByType, "traits_by_type": masterStats.TraitsByType,
"traits_by_group": masterStats.TraitsByGroup, "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. // 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) count := int16(0)
switch tm := traitMap.(type) { switch tm := traitMap.(type) {

View File

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

View File

@ -71,7 +71,7 @@ type Client interface {
SetTransmuteID(id int32) SetTransmuteID(id int32)
QueuePacket(packet []byte) QueuePacket(packet []byte)
SimpleMessage(channel int32, message string) 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 AddItem(item Item, itemDeleted *bool) error
} }

View File

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

View File

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

View File

@ -97,8 +97,8 @@ type NPC interface {
SetRespawnTime(seconds int32) SetRespawnTime(seconds int32)
GetSpawnGroupID() int32 GetSpawnGroupID() int32
SetSpawnGroupID(groupID int32) SetSpawnGroupID(groupID int32)
GetRandomizedFeatures() map[string]interface{} GetRandomizedFeatures() map[string]any
SetRandomizedFeatures(features map[string]interface{}) SetRandomizedFeatures(features map[string]any)
} }
// Object interface represents an interactive world object // Object interface represents an interactive world object
@ -337,12 +337,12 @@ type Recipe interface {
// MovementLocation represents a movement waypoint for NPCs // MovementLocation represents a movement waypoint for NPCs
type MovementLocation struct { type MovementLocation struct {
X float32 X float32
Y float32 Y float32
Z float32 Z float32
Heading float32 Heading float32
Speed float32 Speed float32
Delay int32 Delay int32
MovementType int8 MovementType int8
} }
@ -357,7 +357,7 @@ type PathNode struct {
type SpawnLocation struct { type SpawnLocation struct {
ID int32 ID int32
X float32 X float32
Y float32 Y float32
Z float32 Z float32
Heading float32 Heading float32
Pitch float32 Pitch float32
@ -375,46 +375,46 @@ type SpawnLocation struct {
// SpawnEntry contains the template data for spawns // SpawnEntry contains the template data for spawns
type SpawnEntry struct { type SpawnEntry struct {
ID int32 ID int32
SpawnType int8 SpawnType int8
SpawnEntryID int32 SpawnEntryID int32
Name string Name string
Level int16 Level int16
EncounterLevel int16 EncounterLevel int16
Model string Model string
Size float32 Size float32
HP int32 HP int32
Power int32 Power int32
Heroic int8 Heroic int8
Gender int8 Gender int8
Race int16 Race int16
AdventureClass int16 AdventureClass int16
TradeskillClass int16 TradeskillClass int16
AttackType int8 AttackType int8
MinLevel int16 MinLevel int16
MaxLevel int16 MaxLevel int16
EncounterType int8 EncounterType int8
ShowName int8 ShowName int8
Targetable int8 Targetable int8
ShowLevel int8 ShowLevel int8
Command string Command string
LootTier int8 LootTier int8
MinGold int32 MinGold int32
MaxGold int32 MaxGold int32
HarvestType string HarvestType string
Icon int32 Icon int32
} }
// EntityCommand represents an available command for an entity // EntityCommand represents an available command for an entity
type EntityCommand struct { type EntityCommand struct {
ID int32 ID int32
Name string Name string
Distance float32 Distance float32
ErrorText string ErrorText string
CastTime int16 CastTime int16
SpellVisual int32 SpellVisual int32
Command string Command string
DisplayText string DisplayText string
} }
// LootTable represents a loot table configuration // LootTable represents a loot table configuration
@ -433,9 +433,9 @@ type LootTable struct {
type LootDrop struct { type LootDrop struct {
LootTableID int32 LootTableID int32
ItemID int32 ItemID int32
ItemCharges int16 ItemCharges int16
EquipItem bool EquipItem bool
Probability float32 Probability float32
NoDropQuestCompletedID int32 NoDropQuestCompletedID int32
} }
@ -452,51 +452,51 @@ type GlobalLoot struct {
// TransportDestination represents a transport destination // TransportDestination represents a transport destination
type TransportDestination struct { type TransportDestination struct {
ID int32 ID int32
Type int8 Type int8
Name string Name string
Message string Message string
DestinationZoneID int32 DestinationZoneID int32
DestinationX float32 DestinationX float32
DestinationY float32 DestinationY float32
DestinationZ float32 DestinationZ float32
DestinationHeading float32 DestinationHeading float32
Cost int32 Cost int32
UniqueID int32 UniqueID int32
MinLevel int8 MinLevel int8
MaxLevel int8 MaxLevel int8
QuestRequired int32 QuestRequired int32
QuestStepRequired int16 QuestStepRequired int16
QuestCompleted int32 QuestCompleted int32
MapX int32 MapX int32
MapY int32 MapY int32
ExpansionFlag int32 ExpansionFlag int32
HolidayFlag int32 HolidayFlag int32
MinClientVersion int32 MinClientVersion int32
MaxClientVersion int32 MaxClientVersion int32
FlightPathID int32 FlightPathID int32
MountID int16 MountID int16
MountRedColor int8 MountRedColor int8
MountGreenColor int8 MountGreenColor int8
MountBlueColor int8 MountBlueColor int8
} }
// LocationTransportDestination represents a location-based transport trigger // LocationTransportDestination represents a location-based transport trigger
type LocationTransportDestination struct { type LocationTransportDestination struct {
ZoneID int32 ZoneID int32
Message string Message string
TriggerX float32 TriggerX float32
TriggerY float32 TriggerY float32
TriggerZ float32 TriggerZ float32
TriggerRadius float32 TriggerRadius float32
DestinationZoneID int32 DestinationZoneID int32
DestinationX float32 DestinationX float32
DestinationY float32 DestinationY float32
DestinationZ float32 DestinationZ float32
DestinationHeading float32 DestinationHeading float32
Cost int32 Cost int32
UniqueID int32 UniqueID int32
ForceZone bool ForceZone bool
} }
// Location represents a discoverable location // Location represents a discoverable location
@ -509,4 +509,4 @@ type Location struct {
} }
// Item placeholder - should import from items package // Item placeholder - should import from items package
type Item = items.Item type Item = items.Item