implement tests and beches for ground_spawn package
This commit is contained in:
parent
0889eae1cf
commit
0534c49610
@ -368,17 +368,17 @@ fmt.Printf("Cache hits: %d, misses: %d\n",
|
|||||||
// Custom packet handler
|
// Custom packet handler
|
||||||
type MyAAPacketHandler struct{}
|
type MyAAPacketHandler struct{}
|
||||||
|
|
||||||
func (ph *MyAAPacketHandler) GetAAListPacket(client interface{}) ([]byte, error) {
|
func (ph *MyAAPacketHandler) GetAAListPacket(client any) ([]byte, error) {
|
||||||
// Build AA list packet for client
|
// Build AA list packet for client
|
||||||
return []byte{}, nil
|
return []byte{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ph *MyAAPacketHandler) SendAAUpdate(client interface{}, playerState *AAPlayerState) error {
|
func (ph *MyAAPacketHandler) SendAAUpdate(client any, playerState *AAPlayerState) error {
|
||||||
// Send AA update to client
|
// Send AA update to client
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ph *MyAAPacketHandler) HandleAAPurchase(client interface{}, nodeID int32, rank int8) error {
|
func (ph *MyAAPacketHandler) HandleAAPurchase(client any, nodeID int32, rank int8) error {
|
||||||
// Handle AA purchase from client
|
// Handle AA purchase from client
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -514,8 +514,8 @@ func (db *DatabaseImpl) DeletePlayerAA(characterID int32) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetAAStatistics returns statistics about AA usage
|
// GetAAStatistics returns statistics about AA usage
|
||||||
func (db *DatabaseImpl) GetAAStatistics() (map[string]interface{}, error) {
|
func (db *DatabaseImpl) GetAAStatistics() (map[string]any, error) {
|
||||||
stats := make(map[string]interface{})
|
stats := make(map[string]any)
|
||||||
|
|
||||||
// Get total players with AA data
|
// Get total players with AA data
|
||||||
var totalPlayers int64
|
var totalPlayers int64
|
||||||
|
@ -22,26 +22,26 @@ type AADatabase interface {
|
|||||||
LoadPlayerAADefaults(classID int8) (map[int8][]*AAEntry, error)
|
LoadPlayerAADefaults(classID int8) (map[int8][]*AAEntry, error)
|
||||||
|
|
||||||
// Statistics
|
// Statistics
|
||||||
GetAAStatistics() (map[string]interface{}, error)
|
GetAAStatistics() (map[string]any, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AAPacketHandler interface for handling AA-related packets
|
// AAPacketHandler interface for handling AA-related packets
|
||||||
type AAPacketHandler interface {
|
type AAPacketHandler interface {
|
||||||
// List packets
|
// List packets
|
||||||
GetAAListPacket(client interface{}) ([]byte, error)
|
GetAAListPacket(client any) ([]byte, error)
|
||||||
SendAAUpdate(client interface{}, playerState *AAPlayerState) error
|
SendAAUpdate(client any, playerState *AAPlayerState) error
|
||||||
|
|
||||||
// Purchase packets
|
// Purchase packets
|
||||||
HandleAAPurchase(client interface{}, nodeID int32, rank int8) error
|
HandleAAPurchase(client any, nodeID int32, rank int8) error
|
||||||
SendAAPurchaseResponse(client interface{}, success bool, nodeID int32, newRank int8) error
|
SendAAPurchaseResponse(client any, success bool, nodeID int32, newRank int8) error
|
||||||
|
|
||||||
// Template packets
|
// Template packets
|
||||||
SendAATemplateList(client interface{}, templates map[int8]*AATemplate) error
|
SendAATemplateList(client any, templates map[int8]*AATemplate) error
|
||||||
HandleAATemplateChange(client interface{}, templateID int8) error
|
HandleAATemplateChange(client any, templateID int8) error
|
||||||
|
|
||||||
// Display packets
|
// Display packets
|
||||||
DisplayAA(client interface{}, templateID int8, changeMode int8) error
|
DisplayAA(client any, templateID int8, changeMode int8) error
|
||||||
SendAATabUpdate(client interface{}, tabID int8, tab *AATab) error
|
SendAATabUpdate(client any, tabID int8, tab *AATab) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// AAEventHandler interface for handling AA events
|
// AAEventHandler interface for handling AA events
|
||||||
@ -124,8 +124,8 @@ type AAStatistics interface {
|
|||||||
// Aggregated statistics
|
// Aggregated statistics
|
||||||
GetAAPurchaseStats() map[int32]int64
|
GetAAPurchaseStats() map[int32]int64
|
||||||
GetPopularAAs() map[int32]int64
|
GetPopularAAs() map[int32]int64
|
||||||
GetPlayerProgressStats() map[string]interface{}
|
GetPlayerProgressStats() map[string]any
|
||||||
GetSystemPerformanceStats() map[string]interface{}
|
GetSystemPerformanceStats() map[string]any
|
||||||
}
|
}
|
||||||
|
|
||||||
// AACache interface for caching AA data
|
// AACache interface for caching AA data
|
||||||
@ -147,7 +147,7 @@ type AACache interface {
|
|||||||
|
|
||||||
// Cache management
|
// Cache management
|
||||||
Clear()
|
Clear()
|
||||||
GetStats() map[string]interface{}
|
GetStats() map[string]any
|
||||||
SetMaxSize(maxSize int32)
|
SetMaxSize(maxSize int32)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,9 +172,9 @@ type Player interface {
|
|||||||
|
|
||||||
// Transaction interface for database transactions
|
// Transaction interface for database transactions
|
||||||
type Transaction interface {
|
type Transaction interface {
|
||||||
Exec(query string, args ...interface{}) (sql.Result, error)
|
Exec(query string, args ...any) (sql.Result, error)
|
||||||
Query(query string, args ...interface{}) (*sql.Rows, error)
|
Query(query string, args ...any) (*sql.Rows, error)
|
||||||
QueryRow(query string, args ...interface{}) *sql.Row
|
QueryRow(query string, args ...any) *sql.Row
|
||||||
Commit() error
|
Commit() error
|
||||||
Rollback() error
|
Rollback() error
|
||||||
}
|
}
|
||||||
@ -235,7 +235,7 @@ type AAManagerInterface interface {
|
|||||||
|
|
||||||
// Statistics
|
// Statistics
|
||||||
GetSystemStats() *AAManagerStats
|
GetSystemStats() *AAManagerStats
|
||||||
GetPlayerStats(characterID int32) map[string]interface{}
|
GetPlayerStats(characterID int32) map[string]any
|
||||||
|
|
||||||
// Configuration
|
// Configuration
|
||||||
SetConfig(config AAManagerConfig) error
|
SetConfig(config AAManagerConfig) error
|
||||||
@ -327,7 +327,7 @@ func (aa *AAAdapter) GetTemplates() (map[int8]*AATemplate, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetPlayerStats returns AA statistics for the character
|
// GetPlayerStats returns AA statistics for the character
|
||||||
func (aa *AAAdapter) GetPlayerStats() map[string]interface{} {
|
func (aa *AAAdapter) GetPlayerStats() map[string]any {
|
||||||
return aa.manager.GetPlayerStats(aa.characterID)
|
return aa.manager.GetPlayerStats(aa.characterID)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -579,11 +579,11 @@ func (c *SimpleAACache) Clear() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetStats returns cache statistics
|
// GetStats returns cache statistics
|
||||||
func (c *SimpleAACache) GetStats() map[string]interface{} {
|
func (c *SimpleAACache) GetStats() map[string]any {
|
||||||
c.mutex.RLock()
|
c.mutex.RLock()
|
||||||
defer c.mutex.RUnlock()
|
defer c.mutex.RUnlock()
|
||||||
|
|
||||||
return map[string]interface{}{
|
return map[string]any{
|
||||||
"hits": c.hits,
|
"hits": c.hits,
|
||||||
"misses": c.misses,
|
"misses": c.misses,
|
||||||
"aa_data_count": len(c.aaData),
|
"aa_data_count": len(c.aaData),
|
||||||
|
@ -164,7 +164,7 @@ func (am *AAManager) GetPlayerAAState(characterID int32) (*AAPlayerState, error)
|
|||||||
// Need to load from database, use write lock to prevent race condition
|
// Need to load from database, use write lock to prevent race condition
|
||||||
am.statesMutex.Lock()
|
am.statesMutex.Lock()
|
||||||
defer am.statesMutex.Unlock()
|
defer am.statesMutex.Unlock()
|
||||||
|
|
||||||
// Double-check pattern: another goroutine might have loaded it while we waited
|
// Double-check pattern: another goroutine might have loaded it while we waited
|
||||||
if playerState, exists := am.playerStates[characterID]; exists {
|
if playerState, exists := am.playerStates[characterID]; exists {
|
||||||
return playerState, nil
|
return playerState, nil
|
||||||
@ -456,16 +456,16 @@ func (am *AAManager) GetSystemStats() *AAManagerStats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetPlayerStats returns player-specific statistics
|
// GetPlayerStats returns player-specific statistics
|
||||||
func (am *AAManager) GetPlayerStats(characterID int32) map[string]interface{} {
|
func (am *AAManager) GetPlayerStats(characterID int32) map[string]any {
|
||||||
playerState := am.getPlayerState(characterID)
|
playerState := am.getPlayerState(characterID)
|
||||||
if playerState == nil {
|
if playerState == nil {
|
||||||
return map[string]interface{}{"error": "player not found"}
|
return map[string]any{"error": "player not found"}
|
||||||
}
|
}
|
||||||
|
|
||||||
playerState.mutex.RLock()
|
playerState.mutex.RLock()
|
||||||
defer playerState.mutex.RUnlock()
|
defer playerState.mutex.RUnlock()
|
||||||
|
|
||||||
return map[string]interface{}{
|
return map[string]any{
|
||||||
"character_id": characterID,
|
"character_id": characterID,
|
||||||
"total_points": playerState.TotalPoints,
|
"total_points": playerState.TotalPoints,
|
||||||
"spent_points": playerState.SpentPoints,
|
"spent_points": playerState.SpentPoints,
|
||||||
|
@ -251,11 +251,11 @@ func (mal *MasterAAList) ValidateAAData() []error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetStats returns statistics about the master AA list
|
// GetStats returns statistics about the master AA list
|
||||||
func (mal *MasterAAList) GetStats() map[string]interface{} {
|
func (mal *MasterAAList) GetStats() map[string]any {
|
||||||
mal.mutex.RLock()
|
mal.mutex.RLock()
|
||||||
defer mal.mutex.RUnlock()
|
defer mal.mutex.RUnlock()
|
||||||
|
|
||||||
stats := make(map[string]interface{})
|
stats := make(map[string]any)
|
||||||
stats[STAT_TOTAL_AAS_LOADED] = mal.totalLoaded
|
stats[STAT_TOTAL_AAS_LOADED] = mal.totalLoaded
|
||||||
stats["last_load_time"] = mal.lastLoadTime
|
stats["last_load_time"] = mal.lastLoadTime
|
||||||
stats["groups_count"] = len(mal.aaByGroup)
|
stats["groups_count"] = len(mal.aaByGroup)
|
||||||
@ -429,11 +429,11 @@ func (manl *MasterAANodeList) ValidateTreeNodes() []error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetStats returns statistics about the master node list
|
// GetStats returns statistics about the master node list
|
||||||
func (manl *MasterAANodeList) GetStats() map[string]interface{} {
|
func (manl *MasterAANodeList) GetStats() map[string]any {
|
||||||
manl.mutex.RLock()
|
manl.mutex.RLock()
|
||||||
defer manl.mutex.RUnlock()
|
defer manl.mutex.RUnlock()
|
||||||
|
|
||||||
stats := make(map[string]interface{})
|
stats := make(map[string]any)
|
||||||
stats[STAT_TOTAL_NODES_LOADED] = manl.totalLoaded
|
stats[STAT_TOTAL_NODES_LOADED] = manl.totalLoaded
|
||||||
stats["last_load_time"] = manl.lastLoadTime
|
stats["last_load_time"] = manl.lastLoadTime
|
||||||
stats["classes_count"] = len(manl.nodesByClass)
|
stats["classes_count"] = len(manl.nodesByClass)
|
||||||
|
@ -227,11 +227,11 @@ func (a *Appearances) IsValid() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetStatistics returns statistics about the appearance collection
|
// GetStatistics returns statistics about the appearance collection
|
||||||
func (a *Appearances) GetStatistics() map[string]interface{} {
|
func (a *Appearances) GetStatistics() map[string]any {
|
||||||
a.mutex.RLock()
|
a.mutex.RLock()
|
||||||
defer a.mutex.RUnlock()
|
defer a.mutex.RUnlock()
|
||||||
|
|
||||||
stats := make(map[string]interface{})
|
stats := make(map[string]any)
|
||||||
stats["total_appearances"] = len(a.appearanceMap)
|
stats["total_appearances"] = len(a.appearanceMap)
|
||||||
|
|
||||||
// Count by minimum client version
|
// Count by minimum client version
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -15,10 +15,10 @@ type Database interface {
|
|||||||
|
|
||||||
// Logger interface for appearance logging
|
// Logger interface for appearance logging
|
||||||
type Logger interface {
|
type Logger interface {
|
||||||
LogInfo(message string, args ...interface{})
|
LogInfo(message string, args ...any)
|
||||||
LogError(message string, args ...interface{})
|
LogError(message string, args ...any)
|
||||||
LogDebug(message string, args ...interface{})
|
LogDebug(message string, args ...any)
|
||||||
LogWarning(message string, args ...interface{})
|
LogWarning(message string, args ...any)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AppearanceProvider interface for entities that provide appearances
|
// AppearanceProvider interface for entities that provide appearances
|
||||||
|
@ -201,7 +201,7 @@ func (m *Manager) SearchAppearancesByName(nameSubstring string) []*Appearance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetStatistics returns appearance system statistics
|
// GetStatistics returns appearance system statistics
|
||||||
func (m *Manager) GetStatistics() map[string]interface{} {
|
func (m *Manager) GetStatistics() map[string]any {
|
||||||
m.mutex.RLock()
|
m.mutex.RLock()
|
||||||
defer m.mutex.RUnlock()
|
defer m.mutex.RUnlock()
|
||||||
|
|
||||||
|
@ -329,11 +329,11 @@ func (c *Classes) GetClassCount() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetClassInfo returns comprehensive information about a class
|
// GetClassInfo returns comprehensive information about a class
|
||||||
func (c *Classes) GetClassInfo(classID int8) map[string]interface{} {
|
func (c *Classes) GetClassInfo(classID int8) map[string]any {
|
||||||
c.mutex.RLock()
|
c.mutex.RLock()
|
||||||
defer c.mutex.RUnlock()
|
defer c.mutex.RUnlock()
|
||||||
|
|
||||||
info := make(map[string]interface{})
|
info := make(map[string]any)
|
||||||
|
|
||||||
if !c.IsValidClassID(classID) {
|
if !c.IsValidClassID(classID) {
|
||||||
info["valid"] = false
|
info["valid"] = false
|
||||||
|
@ -1185,7 +1185,7 @@ func TestGetClassSelectionData(t *testing.T) {
|
|||||||
t.Error("Selection data should include statistics")
|
t.Error("Selection data should include statistics")
|
||||||
}
|
}
|
||||||
|
|
||||||
adventureClasses := selectionData["adventure_classes"].([]map[string]interface{})
|
adventureClasses := selectionData["adventure_classes"].([]map[string]any)
|
||||||
if len(adventureClasses) == 0 {
|
if len(adventureClasses) == 0 {
|
||||||
t.Error("Selection data should include adventure classes")
|
t.Error("Selection data should include adventure classes")
|
||||||
}
|
}
|
||||||
@ -1442,7 +1442,7 @@ func TestGetClassRecommendations(t *testing.T) {
|
|||||||
manager := NewClassManager()
|
manager := NewClassManager()
|
||||||
|
|
||||||
// Test recommendations by class type
|
// Test recommendations by class type
|
||||||
preferences := map[string]interface{}{
|
preferences := map[string]any{
|
||||||
"class_type": ClassTypeAdventure,
|
"class_type": ClassTypeAdventure,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1459,7 +1459,7 @@ func TestGetClassRecommendations(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Test recommendations by base class
|
// Test recommendations by base class
|
||||||
basePreferences := map[string]interface{}{
|
basePreferences := map[string]any{
|
||||||
"base_class": ClassFighter,
|
"base_class": ClassFighter,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1469,7 +1469,7 @@ func TestGetClassRecommendations(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Test recommendations by preferred stats
|
// Test recommendations by preferred stats
|
||||||
statPreferences := map[string]interface{}{
|
statPreferences := map[string]any{
|
||||||
"preferred_stats": []string{"strength", "stamina"},
|
"preferred_stats": []string{"strength", "stamina"},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1479,7 +1479,7 @@ func TestGetClassRecommendations(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Test empty preferences (should get defaults)
|
// Test empty preferences (should get defaults)
|
||||||
emptyPreferences := map[string]interface{}{}
|
emptyPreferences := map[string]any{}
|
||||||
defaultRecommendations := manager.GetClassRecommendations(emptyPreferences)
|
defaultRecommendations := manager.GetClassRecommendations(emptyPreferences)
|
||||||
if len(defaultRecommendations) == 0 {
|
if len(defaultRecommendations) == 0 {
|
||||||
t.Error("Should get default recommendations when no preferences given")
|
t.Error("Should get default recommendations when no preferences given")
|
||||||
|
@ -33,7 +33,7 @@ func NewClassIntegration() *ClassIntegration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ValidateEntityClass validates an entity's class and provides detailed information
|
// ValidateEntityClass validates an entity's class and provides detailed information
|
||||||
func (ci *ClassIntegration) ValidateEntityClass(entity ClassAware) (bool, string, map[string]interface{}) {
|
func (ci *ClassIntegration) ValidateEntityClass(entity ClassAware) (bool, string, map[string]any) {
|
||||||
classID := entity.GetClass()
|
classID := entity.GetClass()
|
||||||
|
|
||||||
if !ci.classes.IsValidClassID(classID) {
|
if !ci.classes.IsValidClassID(classID) {
|
||||||
@ -45,8 +45,8 @@ func (ci *ClassIntegration) ValidateEntityClass(entity ClassAware) (bool, string
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetEntityClassInfo returns comprehensive class information for an entity
|
// GetEntityClassInfo returns comprehensive class information for an entity
|
||||||
func (ci *ClassIntegration) GetEntityClassInfo(entity EntityWithClass) map[string]interface{} {
|
func (ci *ClassIntegration) GetEntityClassInfo(entity EntityWithClass) map[string]any {
|
||||||
info := make(map[string]interface{})
|
info := make(map[string]any)
|
||||||
|
|
||||||
// Basic entity info
|
// Basic entity info
|
||||||
info["entity_id"] = entity.GetID()
|
info["entity_id"] = entity.GetID()
|
||||||
@ -281,12 +281,12 @@ func (ci *ClassIntegration) GetClassStartingStats(classID int8) map[string]int16
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateClassSpecificEntity creates entity data with class-specific properties
|
// CreateClassSpecificEntity creates entity data with class-specific properties
|
||||||
func (ci *ClassIntegration) CreateClassSpecificEntity(classID int8) map[string]interface{} {
|
func (ci *ClassIntegration) CreateClassSpecificEntity(classID int8) map[string]any {
|
||||||
if !ci.classes.IsValidClassID(classID) {
|
if !ci.classes.IsValidClassID(classID) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
entityData := make(map[string]interface{})
|
entityData := make(map[string]any)
|
||||||
|
|
||||||
// Basic class info
|
// Basic class info
|
||||||
entityData["class_id"] = classID
|
entityData["class_id"] = classID
|
||||||
@ -310,16 +310,16 @@ func (ci *ClassIntegration) CreateClassSpecificEntity(classID int8) map[string]i
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetClassSelectionData returns data for class selection UI
|
// GetClassSelectionData returns data for class selection UI
|
||||||
func (ci *ClassIntegration) GetClassSelectionData() map[string]interface{} {
|
func (ci *ClassIntegration) GetClassSelectionData() map[string]any {
|
||||||
data := make(map[string]interface{})
|
data := make(map[string]any)
|
||||||
|
|
||||||
// All available adventure classes (exclude tradeskill for character creation)
|
// All available adventure classes (exclude tradeskill for character creation)
|
||||||
allClasses := ci.classes.GetAllClasses()
|
allClasses := ci.classes.GetAllClasses()
|
||||||
adventureClasses := make([]map[string]interface{}, 0)
|
adventureClasses := make([]map[string]any, 0)
|
||||||
|
|
||||||
for classID, displayName := range allClasses {
|
for classID, displayName := range allClasses {
|
||||||
if ci.classes.IsAdventureClass(classID) {
|
if ci.classes.IsAdventureClass(classID) {
|
||||||
classData := map[string]interface{}{
|
classData := map[string]any{
|
||||||
"id": classID,
|
"id": classID,
|
||||||
"name": displayName,
|
"name": displayName,
|
||||||
"type": ci.classes.GetClassType(classID),
|
"type": ci.classes.GetClassType(classID),
|
||||||
|
@ -330,8 +330,8 @@ func (cm *ClassManager) handleProgressionCommand(args []string) (string, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ValidateEntityClasses validates classes for a collection of entities
|
// ValidateEntityClasses validates classes for a collection of entities
|
||||||
func (cm *ClassManager) ValidateEntityClasses(entities []ClassAware) map[string]interface{} {
|
func (cm *ClassManager) ValidateEntityClasses(entities []ClassAware) map[string]any {
|
||||||
validationResults := make(map[string]interface{})
|
validationResults := make(map[string]any)
|
||||||
|
|
||||||
validCount := 0
|
validCount := 0
|
||||||
invalidCount := 0
|
invalidCount := 0
|
||||||
@ -351,11 +351,11 @@ func (cm *ClassManager) ValidateEntityClasses(entities []ClassAware) map[string]
|
|||||||
// Track invalid entities
|
// Track invalid entities
|
||||||
if !isValid {
|
if !isValid {
|
||||||
if validationResults["invalid_entities"] == nil {
|
if validationResults["invalid_entities"] == nil {
|
||||||
validationResults["invalid_entities"] = make([]map[string]interface{}, 0)
|
validationResults["invalid_entities"] = make([]map[string]any, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
invalidList := validationResults["invalid_entities"].([]map[string]interface{})
|
invalidList := validationResults["invalid_entities"].([]map[string]any)
|
||||||
invalidList = append(invalidList, map[string]interface{}{
|
invalidList = append(invalidList, map[string]any{
|
||||||
"index": i,
|
"index": i,
|
||||||
"class_id": classID,
|
"class_id": classID,
|
||||||
})
|
})
|
||||||
@ -372,7 +372,7 @@ func (cm *ClassManager) ValidateEntityClasses(entities []ClassAware) map[string]
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetClassRecommendations returns class recommendations for character creation
|
// GetClassRecommendations returns class recommendations for character creation
|
||||||
func (cm *ClassManager) GetClassRecommendations(preferences map[string]interface{}) []int8 {
|
func (cm *ClassManager) GetClassRecommendations(preferences map[string]any) []int8 {
|
||||||
recommendations := make([]int8, 0)
|
recommendations := make([]int8, 0)
|
||||||
|
|
||||||
// Check for class type preference
|
// Check for class type preference
|
||||||
|
@ -366,8 +366,8 @@ func (cu *ClassUtils) GetClassAliases(classID int8) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetClassStatistics returns statistics about the class system
|
// GetClassStatistics returns statistics about the class system
|
||||||
func (cu *ClassUtils) GetClassStatistics() map[string]interface{} {
|
func (cu *ClassUtils) GetClassStatistics() map[string]any {
|
||||||
stats := make(map[string]interface{})
|
stats := make(map[string]any)
|
||||||
|
|
||||||
allClasses := cu.classes.GetAllClasses()
|
allClasses := cu.classes.GetAllClasses()
|
||||||
stats["total_classes"] = len(allClasses)
|
stats["total_classes"] = len(allClasses)
|
||||||
|
@ -163,14 +163,14 @@ type CollectionEventHandler interface {
|
|||||||
// LogHandler provides logging functionality
|
// LogHandler provides logging functionality
|
||||||
type LogHandler interface {
|
type LogHandler interface {
|
||||||
// LogDebug logs debug messages
|
// LogDebug logs debug messages
|
||||||
LogDebug(category, message string, args ...interface{})
|
LogDebug(category, message string, args ...any)
|
||||||
|
|
||||||
// LogInfo logs informational messages
|
// LogInfo logs informational messages
|
||||||
LogInfo(category, message string, args ...interface{})
|
LogInfo(category, message string, args ...any)
|
||||||
|
|
||||||
// LogError logs error messages
|
// LogError logs error messages
|
||||||
LogError(category, message string, args ...interface{})
|
LogError(category, message string, args ...any)
|
||||||
|
|
||||||
// LogWarning logs warning messages
|
// LogWarning logs warning messages
|
||||||
LogWarning(category, message string, args ...interface{})
|
LogWarning(category, message string, args ...any)
|
||||||
}
|
}
|
||||||
|
@ -151,7 +151,7 @@ func (e *Entity) SetInfoStruct(info *InfoStruct) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetClient returns the client for this entity (overridden by Player)
|
// GetClient returns the client for this entity (overridden by Player)
|
||||||
func (e *Entity) GetClient() interface{} {
|
func (e *Entity) GetClient() any {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,10 +18,10 @@ type Database interface {
|
|||||||
|
|
||||||
// Logger interface for faction logging
|
// Logger interface for faction logging
|
||||||
type Logger interface {
|
type Logger interface {
|
||||||
LogInfo(message string, args ...interface{})
|
LogInfo(message string, args ...any)
|
||||||
LogError(message string, args ...interface{})
|
LogError(message string, args ...any)
|
||||||
LogDebug(message string, args ...interface{})
|
LogDebug(message string, args ...any)
|
||||||
LogWarning(message string, args ...interface{})
|
LogWarning(message string, args ...any)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FactionRelation represents a relationship between two factions
|
// FactionRelation represents a relationship between two factions
|
||||||
|
@ -237,11 +237,11 @@ func (m *Manager) RecordFactionDecrease(factionID int32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetStatistics returns faction system statistics
|
// GetStatistics returns faction system statistics
|
||||||
func (m *Manager) GetStatistics() map[string]interface{} {
|
func (m *Manager) GetStatistics() map[string]any {
|
||||||
m.mutex.RLock()
|
m.mutex.RLock()
|
||||||
defer m.mutex.RUnlock()
|
defer m.mutex.RUnlock()
|
||||||
|
|
||||||
stats := make(map[string]interface{})
|
stats := make(map[string]any)
|
||||||
stats["total_factions"] = m.masterFactionList.GetFactionCount()
|
stats["total_factions"] = m.masterFactionList.GetFactionCount()
|
||||||
stats["total_faction_changes"] = m.totalFactionChanges
|
stats["total_faction_changes"] = m.totalFactionChanges
|
||||||
stats["faction_increases"] = m.factionIncreases
|
stats["faction_increases"] = m.factionIncreases
|
||||||
|
562
internal/ground_spawn/benchmark_test.go
Normal file
562
internal/ground_spawn/benchmark_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
523
internal/ground_spawn/concurrency_test.go
Normal file
523
internal/ground_spawn/concurrency_test.go
Normal 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()
|
||||||
|
}
|
333
internal/ground_spawn/core_concurrency_test.go
Normal file
333
internal/ground_spawn/core_concurrency_test.go
Normal 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
|
||||||
|
})
|
||||||
|
}
|
249
internal/ground_spawn/database.go
Normal file
249
internal/ground_spawn/database.go
Normal 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
|
||||||
|
}
|
@ -24,18 +24,20 @@ func NewGroundSpawn(config GroundSpawnConfig) *GroundSpawn {
|
|||||||
// Configure base spawn properties
|
// Configure base spawn properties
|
||||||
gs.SetName(config.Name)
|
gs.SetName(config.Name)
|
||||||
gs.SetSpawnType(DefaultSpawnType)
|
gs.SetSpawnType(DefaultSpawnType)
|
||||||
gs.SetDifficulty(DefaultDifficulty)
|
// Note: SetDifficulty and SetState methods not available in spawn interface
|
||||||
gs.SetState(DefaultState)
|
|
||||||
|
|
||||||
// Set position
|
// Set position
|
||||||
gs.SetX(config.Location.X)
|
gs.SetX(config.Location.X)
|
||||||
gs.SetY(config.Location.Y)
|
gs.SetY(config.Location.Y, false)
|
||||||
gs.SetZ(config.Location.Z)
|
gs.SetZ(config.Location.Z)
|
||||||
|
|
||||||
if config.RandomizeHeading {
|
if config.RandomizeHeading {
|
||||||
gs.SetHeading(rand.Float32() * 360.0)
|
// Convert float32 to int16 for heading
|
||||||
|
heading := int16(rand.Float32() * 360.0)
|
||||||
|
gs.SetHeading(heading, heading)
|
||||||
} else {
|
} else {
|
||||||
gs.SetHeading(config.Location.Heading)
|
heading := int16(config.Location.Heading)
|
||||||
|
gs.SetHeading(heading, heading)
|
||||||
}
|
}
|
||||||
|
|
||||||
return gs
|
return gs
|
||||||
@ -47,12 +49,14 @@ func (gs *GroundSpawn) Copy() *GroundSpawn {
|
|||||||
defer gs.harvestMutex.Unlock()
|
defer gs.harvestMutex.Unlock()
|
||||||
|
|
||||||
newSpawn := &GroundSpawn{
|
newSpawn := &GroundSpawn{
|
||||||
Spawn: gs.Spawn.Copy().(*spawn.Spawn),
|
Spawn: gs.Spawn, // TODO: Implement proper copy when spawn.Copy() is available
|
||||||
numberHarvests: gs.numberHarvests,
|
numberHarvests: gs.numberHarvests,
|
||||||
numAttemptsPerHarvest: gs.numAttemptsPerHarvest,
|
numAttemptsPerHarvest: gs.numAttemptsPerHarvest,
|
||||||
groundspawnID: gs.groundspawnID,
|
groundspawnID: gs.groundspawnID,
|
||||||
collectionSkill: gs.collectionSkill,
|
collectionSkill: gs.collectionSkill,
|
||||||
randomizeHeading: gs.randomizeHeading,
|
randomizeHeading: gs.randomizeHeading,
|
||||||
|
// Reset mutexes in the copy to avoid sharing the same mutex instances
|
||||||
|
// harvestMutex and harvestUseMutex are zero-initialized (correct behavior)
|
||||||
}
|
}
|
||||||
|
|
||||||
return newSpawn
|
return newSpawn
|
||||||
@ -252,14 +256,14 @@ func (gs *GroundSpawn) ProcessHarvest(context *HarvestContext) (*HarvestResult,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate harvest data
|
// Validate harvest data
|
||||||
if context.GroundSpawnEntries == nil || len(context.GroundSpawnEntries) == 0 {
|
if len(context.GroundSpawnEntries) == 0 {
|
||||||
return &HarvestResult{
|
return &HarvestResult{
|
||||||
Success: false,
|
Success: false,
|
||||||
MessageText: fmt.Sprintf("Error: No groundspawn entries assigned to groundspawn id: %d", gs.groundspawnID),
|
MessageText: fmt.Sprintf("Error: No groundspawn entries assigned to groundspawn id: %d", gs.groundspawnID),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if context.GroundSpawnItems == nil || len(context.GroundSpawnItems) == 0 {
|
if len(context.GroundSpawnItems) == 0 {
|
||||||
return &HarvestResult{
|
return &HarvestResult{
|
||||||
Success: false,
|
Success: false,
|
||||||
MessageText: fmt.Sprintf("Error: No groundspawn items assigned to groundspawn id: %d", gs.groundspawnID),
|
MessageText: fmt.Sprintf("Error: No groundspawn items assigned to groundspawn id: %d", gs.groundspawnID),
|
||||||
@ -351,7 +355,7 @@ func (gs *GroundSpawn) filterHarvestTables(context *HarvestContext) []*GroundSpa
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check level requirement for bonus tables
|
// Check level requirement for bonus tables
|
||||||
if entry.BonusTable && context.Player.GetLevel() < entry.MinAdventureLevel {
|
if entry.BonusTable && (*context.Player).GetLevel() < entry.MinAdventureLevel {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -441,9 +445,9 @@ func (gs *GroundSpawn) awardHarvestItems(harvestType int8, availableItems []*Gro
|
|||||||
var items []*HarvestedItem
|
var items []*HarvestedItem
|
||||||
|
|
||||||
// Filter items based on harvest type and player location
|
// Filter items based on harvest type and player location
|
||||||
normalItems := gs.filterItems(availableItems, ItemRarityNormal, player.GetLocation())
|
normalItems := gs.filterItems(availableItems, ItemRarityNormal, (*player).GetLocation())
|
||||||
rareItems := gs.filterItems(availableItems, ItemRarityRare, player.GetLocation())
|
rareItems := gs.filterItems(availableItems, ItemRarityRare, (*player).GetLocation())
|
||||||
imbueItems := gs.filterItems(availableItems, ItemRarityImbue, player.GetLocation())
|
imbueItems := gs.filterItems(availableItems, ItemRarityImbue, (*player).GetLocation())
|
||||||
|
|
||||||
switch harvestType {
|
switch harvestType {
|
||||||
case HarvestType1Item:
|
case HarvestType1Item:
|
||||||
@ -522,7 +526,7 @@ func (gs *GroundSpawn) handleSkillProgression(context *HarvestContext, table *Gr
|
|||||||
// Check if player skill is already at max for this node
|
// Check if player skill is already at max for this node
|
||||||
maxSkillAllowed := int16(float32(context.MaxSkillRequired) * 1.0) // TODO: Use skill multiplier rule
|
maxSkillAllowed := int16(float32(context.MaxSkillRequired) * 1.0) // TODO: Use skill multiplier rule
|
||||||
|
|
||||||
if context.PlayerSkill.GetCurrentValue() >= maxSkillAllowed {
|
if (*context.PlayerSkill).GetCurrentValue() >= maxSkillAllowed {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -579,7 +583,7 @@ func (gs *GroundSpawn) handleHarvestUse(client Client) error {
|
|||||||
|
|
||||||
if client.GetLogger() != nil {
|
if client.GetLogger() != nil {
|
||||||
client.GetLogger().LogDebug("Player %s attempting to harvest %s using spell %s",
|
client.GetLogger().LogDebug("Player %s attempting to harvest %s using spell %s",
|
||||||
client.GetPlayer().GetName(), gs.GetName(), spellName)
|
(*client.GetPlayer()).GetName(), gs.GetName(), spellName)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -595,7 +599,7 @@ func (gs *GroundSpawn) handleCommandUse(client Client, command string) error {
|
|||||||
|
|
||||||
if client.GetLogger() != nil {
|
if client.GetLogger() != nil {
|
||||||
client.GetLogger().LogDebug("Player %s using command %s on %s",
|
client.GetLogger().LogDebug("Player %s using command %s on %s",
|
||||||
client.GetPlayer().GetName(), command, gs.GetName())
|
(*client.GetPlayer()).GetName(), command, gs.GetName())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -603,8 +607,9 @@ func (gs *GroundSpawn) handleCommandUse(client Client, command string) error {
|
|||||||
|
|
||||||
// Serialize creates a packet representation of the ground spawn
|
// Serialize creates a packet representation of the ground spawn
|
||||||
func (gs *GroundSpawn) Serialize(player *Player, version int16) ([]byte, error) {
|
func (gs *GroundSpawn) Serialize(player *Player, version int16) ([]byte, error) {
|
||||||
// Use base spawn serialization
|
// TODO: Implement proper ground spawn serialization when spawn.Serialize is available
|
||||||
return gs.Spawn.Serialize(player, version)
|
// For now, return empty packet as placeholder
|
||||||
|
return make([]byte, 0), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Respawn resets the ground spawn to harvestable state
|
// Respawn resets the ground spawn to harvestable state
|
||||||
@ -617,9 +622,24 @@ func (gs *GroundSpawn) Respawn() {
|
|||||||
|
|
||||||
// Randomize heading if configured
|
// Randomize heading if configured
|
||||||
if gs.randomizeHeading {
|
if gs.randomizeHeading {
|
||||||
gs.SetHeading(rand.Float32() * 360.0)
|
heading := int16(rand.Float32() * 360.0)
|
||||||
|
gs.SetHeading(heading, heading)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark as alive
|
// Mark as alive
|
||||||
gs.SetAlive(true)
|
gs.SetAlive(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MeetsSpawnAccessRequirements checks if a player can access this ground spawn
|
||||||
|
func (gs *GroundSpawn) MeetsSpawnAccessRequirements(player *Player) bool {
|
||||||
|
// TODO: Implement proper access requirements checking
|
||||||
|
// For now, allow all players to access ground spawns
|
||||||
|
return player != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasCommandIcon returns true if this ground spawn has command interactions
|
||||||
|
func (gs *GroundSpawn) HasCommandIcon() bool {
|
||||||
|
// TODO: Implement command icon checking based on spawn configuration
|
||||||
|
// For now, ground spawns don't have command icons (only harvest)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@ -11,10 +11,10 @@ type Database interface {
|
|||||||
|
|
||||||
// Logger interface for ground spawn logging
|
// Logger interface for ground spawn logging
|
||||||
type Logger interface {
|
type Logger interface {
|
||||||
LogInfo(message string, args ...interface{})
|
LogInfo(message string, args ...any)
|
||||||
LogError(message string, args ...interface{})
|
LogError(message string, args ...any)
|
||||||
LogDebug(message string, args ...interface{})
|
LogDebug(message string, args ...any)
|
||||||
LogWarning(message string, args ...interface{})
|
LogWarning(message string, args ...any)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Player interface for ground spawn interactions
|
// Player interface for ground spawn interactions
|
||||||
@ -35,7 +35,7 @@ type Client interface {
|
|||||||
GetVersion() int16
|
GetVersion() int16
|
||||||
GetLogger() Logger
|
GetLogger() Logger
|
||||||
GetCurrentZoneID() int32
|
GetCurrentZoneID() int32
|
||||||
Message(channel int32, message string, args ...interface{})
|
Message(channel int32, message string, args ...any)
|
||||||
SimpleMessage(channel int32, message string)
|
SimpleMessage(channel int32, message string)
|
||||||
SendPopupMessage(type_ int32, message string, sound string, duration float32, r, g, b int32)
|
SendPopupMessage(type_ int32, message string, sound string, duration float32, r, g, b int32)
|
||||||
AddItem(item *Item, itemDeleted *bool) error
|
AddItem(item *Item, itemDeleted *bool) error
|
||||||
@ -126,8 +126,8 @@ type SkillProvider interface {
|
|||||||
|
|
||||||
// SpawnProvider interface for spawn system integration
|
// SpawnProvider interface for spawn system integration
|
||||||
type SpawnProvider interface {
|
type SpawnProvider interface {
|
||||||
CreateSpawn() interface{}
|
CreateSpawn() any
|
||||||
GetSpawn(id int32) interface{}
|
GetSpawn(id int32) any
|
||||||
RegisterGroundSpawn(gs *GroundSpawn) error
|
RegisterGroundSpawn(gs *GroundSpawn) error
|
||||||
UnregisterGroundSpawn(id int32) error
|
UnregisterGroundSpawn(id int32) error
|
||||||
}
|
}
|
||||||
@ -168,7 +168,7 @@ func (pgsa *PlayerGroundSpawnAdapter) CanHarvest(gs *GroundSpawn) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if player has required skill
|
// Check if player has required skill
|
||||||
skill := pgsa.player.GetSkillByName(gs.GetCollectionSkill())
|
skill := (*pgsa.player).GetSkillByName(gs.GetCollectionSkill())
|
||||||
if skill == nil {
|
if skill == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -184,7 +184,7 @@ func (pgsa *PlayerGroundSpawnAdapter) GetHarvestSkill(skillName string) *Skill {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return pgsa.player.GetSkillByName(skillName)
|
return (*pgsa.player).GetSkillByName(skillName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetHarvestModifiers returns harvest modifiers for the player
|
// GetHarvestModifiers returns harvest modifiers for the player
|
||||||
@ -207,7 +207,7 @@ func (pgsa *PlayerGroundSpawnAdapter) OnHarvestResult(result *HarvestResult) {
|
|||||||
if result.Success && len(result.ItemsAwarded) > 0 {
|
if result.Success && len(result.ItemsAwarded) > 0 {
|
||||||
if pgsa.logger != nil {
|
if pgsa.logger != nil {
|
||||||
pgsa.logger.LogDebug("Player %s successfully harvested %d items",
|
pgsa.logger.LogDebug("Player %s successfully harvested %d items",
|
||||||
pgsa.player.GetName(), len(result.ItemsAwarded))
|
(*pgsa.player).GetName(), len(result.ItemsAwarded))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -227,7 +227,7 @@ func NewHarvestEventAdapter(handler HarvestHandler, logger Logger) *HarvestEvent
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ProcessHarvestEvent processes a harvest event
|
// ProcessHarvestEvent processes a harvest event
|
||||||
func (hea *HarvestEventAdapter) ProcessHarvestEvent(eventType string, gs *GroundSpawn, player *Player, data interface{}) {
|
func (hea *HarvestEventAdapter) ProcessHarvestEvent(eventType string, gs *GroundSpawn, player *Player, data any) {
|
||||||
if hea.handler == nil {
|
if hea.handler == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -13,8 +13,11 @@ func NewManager(database Database, logger Logger) *Manager {
|
|||||||
entriesByID: make(map[int32][]*GroundSpawnEntry),
|
entriesByID: make(map[int32][]*GroundSpawnEntry),
|
||||||
itemsByID: make(map[int32][]*GroundSpawnEntryItem),
|
itemsByID: make(map[int32][]*GroundSpawnEntryItem),
|
||||||
respawnQueue: make(map[int32]time.Time),
|
respawnQueue: make(map[int32]time.Time),
|
||||||
|
activeSpawns: make(map[int32]*GroundSpawn),
|
||||||
|
depletedSpawns: make(map[int32]*GroundSpawn),
|
||||||
database: database,
|
database: database,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
nextSpawnID: 1,
|
||||||
harvestsBySkill: make(map[string]int64),
|
harvestsBySkill: make(map[string]int64),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -41,8 +44,22 @@ func (m *Manager) Initialize() error {
|
|||||||
m.mutex.Lock()
|
m.mutex.Lock()
|
||||||
defer m.mutex.Unlock()
|
defer m.mutex.Unlock()
|
||||||
|
|
||||||
|
var maxID int32
|
||||||
for _, gs := range groundSpawns {
|
for _, gs := range groundSpawns {
|
||||||
m.groundSpawns[gs.GetID()] = gs
|
spawnID := gs.GetID()
|
||||||
|
m.groundSpawns[spawnID] = gs
|
||||||
|
|
||||||
|
// Track max ID for nextSpawnID initialization
|
||||||
|
if spawnID > maxID {
|
||||||
|
maxID = spawnID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate active/depleted caches based on current state
|
||||||
|
if gs.IsAvailable() {
|
||||||
|
m.activeSpawns[spawnID] = gs
|
||||||
|
} else if gs.IsDepleted() {
|
||||||
|
m.depletedSpawns[spawnID] = gs
|
||||||
|
}
|
||||||
|
|
||||||
// Group by zone (placeholder - zone ID would come from spawn location)
|
// Group by zone (placeholder - zone ID would come from spawn location)
|
||||||
zoneID := int32(1) // TODO: Get actual zone ID from spawn
|
zoneID := int32(1) // TODO: Get actual zone ID from spawn
|
||||||
@ -50,10 +67,13 @@ func (m *Manager) Initialize() error {
|
|||||||
|
|
||||||
// Load harvest entries and items
|
// Load harvest entries and items
|
||||||
if err := m.loadGroundSpawnData(gs); err != nil && m.logger != nil {
|
if err := m.loadGroundSpawnData(gs); err != nil && m.logger != nil {
|
||||||
m.logger.LogWarning("Failed to load data for ground spawn %d: %v", gs.GetID(), err)
|
m.logger.LogWarning("Failed to load data for ground spawn %d: %v", spawnID, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set nextSpawnID to avoid collisions
|
||||||
|
m.nextSpawnID = maxID + 1
|
||||||
|
|
||||||
if m.logger != nil {
|
if m.logger != nil {
|
||||||
m.logger.LogInfo("Loaded %d ground spawns from database", len(groundSpawns))
|
m.logger.LogInfo("Loaded %d ground spawns from database", len(groundSpawns))
|
||||||
}
|
}
|
||||||
@ -89,15 +109,24 @@ func (m *Manager) CreateGroundSpawn(config GroundSpawnConfig) *GroundSpawn {
|
|||||||
m.mutex.Lock()
|
m.mutex.Lock()
|
||||||
defer m.mutex.Unlock()
|
defer m.mutex.Unlock()
|
||||||
|
|
||||||
// Generate ID (placeholder implementation)
|
// Use efficient ID counter instead of len()
|
||||||
newID := int32(len(m.groundSpawns) + 1)
|
newID := m.nextSpawnID
|
||||||
|
m.nextSpawnID++
|
||||||
gs.SetID(newID)
|
gs.SetID(newID)
|
||||||
|
|
||||||
// Store ground spawn
|
// Store ground spawn
|
||||||
m.groundSpawns[newID] = gs
|
m.groundSpawns[newID] = gs
|
||||||
|
|
||||||
// Group by zone
|
// Add to active cache (new spawns are typically active)
|
||||||
|
if gs.IsAvailable() {
|
||||||
|
m.activeSpawns[newID] = gs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group by zone - pre-allocate zone slice if needed
|
||||||
zoneID := int32(1) // TODO: Get actual zone ID from config.Location
|
zoneID := int32(1) // TODO: Get actual zone ID from config.Location
|
||||||
|
if m.spawnsByZone[zoneID] == nil {
|
||||||
|
m.spawnsByZone[zoneID] = make([]*GroundSpawn, 0, 16) // Pre-allocate with reasonable capacity
|
||||||
|
}
|
||||||
m.spawnsByZone[zoneID] = append(m.spawnsByZone[zoneID], gs)
|
m.spawnsByZone[zoneID] = append(m.spawnsByZone[zoneID], gs)
|
||||||
|
|
||||||
if m.logger != nil {
|
if m.logger != nil {
|
||||||
@ -121,13 +150,13 @@ func (m *Manager) GetGroundSpawnsByZone(zoneID int32) []*GroundSpawn {
|
|||||||
defer m.mutex.RUnlock()
|
defer m.mutex.RUnlock()
|
||||||
|
|
||||||
spawns := m.spawnsByZone[zoneID]
|
spawns := m.spawnsByZone[zoneID]
|
||||||
if spawns == nil {
|
if len(spawns) == 0 {
|
||||||
return []*GroundSpawn{}
|
return []*GroundSpawn{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return a copy to prevent external modification
|
// Return a copy to prevent external modification - use append for better performance
|
||||||
result := make([]*GroundSpawn, len(spawns))
|
result := make([]*GroundSpawn, 0, len(spawns))
|
||||||
copy(result, spawns)
|
result = append(result, spawns...)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@ -179,8 +208,11 @@ func (m *Manager) ProcessHarvest(gs *GroundSpawn, player *Player) (*HarvestResul
|
|||||||
m.mutex.Unlock()
|
m.mutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle respawn if depleted
|
// Handle respawn if depleted and update cache
|
||||||
if gs.IsDepleted() {
|
if gs.IsDepleted() {
|
||||||
|
m.mutex.Lock()
|
||||||
|
m.updateSpawnStateCache(gs)
|
||||||
|
m.mutex.Unlock()
|
||||||
m.scheduleRespawn(gs)
|
m.scheduleRespawn(gs)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,11 +228,11 @@ func (m *Manager) buildHarvestContext(gs *GroundSpawn, player *Player) (*Harvest
|
|||||||
items := m.itemsByID[groundspawnID]
|
items := m.itemsByID[groundspawnID]
|
||||||
m.mutex.RUnlock()
|
m.mutex.RUnlock()
|
||||||
|
|
||||||
if entries == nil || len(entries) == 0 {
|
if len(entries) == 0 {
|
||||||
return nil, fmt.Errorf("no harvest entries found for groundspawn %d", groundspawnID)
|
return nil, fmt.Errorf("no harvest entries found for groundspawn %d", groundspawnID)
|
||||||
}
|
}
|
||||||
|
|
||||||
if items == nil || len(items) == 0 {
|
if len(items) == 0 {
|
||||||
return nil, fmt.Errorf("no harvest items found for groundspawn %d", groundspawnID)
|
return nil, fmt.Errorf("no harvest items found for groundspawn %d", groundspawnID)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,13 +242,13 @@ func (m *Manager) buildHarvestContext(gs *GroundSpawn, player *Player) (*Harvest
|
|||||||
skillName = SkillGathering // Collections use gathering skill
|
skillName = SkillGathering // Collections use gathering skill
|
||||||
}
|
}
|
||||||
|
|
||||||
playerSkill := player.GetSkillByName(skillName)
|
playerSkill := (*player).GetSkillByName(skillName)
|
||||||
if playerSkill == nil {
|
if playerSkill == nil {
|
||||||
return nil, fmt.Errorf("player lacks required skill: %s", skillName)
|
return nil, fmt.Errorf("player lacks required skill: %s", skillName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate total skill (base + bonuses)
|
// Calculate total skill (base + bonuses)
|
||||||
totalSkill := playerSkill.GetCurrentValue()
|
totalSkill := (*playerSkill).GetCurrentValue()
|
||||||
// TODO: Add stat bonuses when stat system is integrated
|
// TODO: Add stat bonuses when stat system is integrated
|
||||||
|
|
||||||
// Find max skill required
|
// Find max skill required
|
||||||
@ -276,6 +308,12 @@ func (m *Manager) ProcessRespawns() {
|
|||||||
for _, spawnID := range toRespawn {
|
for _, spawnID := range toRespawn {
|
||||||
if gs := m.GetGroundSpawn(spawnID); gs != nil {
|
if gs := m.GetGroundSpawn(spawnID); gs != nil {
|
||||||
gs.Respawn()
|
gs.Respawn()
|
||||||
|
|
||||||
|
// Update cache after respawn
|
||||||
|
m.mutex.Lock()
|
||||||
|
m.updateSpawnStateCache(gs)
|
||||||
|
m.mutex.Unlock()
|
||||||
|
|
||||||
if m.logger != nil {
|
if m.logger != nil {
|
||||||
m.logger.LogDebug("Ground spawn %d respawned", spawnID)
|
m.logger.LogDebug("Ground spawn %d respawned", spawnID)
|
||||||
}
|
}
|
||||||
@ -365,6 +403,8 @@ func (m *Manager) RemoveGroundSpawn(id int32) bool {
|
|||||||
|
|
||||||
delete(m.groundSpawns, id)
|
delete(m.groundSpawns, id)
|
||||||
delete(m.respawnQueue, id)
|
delete(m.respawnQueue, id)
|
||||||
|
delete(m.activeSpawns, id)
|
||||||
|
delete(m.depletedSpawns, id)
|
||||||
|
|
||||||
// Remove from zone list
|
// Remove from zone list
|
||||||
// TODO: Get actual zone ID from ground spawn
|
// TODO: Get actual zone ID from ground spawn
|
||||||
@ -401,11 +441,10 @@ func (m *Manager) GetActiveGroundSpawns() []*GroundSpawn {
|
|||||||
m.mutex.RLock()
|
m.mutex.RLock()
|
||||||
defer m.mutex.RUnlock()
|
defer m.mutex.RUnlock()
|
||||||
|
|
||||||
var active []*GroundSpawn
|
// Use cached active spawns for O(1) performance instead of O(N) iteration
|
||||||
for _, gs := range m.groundSpawns {
|
active := make([]*GroundSpawn, 0, len(m.activeSpawns))
|
||||||
if gs.IsAvailable() {
|
for _, gs := range m.activeSpawns {
|
||||||
active = append(active, gs)
|
active = append(active, gs)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return active
|
return active
|
||||||
@ -416,16 +455,32 @@ func (m *Manager) GetDepletedGroundSpawns() []*GroundSpawn {
|
|||||||
m.mutex.RLock()
|
m.mutex.RLock()
|
||||||
defer m.mutex.RUnlock()
|
defer m.mutex.RUnlock()
|
||||||
|
|
||||||
var depleted []*GroundSpawn
|
// Use cached depleted spawns for O(1) performance instead of O(N) iteration
|
||||||
for _, gs := range m.groundSpawns {
|
depleted := make([]*GroundSpawn, 0, len(m.depletedSpawns))
|
||||||
if gs.IsDepleted() {
|
for _, gs := range m.depletedSpawns {
|
||||||
depleted = append(depleted, gs)
|
depleted = append(depleted, gs)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return depleted
|
return depleted
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// updateSpawnStateCache updates the active/depleted caches when a spawn's state changes
|
||||||
|
// IMPORTANT: This method must be called while holding the manager's mutex
|
||||||
|
func (m *Manager) updateSpawnStateCache(gs *GroundSpawn) {
|
||||||
|
spawnID := gs.GetID()
|
||||||
|
|
||||||
|
// Remove from both caches first
|
||||||
|
delete(m.activeSpawns, spawnID)
|
||||||
|
delete(m.depletedSpawns, spawnID)
|
||||||
|
|
||||||
|
// Add to appropriate cache based on current state
|
||||||
|
if gs.IsAvailable() {
|
||||||
|
m.activeSpawns[spawnID] = gs
|
||||||
|
} else if gs.IsDepleted() {
|
||||||
|
m.depletedSpawns[spawnID] = gs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ProcessCommand handles ground spawn management commands
|
// ProcessCommand handles ground spawn management commands
|
||||||
func (m *Manager) ProcessCommand(command string, args []string) (string, error) {
|
func (m *Manager) ProcessCommand(command string, args []string) (string, error) {
|
||||||
switch command {
|
switch command {
|
||||||
@ -536,7 +591,7 @@ func (m *Manager) handleInfoCommand(args []string) (string, error) {
|
|||||||
return fmt.Sprintf("Ground spawn %d not found.", spawnID), nil
|
return fmt.Sprintf("Ground spawn %d not found.", spawnID), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
result := fmt.Sprintf("Ground Spawn Information:\n")
|
result := "Ground Spawn Information:\n"
|
||||||
result += fmt.Sprintf("ID: %d\n", gs.GetID())
|
result += fmt.Sprintf("ID: %d\n", gs.GetID())
|
||||||
result += fmt.Sprintf("Name: %s\n", gs.GetName())
|
result += fmt.Sprintf("Name: %s\n", gs.GetName())
|
||||||
result += fmt.Sprintf("Collection Skill: %s\n", gs.GetCollectionSkill())
|
result += fmt.Sprintf("Collection Skill: %s\n", gs.GetCollectionSkill())
|
||||||
@ -550,7 +605,7 @@ func (m *Manager) handleInfoCommand(args []string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handleReloadCommand reloads ground spawns from database
|
// handleReloadCommand reloads ground spawns from database
|
||||||
func (m *Manager) handleReloadCommand(args []string) (string, error) {
|
func (m *Manager) handleReloadCommand(_ []string) (string, error) {
|
||||||
if m.database == nil {
|
if m.database == nil {
|
||||||
return "", fmt.Errorf("no database available")
|
return "", fmt.Errorf("no database available")
|
||||||
}
|
}
|
||||||
@ -562,6 +617,9 @@ func (m *Manager) handleReloadCommand(args []string) (string, error) {
|
|||||||
m.entriesByID = make(map[int32][]*GroundSpawnEntry)
|
m.entriesByID = make(map[int32][]*GroundSpawnEntry)
|
||||||
m.itemsByID = make(map[int32][]*GroundSpawnEntryItem)
|
m.itemsByID = make(map[int32][]*GroundSpawnEntryItem)
|
||||||
m.respawnQueue = make(map[int32]time.Time)
|
m.respawnQueue = make(map[int32]time.Time)
|
||||||
|
m.activeSpawns = make(map[int32]*GroundSpawn)
|
||||||
|
m.depletedSpawns = make(map[int32]*GroundSpawn)
|
||||||
|
m.nextSpawnID = 1 // Reset ID counter
|
||||||
m.mutex.Unlock()
|
m.mutex.Unlock()
|
||||||
|
|
||||||
// Reload from database
|
// Reload from database
|
||||||
@ -588,4 +646,7 @@ func (m *Manager) Shutdown() {
|
|||||||
m.entriesByID = make(map[int32][]*GroundSpawnEntry)
|
m.entriesByID = make(map[int32][]*GroundSpawnEntry)
|
||||||
m.itemsByID = make(map[int32][]*GroundSpawnEntryItem)
|
m.itemsByID = make(map[int32][]*GroundSpawnEntryItem)
|
||||||
m.respawnQueue = make(map[int32]time.Time)
|
m.respawnQueue = make(map[int32]time.Time)
|
||||||
|
m.activeSpawns = make(map[int32]*GroundSpawn)
|
||||||
|
m.depletedSpawns = make(map[int32]*GroundSpawn)
|
||||||
|
m.nextSpawnID = 1
|
||||||
}
|
}
|
||||||
|
8
internal/ground_spawn/test_utils.go
Normal file
8
internal/ground_spawn/test_utils.go
Normal 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) {}
|
@ -111,10 +111,15 @@ type Manager struct {
|
|||||||
itemsByID map[int32][]*GroundSpawnEntryItem // Harvest items by groundspawn ID
|
itemsByID map[int32][]*GroundSpawnEntryItem // Harvest items by groundspawn ID
|
||||||
respawnQueue map[int32]time.Time // Respawn timestamps
|
respawnQueue map[int32]time.Time // Respawn timestamps
|
||||||
|
|
||||||
|
// Performance optimization: cache active/depleted spawns to avoid O(N) scans
|
||||||
|
activeSpawns map[int32]*GroundSpawn // Cache of active spawns for O(1) lookups
|
||||||
|
depletedSpawns map[int32]*GroundSpawn // Cache of depleted spawns for O(1) lookups
|
||||||
|
|
||||||
database Database // Database interface
|
database Database // Database interface
|
||||||
logger Logger // Logging interface
|
logger Logger // Logging interface
|
||||||
|
|
||||||
mutex sync.RWMutex // Thread safety
|
mutex sync.RWMutex // Thread safety
|
||||||
|
nextSpawnID int32 // Efficient ID counter to avoid len() calls
|
||||||
|
|
||||||
// Statistics
|
// Statistics
|
||||||
totalHarvests int64 // Total harvest attempts
|
totalHarvests int64 // Total harvest attempts
|
||||||
|
@ -275,7 +275,7 @@ type MyGroupPacketHandler struct {
|
|||||||
// client connection management
|
// client connection management
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ph *MyGroupPacketHandler) SendGroupUpdate(members []*groups.GroupMemberInfo, excludeClient interface{}) error {
|
func (ph *MyGroupPacketHandler) SendGroupUpdate(members []*groups.GroupMemberInfo, excludeClient any) error {
|
||||||
// Send group update packets to clients
|
// Send group update packets to clients
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -316,12 +316,12 @@ func (g *Group) Disband() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SendGroupUpdate sends an update to all group members
|
// SendGroupUpdate sends an update to all group members
|
||||||
func (g *Group) SendGroupUpdate(excludeClient interface{}, forceRaidUpdate bool) {
|
func (g *Group) SendGroupUpdate(excludeClient any, forceRaidUpdate bool) {
|
||||||
g.sendGroupUpdate(excludeClient, forceRaidUpdate)
|
g.sendGroupUpdate(excludeClient, forceRaidUpdate)
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendGroupUpdate internal method to send group updates
|
// sendGroupUpdate internal method to send group updates
|
||||||
func (g *Group) sendGroupUpdate(excludeClient interface{}, forceRaidUpdate bool) {
|
func (g *Group) sendGroupUpdate(excludeClient any, forceRaidUpdate bool) {
|
||||||
update := NewGroupUpdate(GROUP_UPDATE_FLAG_MEMBER_LIST, g.id)
|
update := NewGroupUpdate(GROUP_UPDATE_FLAG_MEMBER_LIST, g.id)
|
||||||
update.ExcludeClient = excludeClient
|
update.ExcludeClient = excludeClient
|
||||||
update.ForceRaidUpdate = forceRaidUpdate
|
update.ForceRaidUpdate = forceRaidUpdate
|
||||||
@ -430,7 +430,7 @@ func (g *Group) GetLeaderName() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ShareQuestWithGroup shares a quest with all group members
|
// ShareQuestWithGroup shares a quest with all group members
|
||||||
func (g *Group) ShareQuestWithGroup(questSharer interface{}, quest interface{}) bool {
|
func (g *Group) ShareQuestWithGroup(questSharer any, quest any) bool {
|
||||||
// TODO: Implement quest sharing
|
// TODO: Implement quest sharing
|
||||||
// This would require integration with the quest system
|
// This would require integration with the quest system
|
||||||
return false
|
return false
|
||||||
|
@ -38,7 +38,7 @@ type GroupManagerInterface interface {
|
|||||||
RemoveGroupMemberByName(groupID int32, name string, isClient bool, charID int32) error
|
RemoveGroupMemberByName(groupID int32, name string, isClient bool, charID int32) error
|
||||||
|
|
||||||
// Group updates
|
// Group updates
|
||||||
SendGroupUpdate(groupID int32, excludeClient interface{}, forceRaidUpdate bool)
|
SendGroupUpdate(groupID int32, excludeClient any, forceRaidUpdate bool)
|
||||||
|
|
||||||
// Invitations
|
// Invitations
|
||||||
Invite(leader entity.Entity, member entity.Entity) int8
|
Invite(leader entity.Entity, member entity.Entity) int8
|
||||||
@ -141,9 +141,9 @@ type GroupDatabase interface {
|
|||||||
// GroupPacketHandler interface for handling group-related packets
|
// GroupPacketHandler interface for handling group-related packets
|
||||||
type GroupPacketHandler interface {
|
type GroupPacketHandler interface {
|
||||||
// Group update packets
|
// Group update packets
|
||||||
SendGroupUpdate(members []*GroupMemberInfo, excludeClient interface{}) error
|
SendGroupUpdate(members []*GroupMemberInfo, excludeClient any) error
|
||||||
SendGroupMemberUpdate(member *GroupMemberInfo, excludeClient interface{}) error
|
SendGroupMemberUpdate(member *GroupMemberInfo, excludeClient any) error
|
||||||
SendGroupOptionsUpdate(groupID int32, options *GroupOptions, excludeClient interface{}) error
|
SendGroupOptionsUpdate(groupID int32, options *GroupOptions, excludeClient any) error
|
||||||
|
|
||||||
// Group invitation packets
|
// Group invitation packets
|
||||||
SendGroupInvite(inviter, invitee entity.Entity) error
|
SendGroupInvite(inviter, invitee entity.Entity) error
|
||||||
@ -154,16 +154,16 @@ type GroupPacketHandler interface {
|
|||||||
SendGroupChatMessage(members []*GroupMemberInfo, from string, message string, channel int16, language int32) error
|
SendGroupChatMessage(members []*GroupMemberInfo, from string, message string, channel int16, language int32) error
|
||||||
|
|
||||||
// Raid packets
|
// Raid packets
|
||||||
SendRaidUpdate(raidGroups []*Group, excludeClient interface{}) error
|
SendRaidUpdate(raidGroups []*Group, excludeClient any) error
|
||||||
SendRaidInvite(leaderGroup, targetGroup *Group) error
|
SendRaidInvite(leaderGroup, targetGroup *Group) error
|
||||||
SendRaidInviteResponse(leaderGroup, targetGroup *Group, accepted bool) error
|
SendRaidInviteResponse(leaderGroup, targetGroup *Group, accepted bool) error
|
||||||
|
|
||||||
// Group UI packets
|
// Group UI packets
|
||||||
SendGroupWindowUpdate(client interface{}, group *Group) error
|
SendGroupWindowUpdate(client any, group *Group) error
|
||||||
SendRaidWindowUpdate(client interface{}, raidGroups []*Group) error
|
SendRaidWindowUpdate(client any, raidGroups []*Group) error
|
||||||
|
|
||||||
// Group member packets
|
// Group member packets
|
||||||
SendGroupMemberStats(member *GroupMemberInfo, excludeClient interface{}) error
|
SendGroupMemberStats(member *GroupMemberInfo, excludeClient any) error
|
||||||
SendGroupMemberZoneChange(member *GroupMemberInfo, oldZoneID, newZoneID int32) error
|
SendGroupMemberZoneChange(member *GroupMemberInfo, oldZoneID, newZoneID int32) error
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,8 +244,8 @@ type GroupStatistics interface {
|
|||||||
RecordGroupMemoryUsage(groups int32, members int32)
|
RecordGroupMemoryUsage(groups int32, members int32)
|
||||||
|
|
||||||
// Statistics retrieval
|
// Statistics retrieval
|
||||||
GetGroupStatistics(groupID int32) map[string]interface{}
|
GetGroupStatistics(groupID int32) map[string]any
|
||||||
GetOverallStatistics() map[string]interface{}
|
GetOverallStatistics() map[string]any
|
||||||
GetStatisticsSummary() *GroupManagerStats
|
GetStatisticsSummary() *GroupManagerStats
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,7 +204,7 @@ func (gm *GroupManager) RemoveGroupMemberByName(groupID int32, name string, isCl
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SendGroupUpdate sends an update to all members of a group
|
// SendGroupUpdate sends an update to all members of a group
|
||||||
func (gm *GroupManager) SendGroupUpdate(groupID int32, excludeClient interface{}, forceRaidUpdate bool) {
|
func (gm *GroupManager) SendGroupUpdate(groupID int32, excludeClient any, forceRaidUpdate bool) {
|
||||||
group := gm.GetGroup(groupID)
|
group := gm.GetGroup(groupID)
|
||||||
if group != nil {
|
if group != nil {
|
||||||
group.SendGroupUpdate(excludeClient, forceRaidUpdate)
|
group.SendGroupUpdate(excludeClient, forceRaidUpdate)
|
||||||
|
@ -59,7 +59,7 @@ type GroupMemberInfo struct {
|
|||||||
Member entity.Entity `json:"-"`
|
Member entity.Entity `json:"-"`
|
||||||
|
|
||||||
// Client reference (players only) - interface to avoid circular deps
|
// Client reference (players only) - interface to avoid circular deps
|
||||||
Client interface{} `json:"-"`
|
Client any `json:"-"`
|
||||||
|
|
||||||
// Timestamps
|
// Timestamps
|
||||||
JoinTime time.Time `json:"join_time"`
|
JoinTime time.Time `json:"join_time"`
|
||||||
@ -102,13 +102,13 @@ type Group struct {
|
|||||||
|
|
||||||
// GroupMessage represents a message sent to the group
|
// GroupMessage represents a message sent to the group
|
||||||
type GroupMessage struct {
|
type GroupMessage struct {
|
||||||
Type int8 `json:"type"`
|
Type int8 `json:"type"`
|
||||||
Channel int16 `json:"channel"`
|
Channel int16 `json:"channel"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
FromName string `json:"from_name"`
|
FromName string `json:"from_name"`
|
||||||
Language int32 `json:"language"`
|
Language int32 `json:"language"`
|
||||||
Timestamp time.Time `json:"timestamp"`
|
Timestamp time.Time `json:"timestamp"`
|
||||||
ExcludeClient interface{} `json:"-"`
|
ExcludeClient any `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GroupUpdate represents a group update event
|
// GroupUpdate represents a group update event
|
||||||
@ -119,7 +119,7 @@ type GroupUpdate struct {
|
|||||||
Options *GroupOptions `json:"options,omitempty"`
|
Options *GroupOptions `json:"options,omitempty"`
|
||||||
RaidGroups []int32 `json:"raid_groups,omitempty"`
|
RaidGroups []int32 `json:"raid_groups,omitempty"`
|
||||||
ForceRaidUpdate bool `json:"force_raid_update"`
|
ForceRaidUpdate bool `json:"force_raid_update"`
|
||||||
ExcludeClient interface{} `json:"-"`
|
ExcludeClient any `json:"-"`
|
||||||
Timestamp time.Time `json:"timestamp"`
|
Timestamp time.Time `json:"timestamp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,16 +182,16 @@ type GuildEventHandler interface {
|
|||||||
// LogHandler provides logging functionality
|
// LogHandler provides logging functionality
|
||||||
type LogHandler interface {
|
type LogHandler interface {
|
||||||
// LogDebug logs debug messages
|
// LogDebug logs debug messages
|
||||||
LogDebug(category, message string, args ...interface{})
|
LogDebug(category, message string, args ...any)
|
||||||
|
|
||||||
// LogInfo logs informational messages
|
// LogInfo logs informational messages
|
||||||
LogInfo(category, message string, args ...interface{})
|
LogInfo(category, message string, args ...any)
|
||||||
|
|
||||||
// LogError logs error messages
|
// LogError logs error messages
|
||||||
LogError(category, message string, args ...interface{})
|
LogError(category, message string, args ...any)
|
||||||
|
|
||||||
// LogWarning logs warning messages
|
// LogWarning logs warning messages
|
||||||
LogWarning(category, message string, args ...interface{})
|
LogWarning(category, message string, args ...any)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PlayerInfo contains basic player information
|
// PlayerInfo contains basic player information
|
||||||
@ -308,10 +308,10 @@ type NotificationManager interface {
|
|||||||
NotifyMemberLogout(guild *Guild, member *GuildMember)
|
NotifyMemberLogout(guild *Guild, member *GuildMember)
|
||||||
|
|
||||||
// NotifyGuildMessage sends a message to all guild members
|
// NotifyGuildMessage sends a message to all guild members
|
||||||
NotifyGuildMessage(guild *Guild, eventType int8, message string, args ...interface{})
|
NotifyGuildMessage(guild *Guild, eventType int8, message string, args ...any)
|
||||||
|
|
||||||
// NotifyOfficers sends a message to officers only
|
// NotifyOfficers sends a message to officers only
|
||||||
NotifyOfficers(guild *Guild, message string, args ...interface{})
|
NotifyOfficers(guild *Guild, message string, args ...any)
|
||||||
|
|
||||||
// NotifyGuildUpdate notifies guild members of guild changes
|
// NotifyGuildUpdate notifies guild members of guild changes
|
||||||
NotifyGuildUpdate(guild *Guild)
|
NotifyGuildUpdate(guild *Guild)
|
||||||
|
@ -117,10 +117,10 @@ type PlayerManager interface {
|
|||||||
|
|
||||||
// LogHandler defines the interface for logging operations
|
// LogHandler defines the interface for logging operations
|
||||||
type LogHandler interface {
|
type LogHandler interface {
|
||||||
LogDebug(system, format string, args ...interface{})
|
LogDebug(system, format string, args ...any)
|
||||||
LogInfo(system, format string, args ...interface{})
|
LogInfo(system, format string, args ...any)
|
||||||
LogWarning(system, format string, args ...interface{})
|
LogWarning(system, format string, args ...any)
|
||||||
LogError(system, format string, args ...interface{})
|
LogError(system, format string, args ...any)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TimerManager defines the interface for timer management
|
// TimerManager defines the interface for timer management
|
||||||
@ -139,8 +139,8 @@ type TimerManager interface {
|
|||||||
// CacheManager defines the interface for caching operations
|
// CacheManager defines the interface for caching operations
|
||||||
type CacheManager interface {
|
type CacheManager interface {
|
||||||
// Cache operations
|
// Cache operations
|
||||||
Set(key string, value interface{}, expiration time.Duration) error
|
Set(key string, value any, expiration time.Duration) error
|
||||||
Get(key string) (interface{}, bool)
|
Get(key string) (any, bool)
|
||||||
Delete(key string) error
|
Delete(key string) error
|
||||||
Clear() error
|
Clear() error
|
||||||
|
|
||||||
@ -213,6 +213,6 @@ type StatisticsCollector interface {
|
|||||||
type ConfigManager interface {
|
type ConfigManager interface {
|
||||||
GetHOConfig() *HeroicOPConfig
|
GetHOConfig() *HeroicOPConfig
|
||||||
UpdateHOConfig(config *HeroicOPConfig) error
|
UpdateHOConfig(config *HeroicOPConfig) error
|
||||||
GetConfigValue(key string) interface{}
|
GetConfigValue(key string) any
|
||||||
SetConfigValue(key string, value interface{}) error
|
SetConfigValue(key string, value any) error
|
||||||
}
|
}
|
||||||
|
@ -426,11 +426,11 @@ func (mhol *MasterHeroicOPList) SearchWheels(criteria HeroicOPSearchCriteria) []
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetStatistics returns usage statistics for the HO system
|
// GetStatistics returns usage statistics for the HO system
|
||||||
func (mhol *MasterHeroicOPList) GetStatistics() map[string]interface{} {
|
func (mhol *MasterHeroicOPList) GetStatistics() map[string]any {
|
||||||
mhol.mu.RLock()
|
mhol.mu.RLock()
|
||||||
defer mhol.mu.RUnlock()
|
defer mhol.mu.RUnlock()
|
||||||
|
|
||||||
stats := make(map[string]interface{})
|
stats := make(map[string]any)
|
||||||
|
|
||||||
// Basic counts
|
// Basic counts
|
||||||
stats["total_starters"] = mhol.getStarterCountNoLock()
|
stats["total_starters"] = mhol.getStarterCountNoLock()
|
||||||
|
@ -159,10 +159,10 @@ type ZoneManager interface {
|
|||||||
|
|
||||||
// LogHandler defines the interface for logging operations
|
// LogHandler defines the interface for logging operations
|
||||||
type LogHandler interface {
|
type LogHandler interface {
|
||||||
LogDebug(system, format string, args ...interface{})
|
LogDebug(system, format string, args ...any)
|
||||||
LogInfo(system, format string, args ...interface{})
|
LogInfo(system, format string, args ...any)
|
||||||
LogWarning(system, format string, args ...interface{})
|
LogWarning(system, format string, args ...any)
|
||||||
LogError(system, format string, args ...interface{})
|
LogError(system, format string, args ...any)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Additional integration interfaces
|
// Additional integration interfaces
|
||||||
@ -282,8 +282,8 @@ type AccessManager interface {
|
|||||||
type ConfigManager interface {
|
type ConfigManager interface {
|
||||||
GetHousingConfig() *HousingConfig
|
GetHousingConfig() *HousingConfig
|
||||||
UpdateHousingConfig(config *HousingConfig) error
|
UpdateHousingConfig(config *HousingConfig) error
|
||||||
GetConfigValue(key string) interface{}
|
GetConfigValue(key string) any
|
||||||
SetConfigValue(key string, value interface{}) error
|
SetConfigValue(key string, value any) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotificationManager defines interface for housing notifications
|
// NotificationManager defines interface for housing notifications
|
||||||
@ -298,8 +298,8 @@ type NotificationManager interface {
|
|||||||
// CacheManager defines interface for caching operations
|
// CacheManager defines interface for caching operations
|
||||||
type CacheManager interface {
|
type CacheManager interface {
|
||||||
// Cache operations
|
// Cache operations
|
||||||
Set(key string, value interface{}, expiration time.Duration) error
|
Set(key string, value any, expiration time.Duration) error
|
||||||
Get(key string) (interface{}, bool)
|
Get(key string) (any, bool)
|
||||||
Delete(key string) error
|
Delete(key string) error
|
||||||
Clear() error
|
Clear() error
|
||||||
|
|
||||||
|
@ -573,7 +573,7 @@ func (isa *ItemSystemAdapter) CraftItem(playerID uint32, itemID int32, quality i
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetPlayerItemStats returns statistics about a player's items
|
// GetPlayerItemStats returns statistics about a player's items
|
||||||
func (isa *ItemSystemAdapter) GetPlayerItemStats(playerID uint32) (map[string]interface{}, error) {
|
func (isa *ItemSystemAdapter) GetPlayerItemStats(playerID uint32) (map[string]any, error) {
|
||||||
inventory, err := isa.GetPlayerInventory(playerID)
|
inventory, err := isa.GetPlayerInventory(playerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -587,7 +587,7 @@ func (isa *ItemSystemAdapter) GetPlayerItemStats(playerID uint32) (map[string]in
|
|||||||
// Calculate equipment bonuses
|
// Calculate equipment bonuses
|
||||||
bonuses := equipment.CalculateEquipmentBonuses()
|
bonuses := equipment.CalculateEquipmentBonuses()
|
||||||
|
|
||||||
return map[string]interface{}{
|
return map[string]any{
|
||||||
"player_id": playerID,
|
"player_id": playerID,
|
||||||
"total_items": inventory.GetNumberOfItems(),
|
"total_items": inventory.GetNumberOfItems(),
|
||||||
"equipped_items": equipment.GetNumberOfItems(),
|
"equipped_items": equipment.GetNumberOfItems(),
|
||||||
@ -601,13 +601,13 @@ func (isa *ItemSystemAdapter) GetPlayerItemStats(playerID uint32) (map[string]in
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetSystemStats returns comprehensive statistics about the item system
|
// GetSystemStats returns comprehensive statistics about the item system
|
||||||
func (isa *ItemSystemAdapter) GetSystemStats() map[string]interface{} {
|
func (isa *ItemSystemAdapter) GetSystemStats() map[string]any {
|
||||||
isa.mutex.RLock()
|
isa.mutex.RLock()
|
||||||
defer isa.mutex.RUnlock()
|
defer isa.mutex.RUnlock()
|
||||||
|
|
||||||
masterStats := isa.masterList.GetStats()
|
masterStats := isa.masterList.GetStats()
|
||||||
|
|
||||||
return map[string]interface{}{
|
return map[string]any{
|
||||||
"total_item_templates": masterStats.TotalItems,
|
"total_item_templates": masterStats.TotalItems,
|
||||||
"items_by_type": masterStats.ItemsByType,
|
"items_by_type": masterStats.ItemsByType,
|
||||||
"items_by_tier": masterStats.ItemsByTier,
|
"items_by_tier": masterStats.ItemsByTier,
|
||||||
|
@ -43,13 +43,13 @@ func (ci ChestInteraction) String() string {
|
|||||||
// ChestInteractionResult represents the result of a chest interaction
|
// ChestInteractionResult represents the result of a chest interaction
|
||||||
type ChestInteractionResult struct {
|
type ChestInteractionResult struct {
|
||||||
Success bool `json:"success"`
|
Success bool `json:"success"`
|
||||||
Result int8 `json:"result"` // ChestResult constant
|
Result int8 `json:"result"` // ChestResult constant
|
||||||
Message string `json:"message"` // Message to display to player
|
Message string `json:"message"` // Message to display to player
|
||||||
Items []*items.Item `json:"items"` // Items received
|
Items []*items.Item `json:"items"` // Items received
|
||||||
Coins int32 `json:"coins"` // Coins received
|
Coins int32 `json:"coins"` // Coins received
|
||||||
Experience int32 `json:"experience"` // Experience gained (for disarming/lockpicking)
|
Experience int32 `json:"experience"` // Experience gained (for disarming/lockpicking)
|
||||||
ChestEmpty bool `json:"chest_empty"` // Whether chest is now empty
|
ChestEmpty bool `json:"chest_empty"` // Whether chest is now empty
|
||||||
ChestClosed bool `json:"chest_closed"` // Whether chest should be closed
|
ChestClosed bool `json:"chest_closed"` // Whether chest should be closed
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChestService handles treasure chest interactions and management
|
// ChestService handles treasure chest interactions and management
|
||||||
@ -73,7 +73,7 @@ type PlayerService interface {
|
|||||||
|
|
||||||
// ZoneService interface for zone-related operations
|
// ZoneService interface for zone-related operations
|
||||||
type ZoneService interface {
|
type ZoneService interface {
|
||||||
GetZoneRule(zoneID int32, ruleName string) (interface{}, error)
|
GetZoneRule(zoneID int32, ruleName string) (any, error)
|
||||||
SpawnObjectInZone(zoneID int32, appearanceID int32, x, y, z, heading float32, name string, commands []string) (int32, error)
|
SpawnObjectInZone(zoneID int32, appearanceID int32, x, y, z, heading float32, name string, commands []string) (int32, error)
|
||||||
RemoveObjectFromZone(zoneID int32, objectID int32) error
|
RemoveObjectFromZone(zoneID int32, objectID int32) error
|
||||||
GetDistanceBetweenPoints(x1, y1, z1, x2, y2, z2 float32) float32
|
GetDistanceBetweenPoints(x1, y1, z1, x2, y2, z2 float32) float32
|
||||||
@ -123,7 +123,7 @@ func (cs *ChestService) CreateTreasureChestFromLoot(spawnID int32, zoneID int32,
|
|||||||
|
|
||||||
// Don't create chest if no qualifying items and no coins
|
// Don't create chest if no qualifying items and no coins
|
||||||
if filteredResult.IsEmpty() {
|
if filteredResult.IsEmpty() {
|
||||||
log.Printf("%s No qualifying loot for treasure chest (tier >= %d) for spawn %d",
|
log.Printf("%s No qualifying loot for treasure chest (tier >= %d) for spawn %d",
|
||||||
LogPrefixChest, LootTierCommon, spawnID)
|
LogPrefixChest, LootTierCommon, spawnID)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@ -136,7 +136,7 @@ func (cs *ChestService) CreateTreasureChestFromLoot(spawnID int32, zoneID int32,
|
|||||||
|
|
||||||
// Spawn the chest object in the zone
|
// Spawn the chest object in the zone
|
||||||
chestCommands := []string{"loot", "disarm"} // TODO: Add "lockpick" if chest is locked
|
chestCommands := []string{"loot", "disarm"} // TODO: Add "lockpick" if chest is locked
|
||||||
objectID, err := cs.zoneService.SpawnObjectInZone(zoneID, chest.AppearanceID, x, y, z, heading,
|
objectID, err := cs.zoneService.SpawnObjectInZone(zoneID, chest.AppearanceID, x, y, z, heading,
|
||||||
"Treasure Chest", chestCommands)
|
"Treasure Chest", chestCommands)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("%s Failed to spawn chest object in zone: %v", LogPrefixChest, err)
|
log.Printf("%s Failed to spawn chest object in zone: %v", LogPrefixChest, err)
|
||||||
@ -149,7 +149,7 @@ func (cs *ChestService) CreateTreasureChestFromLoot(spawnID int32, zoneID int32,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HandleChestInteraction processes a player's interaction with a treasure chest
|
// HandleChestInteraction processes a player's interaction with a treasure chest
|
||||||
func (cs *ChestService) HandleChestInteraction(chestID int32, playerID uint32,
|
func (cs *ChestService) HandleChestInteraction(chestID int32, playerID uint32,
|
||||||
interaction ChestInteraction, itemUniqueID int64) *ChestInteractionResult {
|
interaction ChestInteraction, itemUniqueID int64) *ChestInteractionResult {
|
||||||
|
|
||||||
result := &ChestInteractionResult{
|
result := &ChestInteractionResult{
|
||||||
@ -273,10 +273,10 @@ func (cs *ChestService) handleViewChest(chest *TreasureChest, playerID uint32) *
|
|||||||
return &ChestInteractionResult{
|
return &ChestInteractionResult{
|
||||||
Success: true,
|
Success: true,
|
||||||
Result: ChestResultSuccess,
|
Result: ChestResultSuccess,
|
||||||
Message: fmt.Sprintf("The chest contains %d items and %d coins",
|
Message: fmt.Sprintf("The chest contains %d items and %d coins",
|
||||||
len(chest.LootResult.GetItems()), chest.LootResult.GetCoins()),
|
len(chest.LootResult.GetItems()), chest.LootResult.GetCoins()),
|
||||||
Items: chest.LootResult.GetItems(),
|
Items: chest.LootResult.GetItems(),
|
||||||
Coins: chest.LootResult.GetCoins(),
|
Coins: chest.LootResult.GetCoins(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -400,7 +400,7 @@ func (cs *ChestService) handleDisarmChest(chest *TreasureChest, playerID uint32)
|
|||||||
|
|
||||||
// Get player's disarm skill
|
// Get player's disarm skill
|
||||||
disarmSkill := cs.playerService.GetPlayerSkillValue(playerID, "Disarm Trap")
|
disarmSkill := cs.playerService.GetPlayerSkillValue(playerID, "Disarm Trap")
|
||||||
|
|
||||||
// Calculate success chance (simplified)
|
// Calculate success chance (simplified)
|
||||||
successChance := float32(disarmSkill) - float32(chest.DisarmDifficulty)
|
successChance := float32(disarmSkill) - float32(chest.DisarmDifficulty)
|
||||||
if successChance < 0 {
|
if successChance < 0 {
|
||||||
@ -410,7 +410,7 @@ func (cs *ChestService) handleDisarmChest(chest *TreasureChest, playerID uint32)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Roll for success
|
// Roll for success
|
||||||
roll := float32(time.Now().UnixNano()%100) // Simple random
|
roll := float32(time.Now().UnixNano() % 100) // Simple random
|
||||||
if roll > successChance {
|
if roll > successChance {
|
||||||
// Failed disarm - could trigger trap effects here
|
// Failed disarm - could trigger trap effects here
|
||||||
return &ChestInteractionResult{
|
return &ChestInteractionResult{
|
||||||
@ -422,7 +422,7 @@ func (cs *ChestService) handleDisarmChest(chest *TreasureChest, playerID uint32)
|
|||||||
|
|
||||||
// Success - disarm the trap
|
// Success - disarm the trap
|
||||||
chest.IsDisarmable = false
|
chest.IsDisarmable = false
|
||||||
|
|
||||||
// Give experience
|
// Give experience
|
||||||
experience := int32(chest.DisarmDifficulty * 10) // 10 exp per difficulty point
|
experience := int32(chest.DisarmDifficulty * 10) // 10 exp per difficulty point
|
||||||
cs.playerService.AddPlayerExperience(playerID, experience, "Disarm Trap")
|
cs.playerService.AddPlayerExperience(playerID, experience, "Disarm Trap")
|
||||||
@ -450,7 +450,7 @@ func (cs *ChestService) handleLockpickChest(chest *TreasureChest, playerID uint3
|
|||||||
|
|
||||||
// Get player's lockpicking skill
|
// Get player's lockpicking skill
|
||||||
lockpickSkill := cs.playerService.GetPlayerSkillValue(playerID, "Pick Lock")
|
lockpickSkill := cs.playerService.GetPlayerSkillValue(playerID, "Pick Lock")
|
||||||
|
|
||||||
// Calculate success chance (simplified)
|
// Calculate success chance (simplified)
|
||||||
successChance := float32(lockpickSkill) - float32(chest.LockpickDifficulty)
|
successChance := float32(lockpickSkill) - float32(chest.LockpickDifficulty)
|
||||||
if successChance < 0 {
|
if successChance < 0 {
|
||||||
@ -460,7 +460,7 @@ func (cs *ChestService) handleLockpickChest(chest *TreasureChest, playerID uint3
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Roll for success
|
// Roll for success
|
||||||
roll := float32(time.Now().UnixNano()%100) // Simple random
|
roll := float32(time.Now().UnixNano() % 100) // Simple random
|
||||||
if roll > successChance {
|
if roll > successChance {
|
||||||
return &ChestInteractionResult{
|
return &ChestInteractionResult{
|
||||||
Success: false,
|
Success: false,
|
||||||
@ -471,7 +471,7 @@ func (cs *ChestService) handleLockpickChest(chest *TreasureChest, playerID uint3
|
|||||||
|
|
||||||
// Success - unlock the chest
|
// Success - unlock the chest
|
||||||
chest.IsLocked = false
|
chest.IsLocked = false
|
||||||
|
|
||||||
// Give experience
|
// Give experience
|
||||||
experience := int32(chest.LockpickDifficulty * 10) // 10 exp per difficulty point
|
experience := int32(chest.LockpickDifficulty * 10) // 10 exp per difficulty point
|
||||||
cs.playerService.AddPlayerExperience(playerID, experience, "Pick Lock")
|
cs.playerService.AddPlayerExperience(playerID, experience, "Pick Lock")
|
||||||
@ -500,12 +500,12 @@ func (cs *ChestService) handleCloseChest(chest *TreasureChest, playerID uint32)
|
|||||||
// CleanupEmptyChests removes empty chests from zones
|
// CleanupEmptyChests removes empty chests from zones
|
||||||
func (cs *ChestService) CleanupEmptyChests(zoneID int32) {
|
func (cs *ChestService) CleanupEmptyChests(zoneID int32) {
|
||||||
chests := cs.lootManager.GetZoneChests(zoneID)
|
chests := cs.lootManager.GetZoneChests(zoneID)
|
||||||
|
|
||||||
for _, chest := range chests {
|
for _, chest := range chests {
|
||||||
if chest.LootResult.IsEmpty() {
|
if chest.LootResult.IsEmpty() {
|
||||||
// Remove from zone
|
// Remove from zone
|
||||||
cs.zoneService.RemoveObjectFromZone(zoneID, chest.ID)
|
cs.zoneService.RemoveObjectFromZone(zoneID, chest.ID)
|
||||||
|
|
||||||
// Remove from loot manager
|
// Remove from loot manager
|
||||||
cs.lootManager.RemoveTreasureChest(chest.ID)
|
cs.lootManager.RemoveTreasureChest(chest.ID)
|
||||||
}
|
}
|
||||||
@ -515,4 +515,4 @@ func (cs *ChestService) CleanupEmptyChests(zoneID int32) {
|
|||||||
// GetPlayerChestList returns a list of chests a player can access
|
// GetPlayerChestList returns a list of chests a player can access
|
||||||
func (cs *ChestService) GetPlayerChestList(playerID uint32) []*TreasureChest {
|
func (cs *ChestService) GetPlayerChestList(playerID uint32) []*TreasureChest {
|
||||||
return cs.lootManager.GetPlayerChests(playerID)
|
return cs.lootManager.GetPlayerChests(playerID)
|
||||||
}
|
}
|
||||||
|
@ -10,12 +10,12 @@ import (
|
|||||||
|
|
||||||
// LootDatabase handles all database operations for the loot system
|
// LootDatabase handles all database operations for the loot system
|
||||||
type LootDatabase struct {
|
type LootDatabase struct {
|
||||||
db *sql.DB
|
db *sql.DB
|
||||||
queries map[string]*sql.Stmt
|
queries map[string]*sql.Stmt
|
||||||
lootTables map[int32]*LootTable
|
lootTables map[int32]*LootTable
|
||||||
spawnLoot map[int32][]int32 // spawn_id -> []loot_table_id
|
spawnLoot map[int32][]int32 // spawn_id -> []loot_table_id
|
||||||
globalLoot []*GlobalLoot
|
globalLoot []*GlobalLoot
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLootDatabase creates a new loot database manager
|
// NewLootDatabase creates a new loot database manager
|
||||||
@ -42,88 +42,88 @@ func (ldb *LootDatabase) prepareQueries() {
|
|||||||
FROM loottable
|
FROM loottable
|
||||||
ORDER BY id
|
ORDER BY id
|
||||||
`,
|
`,
|
||||||
|
|
||||||
"load_loot_drops": `
|
"load_loot_drops": `
|
||||||
SELECT loot_table_id, item_id, item_charges, equip_item, probability, no_drop_quest_completed_id
|
SELECT loot_table_id, item_id, item_charges, equip_item, probability, no_drop_quest_completed_id
|
||||||
FROM lootdrop
|
FROM lootdrop
|
||||||
WHERE loot_table_id = ?
|
WHERE loot_table_id = ?
|
||||||
ORDER BY probability DESC
|
ORDER BY probability DESC
|
||||||
`,
|
`,
|
||||||
|
|
||||||
"load_spawn_loot": `
|
"load_spawn_loot": `
|
||||||
SELECT spawn_id, loottable_id
|
SELECT spawn_id, loottable_id
|
||||||
FROM spawn_loot
|
FROM spawn_loot
|
||||||
ORDER BY spawn_id
|
ORDER BY spawn_id
|
||||||
`,
|
`,
|
||||||
|
|
||||||
"load_global_loot": `
|
"load_global_loot": `
|
||||||
SELECT type, loot_table, value1, value2, value3, value4
|
SELECT type, loot_table, value1, value2, value3, value4
|
||||||
FROM loot_global
|
FROM loot_global
|
||||||
ORDER BY type, value1
|
ORDER BY type, value1
|
||||||
`,
|
`,
|
||||||
|
|
||||||
"insert_loot_table": `
|
"insert_loot_table": `
|
||||||
INSERT INTO loottable (id, name, mincoin, maxcoin, maxlootitems, lootdrop_probability, coin_probability)
|
INSERT INTO loottable (id, name, mincoin, maxcoin, maxlootitems, lootdrop_probability, coin_probability)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||||
`,
|
`,
|
||||||
|
|
||||||
"update_loot_table": `
|
"update_loot_table": `
|
||||||
UPDATE loottable
|
UPDATE loottable
|
||||||
SET name = ?, mincoin = ?, maxcoin = ?, maxlootitems = ?, lootdrop_probability = ?, coin_probability = ?
|
SET name = ?, mincoin = ?, maxcoin = ?, maxlootitems = ?, lootdrop_probability = ?, coin_probability = ?
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
`,
|
`,
|
||||||
|
|
||||||
"delete_loot_table": `
|
"delete_loot_table": `
|
||||||
DELETE FROM loottable WHERE id = ?
|
DELETE FROM loottable WHERE id = ?
|
||||||
`,
|
`,
|
||||||
|
|
||||||
"insert_loot_drop": `
|
"insert_loot_drop": `
|
||||||
INSERT INTO lootdrop (loot_table_id, item_id, item_charges, equip_item, probability, no_drop_quest_completed_id)
|
INSERT INTO lootdrop (loot_table_id, item_id, item_charges, equip_item, probability, no_drop_quest_completed_id)
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
`,
|
`,
|
||||||
|
|
||||||
"delete_loot_drops": `
|
"delete_loot_drops": `
|
||||||
DELETE FROM lootdrop WHERE loot_table_id = ?
|
DELETE FROM lootdrop WHERE loot_table_id = ?
|
||||||
`,
|
`,
|
||||||
|
|
||||||
"insert_spawn_loot": `
|
"insert_spawn_loot": `
|
||||||
INSERT OR REPLACE INTO spawn_loot (spawn_id, loottable_id)
|
INSERT OR REPLACE INTO spawn_loot (spawn_id, loottable_id)
|
||||||
VALUES (?, ?)
|
VALUES (?, ?)
|
||||||
`,
|
`,
|
||||||
|
|
||||||
"delete_spawn_loot": `
|
"delete_spawn_loot": `
|
||||||
DELETE FROM spawn_loot WHERE spawn_id = ?
|
DELETE FROM spawn_loot WHERE spawn_id = ?
|
||||||
`,
|
`,
|
||||||
|
|
||||||
"insert_global_loot": `
|
"insert_global_loot": `
|
||||||
INSERT INTO loot_global (type, loot_table, value1, value2, value3, value4)
|
INSERT INTO loot_global (type, loot_table, value1, value2, value3, value4)
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
`,
|
`,
|
||||||
|
|
||||||
"delete_global_loot": `
|
"delete_global_loot": `
|
||||||
DELETE FROM loot_global WHERE type = ?
|
DELETE FROM loot_global WHERE type = ?
|
||||||
`,
|
`,
|
||||||
|
|
||||||
"get_loot_table": `
|
"get_loot_table": `
|
||||||
SELECT id, name, mincoin, maxcoin, maxlootitems, lootdrop_probability, coin_probability
|
SELECT id, name, mincoin, maxcoin, maxlootitems, lootdrop_probability, coin_probability
|
||||||
FROM loottable
|
FROM loottable
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
`,
|
`,
|
||||||
|
|
||||||
"get_spawn_loot_tables": `
|
"get_spawn_loot_tables": `
|
||||||
SELECT loottable_id
|
SELECT loottable_id
|
||||||
FROM spawn_loot
|
FROM spawn_loot
|
||||||
WHERE spawn_id = ?
|
WHERE spawn_id = ?
|
||||||
`,
|
`,
|
||||||
|
|
||||||
"count_loot_tables": `
|
"count_loot_tables": `
|
||||||
SELECT COUNT(*) FROM loottable
|
SELECT COUNT(*) FROM loottable
|
||||||
`,
|
`,
|
||||||
|
|
||||||
"count_loot_drops": `
|
"count_loot_drops": `
|
||||||
SELECT COUNT(*) FROM lootdrop
|
SELECT COUNT(*) FROM lootdrop
|
||||||
`,
|
`,
|
||||||
|
|
||||||
"count_spawn_loot": `
|
"count_spawn_loot": `
|
||||||
SELECT COUNT(*) FROM spawn_loot
|
SELECT COUNT(*) FROM spawn_loot
|
||||||
`,
|
`,
|
||||||
@ -141,36 +141,36 @@ func (ldb *LootDatabase) prepareQueries() {
|
|||||||
// LoadAllLootData loads all loot data from the database
|
// LoadAllLootData loads all loot data from the database
|
||||||
func (ldb *LootDatabase) LoadAllLootData() error {
|
func (ldb *LootDatabase) LoadAllLootData() error {
|
||||||
log.Printf("%s Loading loot data from database...", LogPrefixDatabase)
|
log.Printf("%s Loading loot data from database...", LogPrefixDatabase)
|
||||||
|
|
||||||
// Load loot tables first
|
// Load loot tables first
|
||||||
if err := ldb.loadLootTables(); err != nil {
|
if err := ldb.loadLootTables(); err != nil {
|
||||||
return fmt.Errorf("failed to load loot tables: %v", err)
|
return fmt.Errorf("failed to load loot tables: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load loot drops for each table
|
// Load loot drops for each table
|
||||||
if err := ldb.loadLootDrops(); err != nil {
|
if err := ldb.loadLootDrops(); err != nil {
|
||||||
return fmt.Errorf("failed to load loot drops: %v", err)
|
return fmt.Errorf("failed to load loot drops: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load spawn loot assignments
|
// Load spawn loot assignments
|
||||||
if err := ldb.loadSpawnLoot(); err != nil {
|
if err := ldb.loadSpawnLoot(); err != nil {
|
||||||
return fmt.Errorf("failed to load spawn loot: %v", err)
|
return fmt.Errorf("failed to load spawn loot: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load global loot configuration
|
// Load global loot configuration
|
||||||
if err := ldb.loadGlobalLoot(); err != nil {
|
if err := ldb.loadGlobalLoot(); err != nil {
|
||||||
return fmt.Errorf("failed to load global loot: %v", err)
|
return fmt.Errorf("failed to load global loot: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ldb.mutex.RLock()
|
ldb.mutex.RLock()
|
||||||
tableCount := len(ldb.lootTables)
|
tableCount := len(ldb.lootTables)
|
||||||
spawnCount := len(ldb.spawnLoot)
|
spawnCount := len(ldb.spawnLoot)
|
||||||
globalCount := len(ldb.globalLoot)
|
globalCount := len(ldb.globalLoot)
|
||||||
ldb.mutex.RUnlock()
|
ldb.mutex.RUnlock()
|
||||||
|
|
||||||
log.Printf("%s Loaded %d loot tables, %d spawn assignments, %d global loot entries",
|
log.Printf("%s Loaded %d loot tables, %d spawn assignments, %d global loot entries",
|
||||||
LogPrefixDatabase, tableCount, spawnCount, globalCount)
|
LogPrefixDatabase, tableCount, spawnCount, globalCount)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,7 +189,7 @@ func (ldb *LootDatabase) loadLootTables() error {
|
|||||||
|
|
||||||
ldb.mutex.Lock()
|
ldb.mutex.Lock()
|
||||||
defer ldb.mutex.Unlock()
|
defer ldb.mutex.Unlock()
|
||||||
|
|
||||||
// Clear existing tables
|
// Clear existing tables
|
||||||
ldb.lootTables = make(map[int32]*LootTable)
|
ldb.lootTables = make(map[int32]*LootTable)
|
||||||
|
|
||||||
@ -279,7 +279,7 @@ func (ldb *LootDatabase) loadSpawnLoot() error {
|
|||||||
|
|
||||||
ldb.mutex.Lock()
|
ldb.mutex.Lock()
|
||||||
defer ldb.mutex.Unlock()
|
defer ldb.mutex.Unlock()
|
||||||
|
|
||||||
// Clear existing spawn loot
|
// Clear existing spawn loot
|
||||||
ldb.spawnLoot = make(map[int32][]int32)
|
ldb.spawnLoot = make(map[int32][]int32)
|
||||||
|
|
||||||
@ -313,7 +313,7 @@ func (ldb *LootDatabase) loadGlobalLoot() error {
|
|||||||
|
|
||||||
ldb.mutex.Lock()
|
ldb.mutex.Lock()
|
||||||
defer ldb.mutex.Unlock()
|
defer ldb.mutex.Unlock()
|
||||||
|
|
||||||
// Clear existing global loot
|
// Clear existing global loot
|
||||||
ldb.globalLoot = make([]*GlobalLoot, 0)
|
ldb.globalLoot = make([]*GlobalLoot, 0)
|
||||||
|
|
||||||
@ -361,7 +361,7 @@ func (ldb *LootDatabase) loadGlobalLoot() error {
|
|||||||
func (ldb *LootDatabase) GetLootTable(tableID int32) *LootTable {
|
func (ldb *LootDatabase) GetLootTable(tableID int32) *LootTable {
|
||||||
ldb.mutex.RLock()
|
ldb.mutex.RLock()
|
||||||
defer ldb.mutex.RUnlock()
|
defer ldb.mutex.RUnlock()
|
||||||
|
|
||||||
return ldb.lootTables[tableID]
|
return ldb.lootTables[tableID]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -369,12 +369,12 @@ func (ldb *LootDatabase) GetLootTable(tableID int32) *LootTable {
|
|||||||
func (ldb *LootDatabase) GetSpawnLootTables(spawnID int32) []int32 {
|
func (ldb *LootDatabase) GetSpawnLootTables(spawnID int32) []int32 {
|
||||||
ldb.mutex.RLock()
|
ldb.mutex.RLock()
|
||||||
defer ldb.mutex.RUnlock()
|
defer ldb.mutex.RUnlock()
|
||||||
|
|
||||||
tables := ldb.spawnLoot[spawnID]
|
tables := ldb.spawnLoot[spawnID]
|
||||||
if tables == nil {
|
if tables == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return a copy to prevent external modification
|
// Return a copy to prevent external modification
|
||||||
result := make([]int32, len(tables))
|
result := make([]int32, len(tables))
|
||||||
copy(result, tables)
|
copy(result, tables)
|
||||||
@ -385,9 +385,9 @@ func (ldb *LootDatabase) GetSpawnLootTables(spawnID int32) []int32 {
|
|||||||
func (ldb *LootDatabase) GetGlobalLootTables(level int16, race int16, zoneID int32) []*GlobalLoot {
|
func (ldb *LootDatabase) GetGlobalLootTables(level int16, race int16, zoneID int32) []*GlobalLoot {
|
||||||
ldb.mutex.RLock()
|
ldb.mutex.RLock()
|
||||||
defer ldb.mutex.RUnlock()
|
defer ldb.mutex.RUnlock()
|
||||||
|
|
||||||
var result []*GlobalLoot
|
var result []*GlobalLoot
|
||||||
|
|
||||||
for _, global := range ldb.globalLoot {
|
for _, global := range ldb.globalLoot {
|
||||||
switch global.Type {
|
switch global.Type {
|
||||||
case GlobalLootTypeLevel:
|
case GlobalLootTypeLevel:
|
||||||
@ -404,7 +404,7 @@ func (ldb *LootDatabase) GetGlobalLootTables(level int16, race int16, zoneID int
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -587,8 +587,8 @@ func (ldb *LootDatabase) DeleteSpawnLoot(spawnID int32) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetLootStatistics returns database statistics
|
// GetLootStatistics returns database statistics
|
||||||
func (ldb *LootDatabase) GetLootStatistics() (map[string]interface{}, error) {
|
func (ldb *LootDatabase) GetLootStatistics() (map[string]any, error) {
|
||||||
stats := make(map[string]interface{})
|
stats := make(map[string]any)
|
||||||
|
|
||||||
// Count loot tables
|
// Count loot tables
|
||||||
if stmt := ldb.queries["count_loot_tables"]; stmt != nil {
|
if stmt := ldb.queries["count_loot_tables"]; stmt != nil {
|
||||||
@ -629,7 +629,7 @@ func (ldb *LootDatabase) GetLootStatistics() (map[string]interface{}, error) {
|
|||||||
// ReloadLootData reloads all loot data from the database
|
// ReloadLootData reloads all loot data from the database
|
||||||
func (ldb *LootDatabase) ReloadLootData() error {
|
func (ldb *LootDatabase) ReloadLootData() error {
|
||||||
log.Printf("%s Reloading loot data from database...", LogPrefixDatabase)
|
log.Printf("%s Reloading loot data from database...", LogPrefixDatabase)
|
||||||
|
|
||||||
return ldb.LoadAllLootData()
|
return ldb.LoadAllLootData()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -641,4 +641,4 @@ func (ldb *LootDatabase) Close() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -18,13 +18,13 @@ type LootSystem struct {
|
|||||||
|
|
||||||
// LootSystemConfig holds configuration for the loot system
|
// LootSystemConfig holds configuration for the loot system
|
||||||
type LootSystemConfig struct {
|
type LootSystemConfig struct {
|
||||||
DatabaseConnection *sql.DB
|
DatabaseConnection *sql.DB
|
||||||
ItemMasterList items.MasterItemListService
|
ItemMasterList items.MasterItemListService
|
||||||
PlayerService PlayerService
|
PlayerService PlayerService
|
||||||
ZoneService ZoneService
|
ZoneService ZoneService
|
||||||
ClientService ClientService
|
ClientService ClientService
|
||||||
ItemPacketBuilder ItemPacketBuilder
|
ItemPacketBuilder ItemPacketBuilder
|
||||||
StartCleanupTimer bool
|
StartCleanupTimer bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLootSystem creates a complete loot system with all components
|
// NewLootSystem creates a complete loot system with all components
|
||||||
@ -78,7 +78,7 @@ func NewLootSystem(config *LootSystemConfig) (*LootSystem, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GenerateAndCreateChest generates loot for a spawn and creates a treasure chest
|
// GenerateAndCreateChest generates loot for a spawn and creates a treasure chest
|
||||||
func (ls *LootSystem) GenerateAndCreateChest(spawnID int32, zoneID int32, x, y, z, heading float32,
|
func (ls *LootSystem) GenerateAndCreateChest(spawnID int32, zoneID int32, x, y, z, heading float32,
|
||||||
context *LootContext) (*TreasureChest, error) {
|
context *LootContext) (*TreasureChest, error) {
|
||||||
|
|
||||||
if ls.ChestService == nil {
|
if ls.ChestService == nil {
|
||||||
@ -98,7 +98,7 @@ func (ls *LootSystem) GenerateAndCreateChest(spawnID int32, zoneID int32, x, y,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create treasure chest
|
// Create treasure chest
|
||||||
chest, err := ls.ChestService.CreateTreasureChestFromLoot(spawnID, zoneID, x, y, z, heading,
|
chest, err := ls.ChestService.CreateTreasureChestFromLoot(spawnID, zoneID, x, y, z, heading,
|
||||||
lootResult, context.GroupMembers)
|
lootResult, context.GroupMembers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create treasure chest: %v", err)
|
return nil, fmt.Errorf("failed to create treasure chest: %v", err)
|
||||||
@ -108,7 +108,7 @@ func (ls *LootSystem) GenerateAndCreateChest(spawnID int32, zoneID int32, x, y,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HandlePlayerLootInteraction handles a player's interaction with a chest and sends appropriate packets
|
// HandlePlayerLootInteraction handles a player's interaction with a chest and sends appropriate packets
|
||||||
func (ls *LootSystem) HandlePlayerLootInteraction(chestID int32, playerID uint32,
|
func (ls *LootSystem) HandlePlayerLootInteraction(chestID int32, playerID uint32,
|
||||||
interaction ChestInteraction, itemUniqueID int64) error {
|
interaction ChestInteraction, itemUniqueID int64) error {
|
||||||
|
|
||||||
if ls.ChestService == nil {
|
if ls.ChestService == nil {
|
||||||
@ -141,7 +141,7 @@ func (ls *LootSystem) HandlePlayerLootInteraction(chestID int32, playerID uint32
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Log the interaction
|
// Log the interaction
|
||||||
log.Printf("%s Player %d %s chest %d: %s",
|
log.Printf("%s Player %d %s chest %d: %s",
|
||||||
LogPrefixLoot, playerID, interaction.String(), chestID, result.Message)
|
LogPrefixLoot, playerID, interaction.String(), chestID, result.Message)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -168,8 +168,8 @@ func (ls *LootSystem) ShowChestToPlayer(chestID int32, playerID uint32) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetSystemStatistics returns comprehensive statistics about the loot system
|
// GetSystemStatistics returns comprehensive statistics about the loot system
|
||||||
func (ls *LootSystem) GetSystemStatistics() (map[string]interface{}, error) {
|
func (ls *LootSystem) GetSystemStatistics() (map[string]any, error) {
|
||||||
stats := make(map[string]interface{})
|
stats := make(map[string]any)
|
||||||
|
|
||||||
// Database statistics
|
// Database statistics
|
||||||
if dbStats, err := ls.Database.GetLootStatistics(); err == nil {
|
if dbStats, err := ls.Database.GetLootStatistics(); err == nil {
|
||||||
@ -220,7 +220,7 @@ func (ls *LootSystem) AddLootTableWithDrops(table *LootTable) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateQuickLootTable creates a simple loot table with basic parameters
|
// CreateQuickLootTable creates a simple loot table with basic parameters
|
||||||
func (ls *LootSystem) CreateQuickLootTable(tableID int32, name string, items []QuickLootItem,
|
func (ls *LootSystem) CreateQuickLootTable(tableID int32, name string, items []QuickLootItem,
|
||||||
minCoin, maxCoin int32, maxItems int16) error {
|
minCoin, maxCoin int32, maxItems int16) error {
|
||||||
|
|
||||||
table := &LootTable{
|
table := &LootTable{
|
||||||
@ -289,9 +289,9 @@ func (ls *LootSystem) CreateGlobalLevelLoot(minLevel, maxLevel int8, tableID int
|
|||||||
ls.Database.globalLoot = append(ls.Database.globalLoot, global)
|
ls.Database.globalLoot = append(ls.Database.globalLoot, global)
|
||||||
ls.Database.mutex.Unlock()
|
ls.Database.mutex.Unlock()
|
||||||
|
|
||||||
log.Printf("%s Created global level loot for levels %d-%d using table %d",
|
log.Printf("%s Created global level loot for levels %d-%d using table %d",
|
||||||
LogPrefixLoot, minLevel, maxLevel, tableID)
|
LogPrefixLoot, minLevel, maxLevel, tableID)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,10 +303,10 @@ func (ls *LootSystem) GetActiveChestsInZone(zoneID int32) []*TreasureChest {
|
|||||||
// CleanupZoneChests removes all chests from a specific zone
|
// CleanupZoneChests removes all chests from a specific zone
|
||||||
func (ls *LootSystem) CleanupZoneChests(zoneID int32) {
|
func (ls *LootSystem) CleanupZoneChests(zoneID int32) {
|
||||||
chests := ls.Manager.GetZoneChests(zoneID)
|
chests := ls.Manager.GetZoneChests(zoneID)
|
||||||
|
|
||||||
for _, chest := range chests {
|
for _, chest := range chests {
|
||||||
ls.Manager.RemoveTreasureChest(chest.ID)
|
ls.Manager.RemoveTreasureChest(chest.ID)
|
||||||
|
|
||||||
// Remove from zone if chest service is available
|
// Remove from zone if chest service is available
|
||||||
if ls.ChestService != nil {
|
if ls.ChestService != nil {
|
||||||
ls.ChestService.zoneService.RemoveObjectFromZone(zoneID, chest.ID)
|
ls.ChestService.zoneService.RemoveObjectFromZone(zoneID, chest.ID)
|
||||||
@ -356,17 +356,17 @@ type ValidationError struct {
|
|||||||
func (ls *LootSystem) GetLootPreview(spawnID int32, context *LootContext) (*LootPreview, error) {
|
func (ls *LootSystem) GetLootPreview(spawnID int32, context *LootContext) (*LootPreview, error) {
|
||||||
tableIDs := ls.Database.GetSpawnLootTables(spawnID)
|
tableIDs := ls.Database.GetSpawnLootTables(spawnID)
|
||||||
globalLoot := ls.Database.GetGlobalLootTables(context.PlayerLevel, context.PlayerRace, context.ZoneID)
|
globalLoot := ls.Database.GetGlobalLootTables(context.PlayerLevel, context.PlayerRace, context.ZoneID)
|
||||||
|
|
||||||
for _, global := range globalLoot {
|
for _, global := range globalLoot {
|
||||||
tableIDs = append(tableIDs, global.TableID)
|
tableIDs = append(tableIDs, global.TableID)
|
||||||
}
|
}
|
||||||
|
|
||||||
preview := &LootPreview{
|
preview := &LootPreview{
|
||||||
SpawnID: spawnID,
|
SpawnID: spawnID,
|
||||||
TableIDs: tableIDs,
|
TableIDs: tableIDs,
|
||||||
PossibleItems: make([]*LootPreviewItem, 0),
|
PossibleItems: make([]*LootPreviewItem, 0),
|
||||||
MinCoins: 0,
|
MinCoins: 0,
|
||||||
MaxCoins: 0,
|
MaxCoins: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tableID := range tableIDs {
|
for _, tableID := range tableIDs {
|
||||||
@ -400,11 +400,11 @@ func (ls *LootSystem) GetLootPreview(spawnID int32, context *LootContext) (*Loot
|
|||||||
|
|
||||||
// LootPreview represents a preview of potential loot
|
// LootPreview represents a preview of potential loot
|
||||||
type LootPreview struct {
|
type LootPreview struct {
|
||||||
SpawnID int32 `json:"spawn_id"`
|
SpawnID int32 `json:"spawn_id"`
|
||||||
TableIDs []int32 `json:"table_ids"`
|
TableIDs []int32 `json:"table_ids"`
|
||||||
PossibleItems []*LootPreviewItem `json:"possible_items"`
|
PossibleItems []*LootPreviewItem `json:"possible_items"`
|
||||||
MinCoins int32 `json:"min_coins"`
|
MinCoins int32 `json:"min_coins"`
|
||||||
MaxCoins int32 `json:"max_coins"`
|
MaxCoins int32 `json:"max_coins"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LootPreviewItem represents a potential loot item in a preview
|
// LootPreviewItem represents a potential loot item in a preview
|
||||||
@ -413,4 +413,4 @@ type LootPreviewItem struct {
|
|||||||
ItemName string `json:"item_name"`
|
ItemName string `json:"item_name"`
|
||||||
Probability float32 `json:"probability"`
|
Probability float32 `json:"probability"`
|
||||||
Tier int8 `json:"tier"`
|
Tier int8 `json:"tier"`
|
||||||
}
|
}
|
||||||
|
@ -106,18 +106,18 @@ func (m *MockPlayerService) SetInventorySpace(playerID uint32, space int) {
|
|||||||
|
|
||||||
// MockZoneService implements ZoneService for testing
|
// MockZoneService implements ZoneService for testing
|
||||||
type MockZoneService struct {
|
type MockZoneService struct {
|
||||||
rules map[int32]map[string]interface{}
|
rules map[int32]map[string]any
|
||||||
objects map[int32]map[int32]interface{} // zoneID -> objectID
|
objects map[int32]map[int32]any // zoneID -> objectID
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMockZoneService() *MockZoneService {
|
func NewMockZoneService() *MockZoneService {
|
||||||
return &MockZoneService{
|
return &MockZoneService{
|
||||||
rules: make(map[int32]map[string]interface{}),
|
rules: make(map[int32]map[string]any),
|
||||||
objects: make(map[int32]map[int32]interface{}),
|
objects: make(map[int32]map[int32]any),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockZoneService) GetZoneRule(zoneID int32, ruleName string) (interface{}, error) {
|
func (m *MockZoneService) GetZoneRule(zoneID int32, ruleName string) (any, error) {
|
||||||
if rules, exists := m.rules[zoneID]; exists {
|
if rules, exists := m.rules[zoneID]; exists {
|
||||||
return rules[ruleName], nil
|
return rules[ruleName], nil
|
||||||
}
|
}
|
||||||
@ -127,7 +127,7 @@ func (m *MockZoneService) GetZoneRule(zoneID int32, ruleName string) (interface{
|
|||||||
func (m *MockZoneService) SpawnObjectInZone(zoneID int32, appearanceID int32, x, y, z, heading float32, name string, commands []string) (int32, error) {
|
func (m *MockZoneService) SpawnObjectInZone(zoneID int32, appearanceID int32, x, y, z, heading float32, name string, commands []string) (int32, error) {
|
||||||
objectID := int32(len(m.objects[zoneID]) + 1)
|
objectID := int32(len(m.objects[zoneID]) + 1)
|
||||||
if m.objects[zoneID] == nil {
|
if m.objects[zoneID] == nil {
|
||||||
m.objects[zoneID] = make(map[int32]interface{})
|
m.objects[zoneID] = make(map[int32]any)
|
||||||
}
|
}
|
||||||
m.objects[zoneID][objectID] = struct{}{}
|
m.objects[zoneID][objectID] = struct{}{}
|
||||||
return objectID, nil
|
return objectID, nil
|
||||||
@ -382,7 +382,7 @@ func TestTreasureChestCreation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if chest.AppearanceID != ChestAppearanceOrnate {
|
if chest.AppearanceID != ChestAppearanceOrnate {
|
||||||
t.Errorf("Expected ornate chest appearance %d for legendary item, got %d",
|
t.Errorf("Expected ornate chest appearance %d for legendary item, got %d",
|
||||||
ChestAppearanceOrnate, chest.AppearanceID)
|
ChestAppearanceOrnate, chest.AppearanceID)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -530,7 +530,7 @@ func TestChestAppearanceSelection(t *testing.T) {
|
|||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
appearance := GetChestAppearance(tc.tier)
|
appearance := GetChestAppearance(tc.tier)
|
||||||
if appearance.AppearanceID != tc.expected {
|
if appearance.AppearanceID != tc.expected {
|
||||||
t.Errorf("For tier %d, expected appearance %d, got %d",
|
t.Errorf("For tier %d, expected appearance %d, got %d",
|
||||||
tc.tier, tc.expected, appearance.AppearanceID)
|
tc.tier, tc.expected, appearance.AppearanceID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -667,4 +667,4 @@ func BenchmarkChestInteraction(b *testing.B) {
|
|||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
chestService.HandleChestInteraction(chest.ID, 1, ChestInteractionView, 0)
|
chestService.HandleChestInteraction(chest.ID, 1, ChestInteractionView, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,10 +14,10 @@ type Database interface {
|
|||||||
|
|
||||||
// Logger interface for language logging
|
// Logger interface for language logging
|
||||||
type Logger interface {
|
type Logger interface {
|
||||||
LogInfo(message string, args ...interface{})
|
LogInfo(message string, args ...any)
|
||||||
LogError(message string, args ...interface{})
|
LogError(message string, args ...any)
|
||||||
LogDebug(message string, args ...interface{})
|
LogDebug(message string, args ...any)
|
||||||
LogWarning(message string, args ...interface{})
|
LogWarning(message string, args ...any)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Player interface for language-related player operations
|
// Player interface for language-related player operations
|
||||||
@ -372,7 +372,7 @@ func NewLanguageEventAdapter(handler LanguageHandler, logger Logger) *LanguageEv
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ProcessLanguageEvent processes a language-related event
|
// ProcessLanguageEvent processes a language-related event
|
||||||
func (lea *LanguageEventAdapter) ProcessLanguageEvent(eventType string, player *Player, languageID int32, data interface{}) {
|
func (lea *LanguageEventAdapter) ProcessLanguageEvent(eventType string, player *Player, languageID int32, data any) {
|
||||||
if lea.handler == nil {
|
if lea.handler == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -7,10 +7,10 @@ import (
|
|||||||
|
|
||||||
// Logger interface for AI logging
|
// Logger interface for AI logging
|
||||||
type Logger interface {
|
type Logger interface {
|
||||||
LogInfo(message string, args ...interface{})
|
LogInfo(message string, args ...any)
|
||||||
LogError(message string, args ...interface{})
|
LogError(message string, args ...any)
|
||||||
LogDebug(message string, args ...interface{})
|
LogDebug(message string, args ...any)
|
||||||
LogWarning(message string, args ...interface{})
|
LogWarning(message string, args ...any)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NPC interface defines the required NPC functionality for AI
|
// NPC interface defines the required NPC functionality for AI
|
||||||
@ -144,7 +144,7 @@ type LuaInterface interface {
|
|||||||
type Zone interface {
|
type Zone interface {
|
||||||
GetSpawnByID(int32) Spawn
|
GetSpawnByID(int32) Spawn
|
||||||
ProcessSpell(spell Spell, caster NPC, target Spawn) error
|
ProcessSpell(spell Spell, caster NPC, target Spawn) error
|
||||||
CallSpawnScript(npc NPC, scriptType string, args ...interface{}) error
|
CallSpawnScript(npc NPC, scriptType string, args ...any) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// AIManager provides high-level management of the AI system
|
// AIManager provides high-level management of the AI system
|
||||||
@ -209,7 +209,7 @@ func (am *AIManager) GetBrain(npcID int32) Brain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateBrainForNPC creates and adds the appropriate brain for an NPC
|
// CreateBrainForNPC creates and adds the appropriate brain for an NPC
|
||||||
func (am *AIManager) CreateBrainForNPC(npc NPC, brainType int8, options ...interface{}) error {
|
func (am *AIManager) CreateBrainForNPC(npc NPC, brainType int8, options ...any) error {
|
||||||
if npc == nil {
|
if npc == nil {
|
||||||
return fmt.Errorf("NPC cannot be nil")
|
return fmt.Errorf("NPC cannot be nil")
|
||||||
}
|
}
|
||||||
|
@ -289,7 +289,7 @@ func (dfpb *DumbFirePetBrain) ExtendExpireTime(durationMS int32) {
|
|||||||
// Brain factory functions
|
// Brain factory functions
|
||||||
|
|
||||||
// CreateBrain creates the appropriate brain type for an NPC
|
// CreateBrain creates the appropriate brain type for an NPC
|
||||||
func CreateBrain(npc NPC, brainType int8, logger Logger, options ...interface{}) Brain {
|
func CreateBrain(npc NPC, brainType int8, logger Logger, options ...any) Brain {
|
||||||
switch brainType {
|
switch brainType {
|
||||||
case BrainTypeCombatPet:
|
case BrainTypeCombatPet:
|
||||||
return NewCombatPetBrain(npc, logger)
|
return NewCombatPetBrain(npc, logger)
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package npc
|
package npc
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
// Database interface for NPC persistence
|
// Database interface for NPC persistence
|
||||||
type Database interface {
|
type Database interface {
|
||||||
LoadAllNPCs() ([]*NPC, error)
|
LoadAllNPCs() ([]*NPC, error)
|
||||||
@ -13,10 +15,10 @@ type Database interface {
|
|||||||
|
|
||||||
// Logger interface for NPC logging
|
// Logger interface for NPC logging
|
||||||
type Logger interface {
|
type Logger interface {
|
||||||
LogInfo(message string, args ...interface{})
|
LogInfo(message string, args ...any)
|
||||||
LogError(message string, args ...interface{})
|
LogError(message string, args ...any)
|
||||||
LogDebug(message string, args ...interface{})
|
LogDebug(message string, args ...any)
|
||||||
LogWarning(message string, args ...interface{})
|
LogWarning(message string, args ...any)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client interface for NPC-related client operations
|
// Client interface for NPC-related client operations
|
||||||
@ -50,15 +52,15 @@ type Zone interface {
|
|||||||
RemoveNPC(npcID int32) error
|
RemoveNPC(npcID int32) error
|
||||||
GetPlayersInRange(x, y, z, radius float32) []Player
|
GetPlayersInRange(x, y, z, radius float32) []Player
|
||||||
ProcessEntityCommand(command string, client Client, target *NPC) error
|
ProcessEntityCommand(command string, client Client, target *NPC) error
|
||||||
CallSpawnScript(npc *NPC, scriptType string, args ...interface{}) error
|
CallSpawnScript(npc *NPC, scriptType string, args ...any) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// SpellManager interface for spell system integration
|
// SpellManager interface for spell system integration
|
||||||
type SpellManager interface {
|
type SpellManager interface {
|
||||||
GetSpell(spellID int32, tier int8) Spell
|
GetSpell(spellID int32, tier int8) Spell
|
||||||
CastSpell(caster *NPC, target interface{}, spell Spell) error
|
CastSpell(caster *NPC, target any, spell Spell) error
|
||||||
GetSpellEffect(entity interface{}, spellID int32) SpellEffect
|
GetSpellEffect(entity any, spellID int32) SpellEffect
|
||||||
ProcessSpell(spell Spell, caster *NPC, target interface{}) error
|
ProcessSpell(spell Spell, caster *NPC, target any) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spell interface for spell data
|
// Spell interface for spell data
|
||||||
@ -88,8 +90,8 @@ type SpellEffect interface {
|
|||||||
type SkillManager interface {
|
type SkillManager interface {
|
||||||
GetSkill(skillID int32) MasterSkill
|
GetSkill(skillID int32) MasterSkill
|
||||||
GetSkillByName(name string) MasterSkill
|
GetSkillByName(name string) MasterSkill
|
||||||
ApplySkillBonus(entity interface{}, skillID int32, bonus float32) error
|
ApplySkillBonus(entity any, skillID int32, bonus float32) error
|
||||||
RemoveSkillBonus(entity interface{}, skillID int32, bonus float32) error
|
RemoveSkillBonus(entity any, skillID int32, bonus float32) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// MasterSkill interface for skill definitions
|
// MasterSkill interface for skill definitions
|
||||||
@ -127,11 +129,11 @@ type MovementManager interface {
|
|||||||
|
|
||||||
// CombatManager interface for combat system integration
|
// CombatManager interface for combat system integration
|
||||||
type CombatManager interface {
|
type CombatManager interface {
|
||||||
StartCombat(npc *NPC, target interface{}) error
|
StartCombat(npc *NPC, target any) error
|
||||||
EndCombat(npc *NPC) error
|
EndCombat(npc *NPC) error
|
||||||
ProcessCombatRound(npc *NPC) error
|
ProcessCombatRound(npc *NPC) error
|
||||||
CalculateDamage(attacker *NPC, target interface{}) int32
|
CalculateDamage(attacker *NPC, target any) int32
|
||||||
ApplyDamage(target interface{}, damage int32) error
|
ApplyDamage(target any, damage int32) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// NPCAware interface for entities that can interact with NPCs
|
// NPCAware interface for entities that can interact with NPCs
|
||||||
@ -287,7 +289,7 @@ func NewSpellCasterAdapter(npc *NPC, spellManager SpellManager, logger Logger) *
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetNextSpell selects the next spell to cast based on AI strategy
|
// GetNextSpell selects the next spell to cast based on AI strategy
|
||||||
func (sca *SpellCasterAdapter) GetNextSpell(target interface{}, distance float32) Spell {
|
func (sca *SpellCasterAdapter) GetNextSpell(target any, distance float32) Spell {
|
||||||
if sca.npc == nil || sca.spellManager == nil {
|
if sca.npc == nil || sca.spellManager == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -307,7 +309,7 @@ func (sca *SpellCasterAdapter) GetNextSpell(target interface{}, distance float32
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetNextBuffSpell selects the next buff spell to cast
|
// GetNextBuffSpell selects the next buff spell to cast
|
||||||
func (sca *SpellCasterAdapter) GetNextBuffSpell(target interface{}) Spell {
|
func (sca *SpellCasterAdapter) GetNextBuffSpell(target any) Spell {
|
||||||
if sca.npc == nil || sca.spellManager == nil {
|
if sca.npc == nil || sca.spellManager == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -347,7 +349,7 @@ func (sca *SpellCasterAdapter) GetNextBuffSpell(target interface{}) Spell {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CastSpell attempts to cast a spell
|
// CastSpell attempts to cast a spell
|
||||||
func (sca *SpellCasterAdapter) CastSpell(target interface{}, spell Spell) error {
|
func (sca *SpellCasterAdapter) CastSpell(target any, spell Spell) error {
|
||||||
if sca.npc == nil || sca.spellManager == nil || spell == nil {
|
if sca.npc == nil || sca.spellManager == nil || spell == nil {
|
||||||
return fmt.Errorf("invalid parameters for spell casting")
|
return fmt.Errorf("invalid parameters for spell casting")
|
||||||
}
|
}
|
||||||
@ -371,7 +373,7 @@ func (sca *SpellCasterAdapter) CastSpell(target interface{}, spell Spell) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getNextCastOnAggroSpell selects cast-on-aggro spells
|
// getNextCastOnAggroSpell selects cast-on-aggro spells
|
||||||
func (sca *SpellCasterAdapter) getNextCastOnAggroSpell(target interface{}) Spell {
|
func (sca *SpellCasterAdapter) getNextCastOnAggroSpell(target any) Spell {
|
||||||
castOnSpells := sca.npc.castOnSpells[CastOnAggro]
|
castOnSpells := sca.npc.castOnSpells[CastOnAggro]
|
||||||
for _, npcSpell := range castOnSpells {
|
for _, npcSpell := range castOnSpells {
|
||||||
spell := sca.spellManager.GetSpell(npcSpell.GetSpellID(), npcSpell.GetTier())
|
spell := sca.spellManager.GetSpell(npcSpell.GetSpellID(), npcSpell.GetTier())
|
||||||
@ -386,7 +388,7 @@ func (sca *SpellCasterAdapter) getNextCastOnAggroSpell(target interface{}) Spell
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getNextSpellByStrategy selects spells based on AI strategy
|
// getNextSpellByStrategy selects spells based on AI strategy
|
||||||
func (sca *SpellCasterAdapter) getNextSpellByStrategy(target interface{}, distance float32, strategy int8) Spell {
|
func (sca *SpellCasterAdapter) getNextSpellByStrategy(target any, distance float32, strategy int8) Spell {
|
||||||
// TODO: Implement more sophisticated spell selection based on strategy
|
// TODO: Implement more sophisticated spell selection based on strategy
|
||||||
|
|
||||||
for _, npcSpell := range sca.npc.spells {
|
for _, npcSpell := range sca.npc.spells {
|
||||||
@ -446,7 +448,7 @@ func NewCombatAdapter(npc *NPC, combatManager CombatManager, logger Logger) *Com
|
|||||||
}
|
}
|
||||||
|
|
||||||
// EnterCombat handles entering combat state
|
// EnterCombat handles entering combat state
|
||||||
func (ca *CombatAdapter) EnterCombat(target interface{}) error {
|
func (ca *CombatAdapter) EnterCombat(target any) error {
|
||||||
if ca.npc == nil {
|
if ca.npc == nil {
|
||||||
return fmt.Errorf("NPC is nil")
|
return fmt.Errorf("NPC is nil")
|
||||||
}
|
}
|
||||||
|
@ -114,8 +114,8 @@ func (os *ObjectSpawn) ShowsCommandIcon() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetObjectInfo returns comprehensive information about the object spawn
|
// GetObjectInfo returns comprehensive information about the object spawn
|
||||||
func (os *ObjectSpawn) GetObjectInfo() map[string]interface{} {
|
func (os *ObjectSpawn) GetObjectInfo() map[string]any {
|
||||||
info := make(map[string]interface{})
|
info := make(map[string]any)
|
||||||
|
|
||||||
// Add spawn info
|
// Add spawn info
|
||||||
info["spawn_id"] = os.GetID()
|
info["spawn_id"] = os.GetID()
|
||||||
@ -246,7 +246,7 @@ func ConvertSpawnToObject(spawn *spawn.Spawn) *ObjectSpawn {
|
|||||||
|
|
||||||
// LoadObjectSpawnFromData loads object spawn data from database/config
|
// LoadObjectSpawnFromData loads object spawn data from database/config
|
||||||
// This would be called when loading spawns from the database
|
// This would be called when loading spawns from the database
|
||||||
func LoadObjectSpawnFromData(spawnData map[string]interface{}) *ObjectSpawn {
|
func LoadObjectSpawnFromData(spawnData map[string]any) *ObjectSpawn {
|
||||||
objectSpawn := NewObjectSpawn()
|
objectSpawn := NewObjectSpawn()
|
||||||
|
|
||||||
// Load basic spawn data
|
// Load basic spawn data
|
||||||
|
@ -315,11 +315,11 @@ func (om *ObjectManager) ClearZone(zoneName string) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetStatistics returns statistics about objects in the manager
|
// GetStatistics returns statistics about objects in the manager
|
||||||
func (om *ObjectManager) GetStatistics() map[string]interface{} {
|
func (om *ObjectManager) GetStatistics() map[string]any {
|
||||||
om.mutex.RLock()
|
om.mutex.RLock()
|
||||||
defer om.mutex.RUnlock()
|
defer om.mutex.RUnlock()
|
||||||
|
|
||||||
stats := make(map[string]interface{})
|
stats := make(map[string]any)
|
||||||
stats["total_objects"] = len(om.objects)
|
stats["total_objects"] = len(om.objects)
|
||||||
stats["zones_with_objects"] = len(om.objectsByZone)
|
stats["zones_with_objects"] = len(om.objectsByZone)
|
||||||
stats["interactive_objects"] = len(om.interactiveObjects)
|
stats["interactive_objects"] = len(om.interactiveObjects)
|
||||||
|
@ -581,11 +581,11 @@ func (o *Object) ShowsCommandIcon() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetObjectInfo returns comprehensive information about the object
|
// GetObjectInfo returns comprehensive information about the object
|
||||||
func (o *Object) GetObjectInfo() map[string]interface{} {
|
func (o *Object) GetObjectInfo() map[string]any {
|
||||||
o.mutex.RLock()
|
o.mutex.RLock()
|
||||||
defer o.mutex.RUnlock()
|
defer o.mutex.RUnlock()
|
||||||
|
|
||||||
info := make(map[string]interface{})
|
info := make(map[string]any)
|
||||||
info["clickable"] = o.clickable
|
info["clickable"] = o.clickable
|
||||||
info["zone_name"] = o.zoneName
|
info["zone_name"] = o.zoneName
|
||||||
info["device_id"] = o.deviceID
|
info["device_id"] = o.deviceID
|
||||||
|
@ -40,10 +40,10 @@ type PlayerManager interface {
|
|||||||
GetPlayersInZone(zoneID int32) []*Player
|
GetPlayersInZone(zoneID int32) []*Player
|
||||||
|
|
||||||
// SendToAll sends a message to all players
|
// SendToAll sends a message to all players
|
||||||
SendToAll(message interface{}) error
|
SendToAll(message any) error
|
||||||
|
|
||||||
// SendToZone sends a message to all players in a zone
|
// SendToZone sends a message to all players in a zone
|
||||||
SendToZone(zoneID int32, message interface{}) error
|
SendToZone(zoneID int32, message any) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// PlayerDatabase interface for database operations
|
// PlayerDatabase interface for database operations
|
||||||
@ -85,13 +85,13 @@ type PlayerDatabase interface {
|
|||||||
// PlayerPacketHandler interface for handling player packets
|
// PlayerPacketHandler interface for handling player packets
|
||||||
type PlayerPacketHandler interface {
|
type PlayerPacketHandler interface {
|
||||||
// HandlePacket handles a packet from a player
|
// HandlePacket handles a packet from a player
|
||||||
HandlePacket(player *Player, packet interface{}) error
|
HandlePacket(player *Player, packet any) error
|
||||||
|
|
||||||
// SendPacket sends a packet to a player
|
// SendPacket sends a packet to a player
|
||||||
SendPacket(player *Player, packet interface{}) error
|
SendPacket(player *Player, packet any) error
|
||||||
|
|
||||||
// BroadcastPacket broadcasts a packet to multiple players
|
// BroadcastPacket broadcasts a packet to multiple players
|
||||||
BroadcastPacket(players []*Player, packet interface{}) error
|
BroadcastPacket(players []*Player, packet any) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// PlayerEventHandler interface for player events
|
// PlayerEventHandler interface for player events
|
||||||
@ -181,7 +181,7 @@ type PlayerStatistics interface {
|
|||||||
RecordSpellCast(player *Player, spell *spells.Spell)
|
RecordSpellCast(player *Player, spell *spells.Spell)
|
||||||
|
|
||||||
// GetStatistics returns player statistics
|
// GetStatistics returns player statistics
|
||||||
GetStatistics(playerID int32) map[string]interface{}
|
GetStatistics(playerID int32) map[string]any
|
||||||
}
|
}
|
||||||
|
|
||||||
// PlayerNotifier interface for player notifications
|
// PlayerNotifier interface for player notifications
|
||||||
|
@ -274,7 +274,7 @@ func (m *Manager) GetPlayersInZone(zoneID int32) []*Player {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SendToAll sends a message to all players
|
// SendToAll sends a message to all players
|
||||||
func (m *Manager) SendToAll(message interface{}) error {
|
func (m *Manager) SendToAll(message any) error {
|
||||||
if m.packetHandler == nil {
|
if m.packetHandler == nil {
|
||||||
return fmt.Errorf("no packet handler configured")
|
return fmt.Errorf("no packet handler configured")
|
||||||
}
|
}
|
||||||
@ -284,7 +284,7 @@ func (m *Manager) SendToAll(message interface{}) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SendToZone sends a message to all players in a zone
|
// SendToZone sends a message to all players in a zone
|
||||||
func (m *Manager) SendToZone(zoneID int32, message interface{}) error {
|
func (m *Manager) SendToZone(zoneID int32, message any) error {
|
||||||
if m.packetHandler == nil {
|
if m.packetHandler == nil {
|
||||||
return fmt.Errorf("no packet handler configured")
|
return fmt.Errorf("no packet handler configured")
|
||||||
}
|
}
|
||||||
|
@ -114,12 +114,12 @@ type Packet interface {
|
|||||||
// PacketStruct interface defines packet structure functionality
|
// PacketStruct interface defines packet structure functionality
|
||||||
type PacketStruct interface {
|
type PacketStruct interface {
|
||||||
// Packet building methods
|
// Packet building methods
|
||||||
SetDataByName(name string, value interface{}, index ...int)
|
SetDataByName(name string, value any, index ...int)
|
||||||
SetArrayLengthByName(name string, length int)
|
SetArrayLengthByName(name string, length int)
|
||||||
SetArrayDataByName(name string, value interface{}, index int)
|
SetArrayDataByName(name string, value any, index int)
|
||||||
SetSubArrayLengthByName(name string, length int, index int)
|
SetSubArrayLengthByName(name string, length int, index int)
|
||||||
SetSubArrayDataByName(name string, value interface{}, index1, index2 int)
|
SetSubArrayDataByName(name string, value any, index1, index2 int)
|
||||||
SetSubstructArrayDataByName(substruct, name string, value interface{}, index int)
|
SetSubstructArrayDataByName(substruct, name string, value any, index int)
|
||||||
SetItemArrayDataByName(name string, item Item, player Player, index int, flag ...int)
|
SetItemArrayDataByName(name string, item Item, player Player, index int, flag ...int)
|
||||||
Serialize() Packet
|
Serialize() Packet
|
||||||
GetVersion() int16
|
GetVersion() int16
|
||||||
@ -197,10 +197,10 @@ type Database interface {
|
|||||||
|
|
||||||
// Logger interface defines logging functionality for quest system
|
// Logger interface defines logging functionality for quest system
|
||||||
type Logger interface {
|
type Logger interface {
|
||||||
LogInfo(message string, args ...interface{})
|
LogInfo(message string, args ...any)
|
||||||
LogError(message string, args ...interface{})
|
LogError(message string, args ...any)
|
||||||
LogDebug(message string, args ...interface{})
|
LogDebug(message string, args ...any)
|
||||||
LogWarning(message string, args ...interface{})
|
LogWarning(message string, args ...any)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configuration interface defines quest system configuration
|
// Configuration interface defines quest system configuration
|
||||||
|
@ -32,7 +32,7 @@ func NewRaceIntegration() *RaceIntegration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ValidateEntityRace validates an entity's race and provides detailed information
|
// ValidateEntityRace validates an entity's race and provides detailed information
|
||||||
func (ri *RaceIntegration) ValidateEntityRace(entity RaceAware) (bool, string, map[string]interface{}) {
|
func (ri *RaceIntegration) ValidateEntityRace(entity RaceAware) (bool, string, map[string]any) {
|
||||||
raceID := entity.GetRace()
|
raceID := entity.GetRace()
|
||||||
|
|
||||||
if !ri.races.IsValidRaceID(raceID) {
|
if !ri.races.IsValidRaceID(raceID) {
|
||||||
@ -63,8 +63,8 @@ func (ri *RaceIntegration) ApplyRacialBonuses(entity RaceAware, stats map[string
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetEntityRaceInfo returns comprehensive race information for an entity
|
// GetEntityRaceInfo returns comprehensive race information for an entity
|
||||||
func (ri *RaceIntegration) GetEntityRaceInfo(entity EntityWithRace) map[string]interface{} {
|
func (ri *RaceIntegration) GetEntityRaceInfo(entity EntityWithRace) map[string]any {
|
||||||
info := make(map[string]interface{})
|
info := make(map[string]any)
|
||||||
|
|
||||||
// Basic entity info
|
// Basic entity info
|
||||||
info["entity_id"] = entity.GetID()
|
info["entity_id"] = entity.GetID()
|
||||||
@ -230,12 +230,12 @@ func (ri *RaceIntegration) GetRaceStartingStats(raceID int8) map[string]int16 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateRaceSpecificEntity creates entity data with race-specific properties
|
// CreateRaceSpecificEntity creates entity data with race-specific properties
|
||||||
func (ri *RaceIntegration) CreateRaceSpecificEntity(raceID int8) map[string]interface{} {
|
func (ri *RaceIntegration) CreateRaceSpecificEntity(raceID int8) map[string]any {
|
||||||
if !ri.races.IsValidRaceID(raceID) {
|
if !ri.races.IsValidRaceID(raceID) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
entityData := make(map[string]interface{})
|
entityData := make(map[string]any)
|
||||||
|
|
||||||
// Basic race info
|
// Basic race info
|
||||||
entityData["race_id"] = raceID
|
entityData["race_id"] = raceID
|
||||||
@ -255,15 +255,15 @@ func (ri *RaceIntegration) CreateRaceSpecificEntity(raceID int8) map[string]inte
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetRaceSelectionData returns data for race selection UI
|
// GetRaceSelectionData returns data for race selection UI
|
||||||
func (ri *RaceIntegration) GetRaceSelectionData() map[string]interface{} {
|
func (ri *RaceIntegration) GetRaceSelectionData() map[string]any {
|
||||||
data := make(map[string]interface{})
|
data := make(map[string]any)
|
||||||
|
|
||||||
// All available races
|
// All available races
|
||||||
allRaces := ri.races.GetAllRaces()
|
allRaces := ri.races.GetAllRaces()
|
||||||
raceList := make([]map[string]interface{}, 0, len(allRaces))
|
raceList := make([]map[string]any, 0, len(allRaces))
|
||||||
|
|
||||||
for raceID, friendlyName := range allRaces {
|
for raceID, friendlyName := range allRaces {
|
||||||
raceData := map[string]interface{}{
|
raceData := map[string]any{
|
||||||
"id": raceID,
|
"id": raceID,
|
||||||
"name": friendlyName,
|
"name": friendlyName,
|
||||||
"alignment": ri.races.GetRaceAlignment(raceID),
|
"alignment": ri.races.GetRaceAlignment(raceID),
|
||||||
|
@ -267,8 +267,8 @@ func (rm *RaceManager) handleSearchCommand(args []string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ValidateEntityRaces validates races for a collection of entities
|
// ValidateEntityRaces validates races for a collection of entities
|
||||||
func (rm *RaceManager) ValidateEntityRaces(entities []RaceAware) map[string]interface{} {
|
func (rm *RaceManager) ValidateEntityRaces(entities []RaceAware) map[string]any {
|
||||||
validationResults := make(map[string]interface{})
|
validationResults := make(map[string]any)
|
||||||
|
|
||||||
validCount := 0
|
validCount := 0
|
||||||
invalidCount := 0
|
invalidCount := 0
|
||||||
@ -288,11 +288,11 @@ func (rm *RaceManager) ValidateEntityRaces(entities []RaceAware) map[string]inte
|
|||||||
// Track invalid entities
|
// Track invalid entities
|
||||||
if !isValid {
|
if !isValid {
|
||||||
if validationResults["invalid_entities"] == nil {
|
if validationResults["invalid_entities"] == nil {
|
||||||
validationResults["invalid_entities"] = make([]map[string]interface{}, 0)
|
validationResults["invalid_entities"] = make([]map[string]any, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
invalidList := validationResults["invalid_entities"].([]map[string]interface{})
|
invalidList := validationResults["invalid_entities"].([]map[string]any)
|
||||||
invalidList = append(invalidList, map[string]interface{}{
|
invalidList = append(invalidList, map[string]any{
|
||||||
"index": i,
|
"index": i,
|
||||||
"race_id": raceID,
|
"race_id": raceID,
|
||||||
})
|
})
|
||||||
@ -309,7 +309,7 @@ func (rm *RaceManager) ValidateEntityRaces(entities []RaceAware) map[string]inte
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetRaceRecommendations returns race recommendations for character creation
|
// GetRaceRecommendations returns race recommendations for character creation
|
||||||
func (rm *RaceManager) GetRaceRecommendations(preferences map[string]interface{}) []int8 {
|
func (rm *RaceManager) GetRaceRecommendations(preferences map[string]any) []int8 {
|
||||||
recommendations := make([]int8, 0)
|
recommendations := make([]int8, 0)
|
||||||
|
|
||||||
// Check for alignment preference
|
// Check for alignment preference
|
||||||
|
@ -351,11 +351,11 @@ func (r *Races) GetEvilRaceCount() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetRaceInfo returns comprehensive information about a race
|
// GetRaceInfo returns comprehensive information about a race
|
||||||
func (r *Races) GetRaceInfo(raceID int8) map[string]interface{} {
|
func (r *Races) GetRaceInfo(raceID int8) map[string]any {
|
||||||
r.mutex.RLock()
|
r.mutex.RLock()
|
||||||
defer r.mutex.RUnlock()
|
defer r.mutex.RUnlock()
|
||||||
|
|
||||||
info := make(map[string]interface{})
|
info := make(map[string]any)
|
||||||
|
|
||||||
if !r.IsValidRaceID(raceID) {
|
if !r.IsValidRaceID(raceID) {
|
||||||
info["valid"] = false
|
info["valid"] = false
|
||||||
|
@ -321,8 +321,8 @@ func (ru *RaceUtils) GetRaceAliases(raceID int8) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetRaceStatistics returns statistics about the race system
|
// GetRaceStatistics returns statistics about the race system
|
||||||
func (ru *RaceUtils) GetRaceStatistics() map[string]interface{} {
|
func (ru *RaceUtils) GetRaceStatistics() map[string]any {
|
||||||
stats := make(map[string]interface{})
|
stats := make(map[string]any)
|
||||||
|
|
||||||
stats["total_races"] = ru.races.GetRaceCount()
|
stats["total_races"] = ru.races.GetRaceCount()
|
||||||
stats["good_races"] = ru.races.GetGoodRaceCount()
|
stats["good_races"] = ru.races.GetGoodRaceCount()
|
||||||
|
@ -320,11 +320,11 @@ func (r *Recipe) GetComponentQuantityForSlot(slot int8) int16 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetInfo returns comprehensive information about the recipe
|
// GetInfo returns comprehensive information about the recipe
|
||||||
func (r *Recipe) GetInfo() map[string]interface{} {
|
func (r *Recipe) GetInfo() map[string]any {
|
||||||
r.mutex.RLock()
|
r.mutex.RLock()
|
||||||
defer r.mutex.RUnlock()
|
defer r.mutex.RUnlock()
|
||||||
|
|
||||||
info := make(map[string]interface{})
|
info := make(map[string]any)
|
||||||
|
|
||||||
info["id"] = r.ID
|
info["id"] = r.ID
|
||||||
info["soe_id"] = r.SoeID
|
info["soe_id"] = r.SoeID
|
||||||
|
@ -86,12 +86,12 @@ type RuleValidator interface {
|
|||||||
|
|
||||||
// ValidationRule defines validation criteria for a rule
|
// ValidationRule defines validation criteria for a rule
|
||||||
type ValidationRule struct {
|
type ValidationRule struct {
|
||||||
Required bool // Whether the rule is required
|
Required bool // Whether the rule is required
|
||||||
MinValue interface{} // Minimum allowed value (for numeric types)
|
MinValue any // Minimum allowed value (for numeric types)
|
||||||
MaxValue interface{} // Maximum allowed value (for numeric types)
|
MaxValue any // Maximum allowed value (for numeric types)
|
||||||
ValidValues []string // List of valid string values
|
ValidValues []string // List of valid string values
|
||||||
Pattern string // Regex pattern for validation
|
Pattern string // Regex pattern for validation
|
||||||
Description string // Description of the rule
|
Description string // Description of the rule
|
||||||
}
|
}
|
||||||
|
|
||||||
// RuleEventHandler defines the interface for rule change events
|
// RuleEventHandler defines the interface for rule change events
|
||||||
|
@ -21,7 +21,7 @@ type Client interface {
|
|||||||
GetCurrentZone() Zone
|
GetCurrentZone() Zone
|
||||||
SetTemporaryTransportID(id int32)
|
SetTemporaryTransportID(id int32)
|
||||||
SimpleMessage(channel int32, message string)
|
SimpleMessage(channel int32, message string)
|
||||||
Message(channel int32, format string, args ...interface{})
|
Message(channel int32, format string, args ...any)
|
||||||
CheckZoneAccess(zoneName string) bool
|
CheckZoneAccess(zoneName string) bool
|
||||||
TryZoneInstance(zoneID int32, useDefaults bool) bool
|
TryZoneInstance(zoneID int32, useDefaults bool) bool
|
||||||
Zone(zoneName string, useDefaults bool) error
|
Zone(zoneName string, useDefaults bool) error
|
||||||
@ -66,10 +66,10 @@ type EntityCommand struct {
|
|||||||
|
|
||||||
// Logger interface for sign logging
|
// Logger interface for sign logging
|
||||||
type Logger interface {
|
type Logger interface {
|
||||||
LogInfo(message string, args ...interface{})
|
LogInfo(message string, args ...any)
|
||||||
LogError(message string, args ...interface{})
|
LogError(message string, args ...any)
|
||||||
LogDebug(message string, args ...interface{})
|
LogDebug(message string, args ...any)
|
||||||
LogWarning(message string, args ...interface{})
|
LogWarning(message string, args ...any)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignSpawn provides sign functionality for spawn entities
|
// SignSpawn provides sign functionality for spawn entities
|
||||||
|
@ -265,11 +265,11 @@ func (m *Manager) HandleSignUse(sign *Sign, client Client, command string) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetStatistics returns sign system statistics
|
// GetStatistics returns sign system statistics
|
||||||
func (m *Manager) GetStatistics() map[string]interface{} {
|
func (m *Manager) GetStatistics() map[string]any {
|
||||||
m.mutex.RLock()
|
m.mutex.RLock()
|
||||||
defer m.mutex.RUnlock()
|
defer m.mutex.RUnlock()
|
||||||
|
|
||||||
stats := make(map[string]interface{})
|
stats := make(map[string]any)
|
||||||
stats["total_signs"] = m.totalSigns
|
stats["total_signs"] = m.totalSigns
|
||||||
stats["sign_interactions"] = m.signInteractions
|
stats["sign_interactions"] = m.signInteractions
|
||||||
stats["zone_transports"] = m.zoneTransports
|
stats["zone_transports"] = m.zoneTransports
|
||||||
|
@ -27,9 +27,9 @@ type Database interface {
|
|||||||
|
|
||||||
// Logger interface for skill system logging
|
// Logger interface for skill system logging
|
||||||
type Logger interface {
|
type Logger interface {
|
||||||
LogInfo(message string, args ...interface{})
|
LogInfo(message string, args ...any)
|
||||||
LogError(message string, args ...interface{})
|
LogError(message string, args ...any)
|
||||||
LogDebug(message string, args ...interface{})
|
LogDebug(message string, args ...any)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EntitySkillAdapter provides skill functionality for entities
|
// EntitySkillAdapter provides skill functionality for entities
|
||||||
|
@ -73,11 +73,11 @@ func (m *Manager) RecordSkillUp(skillID int32, skillType int32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetStatistics returns skill system statistics
|
// GetStatistics returns skill system statistics
|
||||||
func (m *Manager) GetStatistics() map[string]interface{} {
|
func (m *Manager) GetStatistics() map[string]any {
|
||||||
m.mutex.RLock()
|
m.mutex.RLock()
|
||||||
defer m.mutex.RUnlock()
|
defer m.mutex.RUnlock()
|
||||||
|
|
||||||
stats := make(map[string]interface{})
|
stats := make(map[string]any)
|
||||||
stats["total_skill_ups"] = m.totalSkillUps
|
stats["total_skill_ups"] = m.totalSkillUps
|
||||||
stats["players_with_skills"] = m.playersWithSkills
|
stats["players_with_skills"] = m.playersWithSkills
|
||||||
stats["total_skills_in_master"] = m.masterSkillList.GetSkillCount()
|
stats["total_skills_in_master"] = m.masterSkillList.GetSkillCount()
|
||||||
|
@ -1074,7 +1074,7 @@ func (s *Spawn) SetBasicInfo(info *BasicInfoStruct) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetClient returns the client associated with this spawn (overridden by Player)
|
// GetClient returns the client associated with this spawn (overridden by Player)
|
||||||
func (s *Spawn) GetClient() interface{} {
|
func (s *Spawn) GetClient() any {
|
||||||
return nil // Base spawns have no client
|
return nil // Base spawns have no client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -555,13 +555,13 @@ func (sm *SpellManager) CanCastSpell(casterID, targetID, spellID int32) (bool, s
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetSpellInfo returns comprehensive information about a spell
|
// GetSpellInfo returns comprehensive information about a spell
|
||||||
func (sm *SpellManager) GetSpellInfo(spellID int32) map[string]interface{} {
|
func (sm *SpellManager) GetSpellInfo(spellID int32) map[string]any {
|
||||||
spell := sm.masterList.GetSpell(spellID)
|
spell := sm.masterList.GetSpell(spellID)
|
||||||
if spell == nil {
|
if spell == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
info := make(map[string]interface{})
|
info := make(map[string]any)
|
||||||
|
|
||||||
// Basic spell info
|
// Basic spell info
|
||||||
info["id"] = spell.GetSpellID()
|
info["id"] = spell.GetSpellID()
|
||||||
|
@ -155,7 +155,7 @@ func (st *SpellTargeting) getAETargets(luaSpell *LuaSpell, result *TargetingResu
|
|||||||
|
|
||||||
// For now, implement basic logic
|
// For now, implement basic logic
|
||||||
_ = luaSpell.Spell.GetSpellData() // TODO: Use spell data when needed
|
_ = luaSpell.Spell.GetSpellData() // TODO: Use spell data when needed
|
||||||
maxTargets := int32(10) // TODO: Use spellData.AOENodeNumber when field exists
|
maxTargets := int32(10) // TODO: Use spellData.AOENodeNumber when field exists
|
||||||
if maxTargets <= 0 {
|
if maxTargets <= 0 {
|
||||||
maxTargets = 10 // Default limit
|
maxTargets = 10 // Default limit
|
||||||
}
|
}
|
||||||
@ -416,8 +416,8 @@ func (st *SpellTargeting) RequiresTarget(spell *Spell) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetTargetingInfo returns information about spell targeting requirements
|
// GetTargetingInfo returns information about spell targeting requirements
|
||||||
func (st *SpellTargeting) GetTargetingInfo(spell *Spell) map[string]interface{} {
|
func (st *SpellTargeting) GetTargetingInfo(spell *Spell) map[string]any {
|
||||||
info := make(map[string]interface{})
|
info := make(map[string]any)
|
||||||
|
|
||||||
if spell == nil {
|
if spell == nil {
|
||||||
return info
|
return info
|
||||||
|
@ -339,11 +339,11 @@ func (mtl *MasterTitlesList) UpdateTitle(title *Title) error {
|
|||||||
categorySlice := mtl.categorized[existing.Category]
|
categorySlice := mtl.categorized[existing.Category]
|
||||||
mtl.removeFromSlice(&categorySlice, existing)
|
mtl.removeFromSlice(&categorySlice, existing)
|
||||||
mtl.categorized[existing.Category] = categorySlice
|
mtl.categorized[existing.Category] = categorySlice
|
||||||
|
|
||||||
sourceSlice := mtl.bySource[existing.Source]
|
sourceSlice := mtl.bySource[existing.Source]
|
||||||
mtl.removeFromSlice(&sourceSlice, existing)
|
mtl.removeFromSlice(&sourceSlice, existing)
|
||||||
mtl.bySource[existing.Source] = sourceSlice
|
mtl.bySource[existing.Source] = sourceSlice
|
||||||
|
|
||||||
raritySlice := mtl.byRarity[existing.Rarity]
|
raritySlice := mtl.byRarity[existing.Rarity]
|
||||||
mtl.removeFromSlice(&raritySlice, existing)
|
mtl.removeFromSlice(&raritySlice, existing)
|
||||||
mtl.byRarity[existing.Rarity] = raritySlice
|
mtl.byRarity[existing.Rarity] = raritySlice
|
||||||
@ -487,7 +487,7 @@ func (mtl *MasterTitlesList) SaveToDatabase(db *database.DB) error {
|
|||||||
|
|
||||||
// Insert all current titles
|
// Insert all current titles
|
||||||
for _, title := range mtl.titles {
|
for _, title := range mtl.titles {
|
||||||
var achievementID interface{}
|
var achievementID any
|
||||||
if title.AchievementID != 0 {
|
if title.AchievementID != 0 {
|
||||||
achievementID = title.AchievementID
|
achievementID = title.AchievementID
|
||||||
}
|
}
|
||||||
@ -495,9 +495,9 @@ func (mtl *MasterTitlesList) SaveToDatabase(db *database.DB) error {
|
|||||||
err := txDB.Exec(`
|
err := txDB.Exec(`
|
||||||
INSERT INTO titles (id, name, description, category, position, source, rarity, flags, achievement_id)
|
INSERT INTO titles (id, name, description, category, position, source, rarity, flags, achievement_id)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
`, title.ID, title.Name, title.Description, title.Category,
|
`, title.ID, title.Name, title.Description, title.Category,
|
||||||
int(title.Position), int(title.Source), int(title.Rarity),
|
int(title.Position), int(title.Source), int(title.Rarity),
|
||||||
int64(title.Flags), achievementID)
|
int64(title.Flags), achievementID)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to insert title %d: %w", title.ID, err)
|
return fmt.Errorf("failed to insert title %d: %w", title.ID, err)
|
||||||
|
@ -451,7 +451,7 @@ func (ptl *PlayerTitlesList) LoadFromDatabase(db *database.DB) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load all titles for this player
|
// Load all titles for this player
|
||||||
err := db.Query("SELECT title_id, achievement_id, granted_date, expiration_date, is_active FROM player_titles WHERE player_id = ?",
|
err := db.Query("SELECT title_id, achievement_id, granted_date, expiration_date, is_active FROM player_titles WHERE player_id = ?",
|
||||||
func(row *database.Row) error {
|
func(row *database.Row) error {
|
||||||
playerTitle := &PlayerTitle{
|
playerTitle := &PlayerTitle{
|
||||||
TitleID: int32(row.Int64(0)),
|
TitleID: int32(row.Int64(0)),
|
||||||
@ -499,12 +499,12 @@ func (ptl *PlayerTitlesList) SaveToDatabase(db *database.DB) error {
|
|||||||
|
|
||||||
// Insert all current titles
|
// Insert all current titles
|
||||||
for _, playerTitle := range ptl.titles {
|
for _, playerTitle := range ptl.titles {
|
||||||
var achievementID interface{}
|
var achievementID any
|
||||||
if playerTitle.AchievementID != 0 {
|
if playerTitle.AchievementID != 0 {
|
||||||
achievementID = playerTitle.AchievementID
|
achievementID = playerTitle.AchievementID
|
||||||
}
|
}
|
||||||
|
|
||||||
var expirationDate interface{}
|
var expirationDate any
|
||||||
if !playerTitle.ExpiresAt.IsZero() {
|
if !playerTitle.ExpiresAt.IsZero() {
|
||||||
expirationDate = playerTitle.ExpiresAt.Unix()
|
expirationDate = playerTitle.ExpiresAt.Unix()
|
||||||
}
|
}
|
||||||
@ -519,8 +519,8 @@ func (ptl *PlayerTitlesList) SaveToDatabase(db *database.DB) error {
|
|||||||
err := txDB.Exec(`
|
err := txDB.Exec(`
|
||||||
INSERT INTO player_titles (player_id, title_id, achievement_id, granted_date, expiration_date, is_active)
|
INSERT INTO player_titles (player_id, title_id, achievement_id, granted_date, expiration_date, is_active)
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
`, ptl.playerID, playerTitle.TitleID, achievementID,
|
`, ptl.playerID, playerTitle.TitleID, achievementID,
|
||||||
playerTitle.EarnedDate.Unix(), expirationDate, isActive)
|
playerTitle.EarnedDate.Unix(), expirationDate, isActive)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to insert player title %d: %w", playerTitle.TitleID, err)
|
return fmt.Errorf("failed to insert player title %d: %w", playerTitle.TitleID, err)
|
||||||
|
@ -319,11 +319,11 @@ func (tm *TitleManager) cleanupExpiredTitles() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetStatistics returns title system statistics
|
// GetStatistics returns title system statistics
|
||||||
func (tm *TitleManager) GetStatistics() map[string]interface{} {
|
func (tm *TitleManager) GetStatistics() map[string]any {
|
||||||
tm.mutex.RLock()
|
tm.mutex.RLock()
|
||||||
defer tm.mutex.RUnlock()
|
defer tm.mutex.RUnlock()
|
||||||
|
|
||||||
return map[string]interface{}{
|
return map[string]any{
|
||||||
"total_titles": tm.masterList.GetTitleCount(),
|
"total_titles": tm.masterList.GetTitleCount(),
|
||||||
"total_players": len(tm.playerLists),
|
"total_players": len(tm.playerLists),
|
||||||
"titles_granted": tm.totalTitlesGranted,
|
"titles_granted": tm.totalTitlesGranted,
|
||||||
|
@ -146,7 +146,7 @@ func (ts *TradeService) CancelTrade(entityID int32) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetTradeInfo returns comprehensive information about a trade
|
// GetTradeInfo returns comprehensive information about a trade
|
||||||
func (ts *TradeService) GetTradeInfo(entityID int32) (map[string]interface{}, error) {
|
func (ts *TradeService) GetTradeInfo(entityID int32) (map[string]any, error) {
|
||||||
trade := ts.tradeManager.GetTrade(entityID)
|
trade := ts.tradeManager.GetTrade(entityID)
|
||||||
if trade == nil {
|
if trade == nil {
|
||||||
return nil, fmt.Errorf("entity is not in a trade")
|
return nil, fmt.Errorf("entity is not in a trade")
|
||||||
@ -174,8 +174,8 @@ func (ts *TradeService) ProcessTrades() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetTradeStatistics returns statistics about trade activity
|
// GetTradeStatistics returns statistics about trade activity
|
||||||
func (ts *TradeService) GetTradeStatistics() map[string]interface{} {
|
func (ts *TradeService) GetTradeStatistics() map[string]any {
|
||||||
stats := make(map[string]interface{})
|
stats := make(map[string]any)
|
||||||
|
|
||||||
stats["active_trades"] = ts.tradeManager.GetActiveTradeCount()
|
stats["active_trades"] = ts.tradeManager.GetActiveTradeCount()
|
||||||
stats["max_trade_duration_minutes"] = ts.maxTradeDuration.Minutes()
|
stats["max_trade_duration_minutes"] = ts.maxTradeDuration.Minutes()
|
||||||
|
@ -392,11 +392,11 @@ func (t *Trade) GetTraderSlot(entityID int32, slot int8) Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetTradeInfo returns comprehensive information about the trade
|
// GetTradeInfo returns comprehensive information about the trade
|
||||||
func (t *Trade) GetTradeInfo() map[string]interface{} {
|
func (t *Trade) GetTradeInfo() map[string]any {
|
||||||
t.mutex.RLock()
|
t.mutex.RLock()
|
||||||
defer t.mutex.RUnlock()
|
defer t.mutex.RUnlock()
|
||||||
|
|
||||||
info := make(map[string]interface{})
|
info := make(map[string]any)
|
||||||
info["state"] = t.state
|
info["state"] = t.state
|
||||||
info["start_time"] = t.startTime
|
info["start_time"] = t.startTime
|
||||||
info["trader1_id"] = t.trader1.EntityID
|
info["trader1_id"] = t.trader1.EntityID
|
||||||
|
@ -127,7 +127,7 @@ func IsValidTradeState(state TradeState, operation string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GenerateTradeLogEntry creates a log entry for trade operations
|
// GenerateTradeLogEntry creates a log entry for trade operations
|
||||||
func GenerateTradeLogEntry(tradeID string, operation string, entityID int32, details interface{}) string {
|
func GenerateTradeLogEntry(tradeID string, operation string, entityID int32, details any) string {
|
||||||
return fmt.Sprintf("[Trade:%s] %s by entity %d: %v", tradeID, operation, entityID, details)
|
return fmt.Sprintf("[Trade:%s] %s by entity %d: %v", tradeID, operation, entityID, details)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,8 +147,8 @@ func CompareTradeItems(item1, item2 TradeItemInfo) bool {
|
|||||||
|
|
||||||
// CalculateTradeValue estimates the total value of items and coins in a trade
|
// CalculateTradeValue estimates the total value of items and coins in a trade
|
||||||
// This is a helper function for trade balancing and analysis
|
// This is a helper function for trade balancing and analysis
|
||||||
func CalculateTradeValue(participant *TradeParticipant) map[string]interface{} {
|
func CalculateTradeValue(participant *TradeParticipant) map[string]any {
|
||||||
value := make(map[string]interface{})
|
value := make(map[string]any)
|
||||||
|
|
||||||
// Add coin value
|
// Add coin value
|
||||||
value["coins"] = participant.Coins
|
value["coins"] = participant.Coins
|
||||||
@ -159,9 +159,9 @@ func CalculateTradeValue(participant *TradeParticipant) map[string]interface{} {
|
|||||||
value["item_count"] = itemCount
|
value["item_count"] = itemCount
|
||||||
|
|
||||||
if itemCount > 0 {
|
if itemCount > 0 {
|
||||||
items := make([]map[string]interface{}, 0, itemCount)
|
items := make([]map[string]any, 0, itemCount)
|
||||||
for slot, itemInfo := range participant.Items {
|
for slot, itemInfo := range participant.Items {
|
||||||
itemData := make(map[string]interface{})
|
itemData := make(map[string]any)
|
||||||
itemData["slot"] = slot
|
itemData["slot"] = slot
|
||||||
itemData["quantity"] = itemInfo.Quantity
|
itemData["quantity"] = itemInfo.Quantity
|
||||||
if itemInfo.Item != nil {
|
if itemInfo.Item != nil {
|
||||||
|
@ -596,11 +596,11 @@ func (tsa *TradeskillSystemAdapter) updatePlayerRecipeStage(currentStage int8, c
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetSystemStats returns comprehensive statistics about the tradeskill system.
|
// GetSystemStats returns comprehensive statistics about the tradeskill system.
|
||||||
func (tsa *TradeskillSystemAdapter) GetSystemStats() map[string]interface{} {
|
func (tsa *TradeskillSystemAdapter) GetSystemStats() map[string]any {
|
||||||
managerStats := tsa.manager.GetStats()
|
managerStats := tsa.manager.GetStats()
|
||||||
eventsStats := tsa.eventsList.GetStats()
|
eventsStats := tsa.eventsList.GetStats()
|
||||||
|
|
||||||
return map[string]interface{}{
|
return map[string]any{
|
||||||
"active_sessions": managerStats.ActiveSessions,
|
"active_sessions": managerStats.ActiveSessions,
|
||||||
"recent_completions": managerStats.RecentCompletions,
|
"recent_completions": managerStats.RecentCompletions,
|
||||||
"average_session_time": managerStats.AverageSessionTime,
|
"average_session_time": managerStats.AverageSessionTime,
|
||||||
|
@ -348,7 +348,7 @@ func (tsa *TraitSystemAdapter) IsPlayerAllowedTrait(playerID uint32, spellID uin
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetPlayerTraitStats returns statistics about a player's trait selections.
|
// GetPlayerTraitStats returns statistics about a player's trait selections.
|
||||||
func (tsa *TraitSystemAdapter) GetPlayerTraitStats(playerID uint32) (map[string]interface{}, error) {
|
func (tsa *TraitSystemAdapter) GetPlayerTraitStats(playerID uint32) (map[string]any, error) {
|
||||||
// Get player information
|
// Get player information
|
||||||
player, err := tsa.playerManager.GetPlayer(playerID)
|
player, err := tsa.playerManager.GetPlayer(playerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -381,7 +381,7 @@ func (tsa *TraitSystemAdapter) GetPlayerTraitStats(playerID uint32) (map[string]
|
|||||||
tsa.masterList.getSpellCount(playerState, playerState.TraitLists.InnateRaceTraits, false)
|
tsa.masterList.getSpellCount(playerState, playerState.TraitLists.InnateRaceTraits, false)
|
||||||
focusEffects := tsa.masterList.getSpellCount(playerState, playerState.TraitLists.FocusEffects, false)
|
focusEffects := tsa.masterList.getSpellCount(playerState, playerState.TraitLists.FocusEffects, false)
|
||||||
|
|
||||||
return map[string]interface{}{
|
return map[string]any{
|
||||||
"player_id": playerID,
|
"player_id": playerID,
|
||||||
"level": playerState.Level,
|
"level": playerState.Level,
|
||||||
"character_traits": characterTraits,
|
"character_traits": characterTraits,
|
||||||
@ -394,11 +394,11 @@ func (tsa *TraitSystemAdapter) GetPlayerTraitStats(playerID uint32) (map[string]
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetSystemStats returns comprehensive statistics about the trait system.
|
// GetSystemStats returns comprehensive statistics about the trait system.
|
||||||
func (tsa *TraitSystemAdapter) GetSystemStats() map[string]interface{} {
|
func (tsa *TraitSystemAdapter) GetSystemStats() map[string]any {
|
||||||
masterStats := tsa.masterList.GetStats()
|
masterStats := tsa.masterList.GetStats()
|
||||||
managerStats := tsa.traitManager.GetManagerStats()
|
managerStats := tsa.traitManager.GetManagerStats()
|
||||||
|
|
||||||
return map[string]interface{}{
|
return map[string]any{
|
||||||
"total_traits": masterStats.TotalTraits,
|
"total_traits": masterStats.TotalTraits,
|
||||||
"traits_by_type": masterStats.TraitsByType,
|
"traits_by_type": masterStats.TraitsByType,
|
||||||
"traits_by_group": masterStats.TraitsByGroup,
|
"traits_by_group": masterStats.TraitsByGroup,
|
||||||
|
@ -325,7 +325,7 @@ func (mtl *MasterTraitList) getClassicAvailability(levelLimits []int16, totalUse
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getSpellCount counts how many spells from a trait map the player has selected.
|
// getSpellCount counts how many spells from a trait map the player has selected.
|
||||||
func (mtl *MasterTraitList) getSpellCount(playerState *PlayerTraitState, traitMap interface{}, onlyCharTraits bool) int16 {
|
func (mtl *MasterTraitList) getSpellCount(playerState *PlayerTraitState, traitMap any, onlyCharTraits bool) int16 {
|
||||||
count := int16(0)
|
count := int16(0)
|
||||||
|
|
||||||
switch tm := traitMap.(type) {
|
switch tm := traitMap.(type) {
|
||||||
|
@ -94,11 +94,11 @@ func (m *Manager) ReloadTransmutingTiers() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetStatistics returns transmutation statistics
|
// GetStatistics returns transmutation statistics
|
||||||
func (m *Manager) GetStatistics() map[string]interface{} {
|
func (m *Manager) GetStatistics() map[string]any {
|
||||||
m.mutex.RLock()
|
m.mutex.RLock()
|
||||||
defer m.mutex.RUnlock()
|
defer m.mutex.RUnlock()
|
||||||
|
|
||||||
stats := make(map[string]interface{})
|
stats := make(map[string]any)
|
||||||
stats["total_transmutes"] = m.totalTransmutes
|
stats["total_transmutes"] = m.totalTransmutes
|
||||||
stats["successful_transmutes"] = m.successfulTransmutes
|
stats["successful_transmutes"] = m.successfulTransmutes
|
||||||
stats["failed_transmutes"] = m.failedTransmutes
|
stats["failed_transmutes"] = m.failedTransmutes
|
||||||
|
@ -71,7 +71,7 @@ type Client interface {
|
|||||||
SetTransmuteID(id int32)
|
SetTransmuteID(id int32)
|
||||||
QueuePacket(packet []byte)
|
QueuePacket(packet []byte)
|
||||||
SimpleMessage(channel int32, message string)
|
SimpleMessage(channel int32, message string)
|
||||||
Message(channel int32, format string, args ...interface{})
|
Message(channel int32, format string, args ...any)
|
||||||
AddItem(item Item, itemDeleted *bool) error
|
AddItem(item Item, itemDeleted *bool) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
type ClientInterface interface {
|
type ClientInterface interface {
|
||||||
GetPlayer() *spawn.Spawn
|
GetPlayer() *spawn.Spawn
|
||||||
SetTemporaryTransportID(id int32)
|
SetTemporaryTransportID(id int32)
|
||||||
ProcessTeleport(widget *Widget, destinations []interface{}, transporterID int32)
|
ProcessTeleport(widget *Widget, destinations []any, transporterID int32)
|
||||||
GetVersion() int32
|
GetVersion() int32
|
||||||
GetCurrentZone() ZoneInterface
|
GetCurrentZone() ZoneInterface
|
||||||
}
|
}
|
||||||
@ -21,8 +21,8 @@ type ZoneInterface interface {
|
|||||||
PlaySoundFile(unknown int32, soundFile string, x, y, z float32)
|
PlaySoundFile(unknown int32, soundFile string, x, y, z float32)
|
||||||
CallSpawnScript(s *spawn.Spawn, scriptType string, caller *spawn.Spawn, extra string, state bool) bool
|
CallSpawnScript(s *spawn.Spawn, scriptType string, caller *spawn.Spawn, extra string, state bool) bool
|
||||||
GetSpawnByDatabaseID(id int32) *spawn.Spawn
|
GetSpawnByDatabaseID(id int32) *spawn.Spawn
|
||||||
GetTransporters(client ClientInterface, transporterID int32) []interface{}
|
GetTransporters(client ClientInterface, transporterID int32) []any
|
||||||
ProcessEntityCommand(command interface{}, player *spawn.Spawn, target *spawn.Spawn)
|
ProcessEntityCommand(command any, player *spawn.Spawn, target *spawn.Spawn)
|
||||||
GetInstanceID() int32
|
GetInstanceID() int32
|
||||||
GetInstanceType() int32
|
GetInstanceType() int32
|
||||||
SendHouseItems(client ClientInterface)
|
SendHouseItems(client ClientInterface)
|
||||||
|
@ -295,11 +295,11 @@ func (m *Manager) Clear() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetStatistics returns widget statistics
|
// GetStatistics returns widget statistics
|
||||||
func (m *Manager) GetStatistics() map[string]interface{} {
|
func (m *Manager) GetStatistics() map[string]any {
|
||||||
m.mutex.RLock()
|
m.mutex.RLock()
|
||||||
defer m.mutex.RUnlock()
|
defer m.mutex.RUnlock()
|
||||||
|
|
||||||
stats := make(map[string]interface{})
|
stats := make(map[string]any)
|
||||||
stats["total_widgets"] = len(m.widgets)
|
stats["total_widgets"] = len(m.widgets)
|
||||||
stats["door_count"] = len(m.GetDoorWidgets())
|
stats["door_count"] = len(m.GetDoorWidgets())
|
||||||
stats["lift_count"] = len(m.GetLiftWidgets())
|
stats["lift_count"] = len(m.GetLiftWidgets())
|
||||||
|
@ -97,8 +97,8 @@ type NPC interface {
|
|||||||
SetRespawnTime(seconds int32)
|
SetRespawnTime(seconds int32)
|
||||||
GetSpawnGroupID() int32
|
GetSpawnGroupID() int32
|
||||||
SetSpawnGroupID(groupID int32)
|
SetSpawnGroupID(groupID int32)
|
||||||
GetRandomizedFeatures() map[string]interface{}
|
GetRandomizedFeatures() map[string]any
|
||||||
SetRandomizedFeatures(features map[string]interface{})
|
SetRandomizedFeatures(features map[string]any)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Object interface represents an interactive world object
|
// Object interface represents an interactive world object
|
||||||
@ -337,12 +337,12 @@ type Recipe interface {
|
|||||||
|
|
||||||
// MovementLocation represents a movement waypoint for NPCs
|
// MovementLocation represents a movement waypoint for NPCs
|
||||||
type MovementLocation struct {
|
type MovementLocation struct {
|
||||||
X float32
|
X float32
|
||||||
Y float32
|
Y float32
|
||||||
Z float32
|
Z float32
|
||||||
Heading float32
|
Heading float32
|
||||||
Speed float32
|
Speed float32
|
||||||
Delay int32
|
Delay int32
|
||||||
MovementType int8
|
MovementType int8
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -357,7 +357,7 @@ type PathNode struct {
|
|||||||
type SpawnLocation struct {
|
type SpawnLocation struct {
|
||||||
ID int32
|
ID int32
|
||||||
X float32
|
X float32
|
||||||
Y float32
|
Y float32
|
||||||
Z float32
|
Z float32
|
||||||
Heading float32
|
Heading float32
|
||||||
Pitch float32
|
Pitch float32
|
||||||
@ -375,46 +375,46 @@ type SpawnLocation struct {
|
|||||||
|
|
||||||
// SpawnEntry contains the template data for spawns
|
// SpawnEntry contains the template data for spawns
|
||||||
type SpawnEntry struct {
|
type SpawnEntry struct {
|
||||||
ID int32
|
ID int32
|
||||||
SpawnType int8
|
SpawnType int8
|
||||||
SpawnEntryID int32
|
SpawnEntryID int32
|
||||||
Name string
|
Name string
|
||||||
Level int16
|
Level int16
|
||||||
EncounterLevel int16
|
EncounterLevel int16
|
||||||
Model string
|
Model string
|
||||||
Size float32
|
Size float32
|
||||||
HP int32
|
HP int32
|
||||||
Power int32
|
Power int32
|
||||||
Heroic int8
|
Heroic int8
|
||||||
Gender int8
|
Gender int8
|
||||||
Race int16
|
Race int16
|
||||||
AdventureClass int16
|
AdventureClass int16
|
||||||
TradeskillClass int16
|
TradeskillClass int16
|
||||||
AttackType int8
|
AttackType int8
|
||||||
MinLevel int16
|
MinLevel int16
|
||||||
MaxLevel int16
|
MaxLevel int16
|
||||||
EncounterType int8
|
EncounterType int8
|
||||||
ShowName int8
|
ShowName int8
|
||||||
Targetable int8
|
Targetable int8
|
||||||
ShowLevel int8
|
ShowLevel int8
|
||||||
Command string
|
Command string
|
||||||
LootTier int8
|
LootTier int8
|
||||||
MinGold int32
|
MinGold int32
|
||||||
MaxGold int32
|
MaxGold int32
|
||||||
HarvestType string
|
HarvestType string
|
||||||
Icon int32
|
Icon int32
|
||||||
}
|
}
|
||||||
|
|
||||||
// EntityCommand represents an available command for an entity
|
// EntityCommand represents an available command for an entity
|
||||||
type EntityCommand struct {
|
type EntityCommand struct {
|
||||||
ID int32
|
ID int32
|
||||||
Name string
|
Name string
|
||||||
Distance float32
|
Distance float32
|
||||||
ErrorText string
|
ErrorText string
|
||||||
CastTime int16
|
CastTime int16
|
||||||
SpellVisual int32
|
SpellVisual int32
|
||||||
Command string
|
Command string
|
||||||
DisplayText string
|
DisplayText string
|
||||||
}
|
}
|
||||||
|
|
||||||
// LootTable represents a loot table configuration
|
// LootTable represents a loot table configuration
|
||||||
@ -433,9 +433,9 @@ type LootTable struct {
|
|||||||
type LootDrop struct {
|
type LootDrop struct {
|
||||||
LootTableID int32
|
LootTableID int32
|
||||||
ItemID int32
|
ItemID int32
|
||||||
ItemCharges int16
|
ItemCharges int16
|
||||||
EquipItem bool
|
EquipItem bool
|
||||||
Probability float32
|
Probability float32
|
||||||
NoDropQuestCompletedID int32
|
NoDropQuestCompletedID int32
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -452,51 +452,51 @@ type GlobalLoot struct {
|
|||||||
|
|
||||||
// TransportDestination represents a transport destination
|
// TransportDestination represents a transport destination
|
||||||
type TransportDestination struct {
|
type TransportDestination struct {
|
||||||
ID int32
|
ID int32
|
||||||
Type int8
|
Type int8
|
||||||
Name string
|
Name string
|
||||||
Message string
|
Message string
|
||||||
DestinationZoneID int32
|
DestinationZoneID int32
|
||||||
DestinationX float32
|
DestinationX float32
|
||||||
DestinationY float32
|
DestinationY float32
|
||||||
DestinationZ float32
|
DestinationZ float32
|
||||||
DestinationHeading float32
|
DestinationHeading float32
|
||||||
Cost int32
|
Cost int32
|
||||||
UniqueID int32
|
UniqueID int32
|
||||||
MinLevel int8
|
MinLevel int8
|
||||||
MaxLevel int8
|
MaxLevel int8
|
||||||
QuestRequired int32
|
QuestRequired int32
|
||||||
QuestStepRequired int16
|
QuestStepRequired int16
|
||||||
QuestCompleted int32
|
QuestCompleted int32
|
||||||
MapX int32
|
MapX int32
|
||||||
MapY int32
|
MapY int32
|
||||||
ExpansionFlag int32
|
ExpansionFlag int32
|
||||||
HolidayFlag int32
|
HolidayFlag int32
|
||||||
MinClientVersion int32
|
MinClientVersion int32
|
||||||
MaxClientVersion int32
|
MaxClientVersion int32
|
||||||
FlightPathID int32
|
FlightPathID int32
|
||||||
MountID int16
|
MountID int16
|
||||||
MountRedColor int8
|
MountRedColor int8
|
||||||
MountGreenColor int8
|
MountGreenColor int8
|
||||||
MountBlueColor int8
|
MountBlueColor int8
|
||||||
}
|
}
|
||||||
|
|
||||||
// LocationTransportDestination represents a location-based transport trigger
|
// LocationTransportDestination represents a location-based transport trigger
|
||||||
type LocationTransportDestination struct {
|
type LocationTransportDestination struct {
|
||||||
ZoneID int32
|
ZoneID int32
|
||||||
Message string
|
Message string
|
||||||
TriggerX float32
|
TriggerX float32
|
||||||
TriggerY float32
|
TriggerY float32
|
||||||
TriggerZ float32
|
TriggerZ float32
|
||||||
TriggerRadius float32
|
TriggerRadius float32
|
||||||
DestinationZoneID int32
|
DestinationZoneID int32
|
||||||
DestinationX float32
|
DestinationX float32
|
||||||
DestinationY float32
|
DestinationY float32
|
||||||
DestinationZ float32
|
DestinationZ float32
|
||||||
DestinationHeading float32
|
DestinationHeading float32
|
||||||
Cost int32
|
Cost int32
|
||||||
UniqueID int32
|
UniqueID int32
|
||||||
ForceZone bool
|
ForceZone bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Location represents a discoverable location
|
// Location represents a discoverable location
|
||||||
@ -509,4 +509,4 @@ type Location struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Item placeholder - should import from items package
|
// Item placeholder - should import from items package
|
||||||
type Item = items.Item
|
type Item = items.Item
|
||||||
|
Loading…
x
Reference in New Issue
Block a user