implement tests and beches for ground_spawn package

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

View File

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

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
func (db *DatabaseImpl) GetAAStatistics() (map[string]interface{}, error) {
stats := make(map[string]interface{})
func (db *DatabaseImpl) GetAAStatistics() (map[string]any, error) {
stats := make(map[string]any)
// Get total players with AA data
var totalPlayers int64

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,13 +18,13 @@ type LootSystem struct {
// LootSystemConfig holds configuration for the loot system
type LootSystemConfig struct {
DatabaseConnection *sql.DB
ItemMasterList items.MasterItemListService
PlayerService PlayerService
ZoneService ZoneService
ClientService ClientService
ItemPacketBuilder ItemPacketBuilder
StartCleanupTimer bool
DatabaseConnection *sql.DB
ItemMasterList items.MasterItemListService
PlayerService PlayerService
ZoneService ZoneService
ClientService ClientService
ItemPacketBuilder ItemPacketBuilder
StartCleanupTimer bool
}
// 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
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) {
if ls.ChestService == nil {
@ -98,7 +98,7 @@ func (ls *LootSystem) GenerateAndCreateChest(spawnID int32, zoneID int32, x, y,
}
// 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)
if err != nil {
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
func (ls *LootSystem) HandlePlayerLootInteraction(chestID int32, playerID uint32,
func (ls *LootSystem) HandlePlayerLootInteraction(chestID int32, playerID uint32,
interaction ChestInteraction, itemUniqueID int64) error {
if ls.ChestService == nil {
@ -141,7 +141,7 @@ func (ls *LootSystem) HandlePlayerLootInteraction(chestID int32, playerID uint32
}
// 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)
return nil
@ -168,8 +168,8 @@ func (ls *LootSystem) ShowChestToPlayer(chestID int32, playerID uint32) error {
}
// GetSystemStatistics returns comprehensive statistics about the loot system
func (ls *LootSystem) GetSystemStatistics() (map[string]interface{}, error) {
stats := make(map[string]interface{})
func (ls *LootSystem) GetSystemStatistics() (map[string]any, error) {
stats := make(map[string]any)
// Database statistics
if dbStats, err := ls.Database.GetLootStatistics(); err == nil {
@ -220,7 +220,7 @@ func (ls *LootSystem) AddLootTableWithDrops(table *LootTable) error {
}
// 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 {
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.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)
return nil
}
@ -303,10 +303,10 @@ func (ls *LootSystem) GetActiveChestsInZone(zoneID int32) []*TreasureChest {
// CleanupZoneChests removes all chests from a specific zone
func (ls *LootSystem) CleanupZoneChests(zoneID int32) {
chests := ls.Manager.GetZoneChests(zoneID)
for _, chest := range chests {
ls.Manager.RemoveTreasureChest(chest.ID)
// Remove from zone if chest service is available
if ls.ChestService != nil {
ls.ChestService.zoneService.RemoveObjectFromZone(zoneID, chest.ID)
@ -356,17 +356,17 @@ type ValidationError struct {
func (ls *LootSystem) GetLootPreview(spawnID int32, context *LootContext) (*LootPreview, error) {
tableIDs := ls.Database.GetSpawnLootTables(spawnID)
globalLoot := ls.Database.GetGlobalLootTables(context.PlayerLevel, context.PlayerRace, context.ZoneID)
for _, global := range globalLoot {
tableIDs = append(tableIDs, global.TableID)
}
preview := &LootPreview{
SpawnID: spawnID,
TableIDs: tableIDs,
SpawnID: spawnID,
TableIDs: tableIDs,
PossibleItems: make([]*LootPreviewItem, 0),
MinCoins: 0,
MaxCoins: 0,
MinCoins: 0,
MaxCoins: 0,
}
for _, tableID := range tableIDs {
@ -400,11 +400,11 @@ func (ls *LootSystem) GetLootPreview(spawnID int32, context *LootContext) (*Loot
// LootPreview represents a preview of potential loot
type LootPreview struct {
SpawnID int32 `json:"spawn_id"`
TableIDs []int32 `json:"table_ids"`
PossibleItems []*LootPreviewItem `json:"possible_items"`
MinCoins int32 `json:"min_coins"`
MaxCoins int32 `json:"max_coins"`
SpawnID int32 `json:"spawn_id"`
TableIDs []int32 `json:"table_ids"`
PossibleItems []*LootPreviewItem `json:"possible_items"`
MinCoins int32 `json:"min_coins"`
MaxCoins int32 `json:"max_coins"`
}
// LootPreviewItem represents a potential loot item in a preview
@ -413,4 +413,4 @@ type LootPreviewItem struct {
ItemName string `json:"item_name"`
Probability float32 `json:"probability"`
Tier int8 `json:"tier"`
}
}

View File

@ -106,18 +106,18 @@ func (m *MockPlayerService) SetInventorySpace(playerID uint32, space int) {
// MockZoneService implements ZoneService for testing
type MockZoneService struct {
rules map[int32]map[string]interface{}
objects map[int32]map[int32]interface{} // zoneID -> objectID
rules map[int32]map[string]any
objects map[int32]map[int32]any // zoneID -> objectID
}
func NewMockZoneService() *MockZoneService {
return &MockZoneService{
rules: make(map[int32]map[string]interface{}),
objects: make(map[int32]map[int32]interface{}),
rules: make(map[int32]map[string]any),
objects: make(map[int32]map[int32]any),
}
}
func (m *MockZoneService) GetZoneRule(zoneID int32, ruleName string) (interface{}, error) {
func (m *MockZoneService) GetZoneRule(zoneID int32, ruleName string) (any, error) {
if rules, exists := m.rules[zoneID]; exists {
return rules[ruleName], nil
}
@ -127,7 +127,7 @@ func (m *MockZoneService) GetZoneRule(zoneID int32, ruleName string) (interface{
func (m *MockZoneService) SpawnObjectInZone(zoneID int32, appearanceID int32, x, y, z, heading float32, name string, commands []string) (int32, error) {
objectID := int32(len(m.objects[zoneID]) + 1)
if m.objects[zoneID] == nil {
m.objects[zoneID] = make(map[int32]interface{})
m.objects[zoneID] = make(map[int32]any)
}
m.objects[zoneID][objectID] = struct{}{}
return objectID, nil
@ -382,7 +382,7 @@ func TestTreasureChestCreation(t *testing.T) {
}
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)
}
@ -530,7 +530,7 @@ func TestChestAppearanceSelection(t *testing.T) {
for _, tc := range testCases {
appearance := GetChestAppearance(tc.tier)
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)
}
}
@ -667,4 +667,4 @@ func BenchmarkChestInteraction(b *testing.B) {
for i := 0; i < b.N; i++ {
chestService.HandleChestInteraction(chest.ID, 1, ChestInteractionView, 0)
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -155,7 +155,7 @@ func (st *SpellTargeting) getAETargets(luaSpell *LuaSpell, result *TargetingResu
// For now, implement basic logic
_ = 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 {
maxTargets = 10 // Default limit
}
@ -416,8 +416,8 @@ func (st *SpellTargeting) RequiresTarget(spell *Spell) bool {
}
// GetTargetingInfo returns information about spell targeting requirements
func (st *SpellTargeting) GetTargetingInfo(spell *Spell) map[string]interface{} {
info := make(map[string]interface{})
func (st *SpellTargeting) GetTargetingInfo(spell *Spell) map[string]any {
info := make(map[string]any)
if spell == nil {
return info

View File

@ -339,11 +339,11 @@ func (mtl *MasterTitlesList) UpdateTitle(title *Title) error {
categorySlice := mtl.categorized[existing.Category]
mtl.removeFromSlice(&categorySlice, existing)
mtl.categorized[existing.Category] = categorySlice
sourceSlice := mtl.bySource[existing.Source]
mtl.removeFromSlice(&sourceSlice, existing)
mtl.bySource[existing.Source] = sourceSlice
raritySlice := mtl.byRarity[existing.Rarity]
mtl.removeFromSlice(&raritySlice, existing)
mtl.byRarity[existing.Rarity] = raritySlice
@ -487,7 +487,7 @@ func (mtl *MasterTitlesList) SaveToDatabase(db *database.DB) error {
// Insert all current titles
for _, title := range mtl.titles {
var achievementID interface{}
var achievementID any
if title.AchievementID != 0 {
achievementID = title.AchievementID
}
@ -495,9 +495,9 @@ func (mtl *MasterTitlesList) SaveToDatabase(db *database.DB) error {
err := txDB.Exec(`
INSERT INTO titles (id, name, description, category, position, source, rarity, flags, achievement_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
`, title.ID, title.Name, title.Description, title.Category,
int(title.Position), int(title.Source), int(title.Rarity),
int64(title.Flags), achievementID)
`, title.ID, title.Name, title.Description, title.Category,
int(title.Position), int(title.Source), int(title.Rarity),
int64(title.Flags), achievementID)
if err != nil {
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
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 {
playerTitle := &PlayerTitle{
TitleID: int32(row.Int64(0)),
@ -499,12 +499,12 @@ func (ptl *PlayerTitlesList) SaveToDatabase(db *database.DB) error {
// Insert all current titles
for _, playerTitle := range ptl.titles {
var achievementID interface{}
var achievementID any
if playerTitle.AchievementID != 0 {
achievementID = playerTitle.AchievementID
}
var expirationDate interface{}
var expirationDate any
if !playerTitle.ExpiresAt.IsZero() {
expirationDate = playerTitle.ExpiresAt.Unix()
}
@ -519,8 +519,8 @@ func (ptl *PlayerTitlesList) SaveToDatabase(db *database.DB) error {
err := txDB.Exec(`
INSERT INTO player_titles (player_id, title_id, achievement_id, granted_date, expiration_date, is_active)
VALUES (?, ?, ?, ?, ?, ?)
`, ptl.playerID, playerTitle.TitleID, achievementID,
playerTitle.EarnedDate.Unix(), expirationDate, isActive)
`, ptl.playerID, playerTitle.TitleID, achievementID,
playerTitle.EarnedDate.Unix(), expirationDate, isActive)
if err != nil {
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
func (tm *TitleManager) GetStatistics() map[string]interface{} {
func (tm *TitleManager) GetStatistics() map[string]any {
tm.mutex.RLock()
defer tm.mutex.RUnlock()
return map[string]interface{}{
return map[string]any{
"total_titles": tm.masterList.GetTitleCount(),
"total_players": len(tm.playerLists),
"titles_granted": tm.totalTitlesGranted,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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