762 lines
19 KiB
Go
762 lines
19 KiB
Go
package npc
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// Manager provides high-level management of the NPC system
|
|
type Manager struct {
|
|
npcs map[int32]*NPC // NPCs indexed by ID
|
|
npcsByZone map[int32][]*NPC // NPCs indexed by zone ID
|
|
npcsByAppearance map[int32][]*NPC // NPCs indexed by appearance ID
|
|
database Database // Database interface
|
|
logger Logger // Logger interface
|
|
spellManager SpellManager // Spell system interface
|
|
skillManager SkillManager // Skill system interface
|
|
appearanceManager AppearanceManager // Appearance system interface
|
|
mutex sync.RWMutex // Thread safety
|
|
|
|
// Statistics
|
|
totalNPCs int64
|
|
npcsInCombat int64
|
|
spellCastCount int64
|
|
skillUsageCount int64
|
|
runbackCount int64
|
|
aiStrategyCounts map[int8]int64
|
|
|
|
// Configuration
|
|
maxNPCs int32
|
|
defaultAggroRadius float32
|
|
enableAI bool
|
|
}
|
|
|
|
// NewManager creates a new NPC manager
|
|
func NewManager(database Database, logger Logger) *Manager {
|
|
return &Manager{
|
|
npcs: make(map[int32]*NPC),
|
|
npcsByZone: make(map[int32][]*NPC),
|
|
npcsByAppearance: make(map[int32][]*NPC),
|
|
database: database,
|
|
logger: logger,
|
|
aiStrategyCounts: make(map[int8]int64),
|
|
maxNPCs: 10000, // Default limit
|
|
defaultAggroRadius: DefaultAggroRadius,
|
|
enableAI: true,
|
|
}
|
|
}
|
|
|
|
// Initialize loads NPCs from database and sets up the system
|
|
func (m *Manager) Initialize() error {
|
|
if m.logger != nil {
|
|
m.logger.LogInfo("Initializing NPC manager...")
|
|
}
|
|
|
|
if m.database == nil {
|
|
if m.logger != nil {
|
|
m.logger.LogWarning("No database provided, starting with empty NPC list")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Load NPCs from database
|
|
npcs, err := m.database.LoadAllNPCs()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load NPCs from database: %w", err)
|
|
}
|
|
|
|
for _, npc := range npcs {
|
|
if err := m.addNPCInternal(npc); err != nil {
|
|
if m.logger != nil {
|
|
m.logger.LogError("Failed to add NPC %d: %v", npc.GetNPCID(), err)
|
|
}
|
|
}
|
|
}
|
|
|
|
if m.logger != nil {
|
|
m.logger.LogInfo("Loaded %d NPCs from database", len(npcs))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// AddNPC adds a new NPC to the system
|
|
func (m *Manager) AddNPC(npc *NPC) error {
|
|
if npc == nil {
|
|
return fmt.Errorf("NPC cannot be nil")
|
|
}
|
|
|
|
if !npc.IsValid() {
|
|
return fmt.Errorf("NPC is not valid: %s", npc.String())
|
|
}
|
|
|
|
m.mutex.Lock()
|
|
defer m.mutex.Unlock()
|
|
|
|
if len(m.npcs) >= int(m.maxNPCs) {
|
|
return fmt.Errorf("maximum NPC limit reached (%d)", m.maxNPCs)
|
|
}
|
|
|
|
return m.addNPCInternal(npc)
|
|
}
|
|
|
|
// addNPCInternal adds an NPC without locking (internal use)
|
|
func (m *Manager) addNPCInternal(npc *NPC) error {
|
|
npcID := npc.GetNPCID()
|
|
|
|
// Check for duplicate ID
|
|
if _, exists := m.npcs[npcID]; exists {
|
|
return fmt.Errorf("NPC with ID %d already exists", npcID)
|
|
}
|
|
|
|
// Add to main index
|
|
m.npcs[npcID] = npc
|
|
|
|
// Add to zone index
|
|
if npc.Entity != nil {
|
|
zoneID := npc.Entity.GetZoneID()
|
|
m.npcsByZone[zoneID] = append(m.npcsByZone[zoneID], npc)
|
|
}
|
|
|
|
// Add to appearance index
|
|
appearanceID := npc.GetAppearanceID()
|
|
m.npcsByAppearance[appearanceID] = append(m.npcsByAppearance[appearanceID], npc)
|
|
|
|
// Update statistics
|
|
m.totalNPCs++
|
|
strategy := npc.GetAIStrategy()
|
|
m.aiStrategyCounts[strategy]++
|
|
|
|
// Save to database if available
|
|
if m.database != nil {
|
|
if err := m.database.SaveNPC(npc); err != nil {
|
|
// Remove from indexes if database save failed
|
|
delete(m.npcs, npcID)
|
|
m.removeFromZoneIndex(npc)
|
|
m.removeFromAppearanceIndex(npc)
|
|
m.totalNPCs--
|
|
m.aiStrategyCounts[strategy]--
|
|
return fmt.Errorf("failed to save NPC to database: %w", err)
|
|
}
|
|
}
|
|
|
|
if m.logger != nil {
|
|
m.logger.LogInfo("Added NPC %d: %s", npcID, npc.String())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetNPC retrieves an NPC by ID
|
|
func (m *Manager) GetNPC(id int32) *NPC {
|
|
m.mutex.RLock()
|
|
defer m.mutex.RUnlock()
|
|
|
|
return m.npcs[id]
|
|
}
|
|
|
|
// GetNPCsByZone retrieves all NPCs in a specific zone
|
|
func (m *Manager) GetNPCsByZone(zoneID int32) []*NPC {
|
|
m.mutex.RLock()
|
|
defer m.mutex.RUnlock()
|
|
|
|
npcs := m.npcsByZone[zoneID]
|
|
result := make([]*NPC, len(npcs))
|
|
copy(result, npcs)
|
|
return result
|
|
}
|
|
|
|
// GetNPCsByAppearance retrieves all NPCs with a specific appearance
|
|
func (m *Manager) GetNPCsByAppearance(appearanceID int32) []*NPC {
|
|
m.mutex.RLock()
|
|
defer m.mutex.RUnlock()
|
|
|
|
npcs := m.npcsByAppearance[appearanceID]
|
|
result := make([]*NPC, len(npcs))
|
|
copy(result, npcs)
|
|
return result
|
|
}
|
|
|
|
// RemoveNPC removes an NPC from the system
|
|
func (m *Manager) RemoveNPC(id int32) error {
|
|
m.mutex.Lock()
|
|
defer m.mutex.Unlock()
|
|
|
|
npc, exists := m.npcs[id]
|
|
if !exists {
|
|
return fmt.Errorf("NPC with ID %d does not exist", id)
|
|
}
|
|
|
|
// Remove from database first if available
|
|
if m.database != nil {
|
|
if err := m.database.DeleteNPC(id); err != nil {
|
|
return fmt.Errorf("failed to delete NPC from database: %w", err)
|
|
}
|
|
}
|
|
|
|
// Remove from all indexes
|
|
delete(m.npcs, id)
|
|
m.removeFromZoneIndex(npc)
|
|
m.removeFromAppearanceIndex(npc)
|
|
|
|
// Update statistics
|
|
m.totalNPCs--
|
|
strategy := npc.GetAIStrategy()
|
|
if count := m.aiStrategyCounts[strategy]; count > 0 {
|
|
m.aiStrategyCounts[strategy]--
|
|
}
|
|
|
|
if m.logger != nil {
|
|
m.logger.LogInfo("Removed NPC %d", id)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// UpdateNPC updates an existing NPC
|
|
func (m *Manager) UpdateNPC(npc *NPC) error {
|
|
if npc == nil {
|
|
return fmt.Errorf("NPC cannot be nil")
|
|
}
|
|
|
|
if !npc.IsValid() {
|
|
return fmt.Errorf("NPC is not valid: %s", npc.String())
|
|
}
|
|
|
|
m.mutex.Lock()
|
|
defer m.mutex.Unlock()
|
|
|
|
npcID := npc.GetNPCID()
|
|
oldNPC, exists := m.npcs[npcID]
|
|
if !exists {
|
|
return fmt.Errorf("NPC with ID %d does not exist", npcID)
|
|
}
|
|
|
|
// Update indexes if zone or appearance changed
|
|
if npc.Entity != nil && oldNPC.Entity != nil {
|
|
if npc.Entity.GetZoneID() != oldNPC.Entity.GetZoneID() {
|
|
m.removeFromZoneIndex(oldNPC)
|
|
zoneID := npc.Entity.GetZoneID()
|
|
m.npcsByZone[zoneID] = append(m.npcsByZone[zoneID], npc)
|
|
}
|
|
}
|
|
|
|
if npc.GetAppearanceID() != oldNPC.GetAppearanceID() {
|
|
m.removeFromAppearanceIndex(oldNPC)
|
|
appearanceID := npc.GetAppearanceID()
|
|
m.npcsByAppearance[appearanceID] = append(m.npcsByAppearance[appearanceID], npc)
|
|
}
|
|
|
|
// Update AI strategy statistics
|
|
oldStrategy := oldNPC.GetAIStrategy()
|
|
newStrategy := npc.GetAIStrategy()
|
|
if oldStrategy != newStrategy {
|
|
if count := m.aiStrategyCounts[oldStrategy]; count > 0 {
|
|
m.aiStrategyCounts[oldStrategy]--
|
|
}
|
|
m.aiStrategyCounts[newStrategy]++
|
|
}
|
|
|
|
// Update main index
|
|
m.npcs[npcID] = npc
|
|
|
|
// Save to database if available
|
|
if m.database != nil {
|
|
if err := m.database.SaveNPC(npc); err != nil {
|
|
return fmt.Errorf("failed to save NPC to database: %w", err)
|
|
}
|
|
}
|
|
|
|
if m.logger != nil {
|
|
m.logger.LogInfo("Updated NPC %d: %s", npcID, npc.String())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CreateNPCFromTemplate creates a new NPC from an existing template
|
|
func (m *Manager) CreateNPCFromTemplate(templateID, newID int32) (*NPC, error) {
|
|
template := m.GetNPC(templateID)
|
|
if template == nil {
|
|
return nil, fmt.Errorf("template NPC with ID %d not found", templateID)
|
|
}
|
|
|
|
// Create new NPC from template
|
|
newNPC := NewNPCFromExisting(template)
|
|
newNPC.SetNPCID(newID)
|
|
|
|
// Add to system
|
|
if err := m.AddNPC(newNPC); err != nil {
|
|
return nil, fmt.Errorf("failed to add new NPC: %w", err)
|
|
}
|
|
|
|
return newNPC, nil
|
|
}
|
|
|
|
// GetRandomNPCByAppearance returns a random NPC with the specified appearance
|
|
func (m *Manager) GetRandomNPCByAppearance(appearanceID int32) *NPC {
|
|
npcs := m.GetNPCsByAppearance(appearanceID)
|
|
if len(npcs) == 0 {
|
|
return nil
|
|
}
|
|
|
|
return npcs[rand.Intn(len(npcs))]
|
|
}
|
|
|
|
// ProcessCombat handles combat processing for all NPCs
|
|
func (m *Manager) ProcessCombat() {
|
|
m.mutex.RLock()
|
|
npcs := make([]*NPC, 0, len(m.npcs))
|
|
for _, npc := range m.npcs {
|
|
if npc.Entity != nil && npc.Entity.GetInCombat() {
|
|
npcs = append(npcs, npc)
|
|
}
|
|
}
|
|
m.mutex.RUnlock()
|
|
|
|
// Process combat for each NPC in combat
|
|
for _, npc := range npcs {
|
|
npc.ProcessCombat()
|
|
}
|
|
|
|
// Update combat statistics
|
|
m.mutex.Lock()
|
|
m.npcsInCombat = int64(len(npcs))
|
|
m.mutex.Unlock()
|
|
}
|
|
|
|
// ProcessAI handles AI processing for all NPCs
|
|
func (m *Manager) ProcessAI() {
|
|
if !m.enableAI {
|
|
return
|
|
}
|
|
|
|
m.mutex.RLock()
|
|
npcs := make([]*NPC, 0, len(m.npcs))
|
|
for _, npc := range m.npcs {
|
|
npcs = append(npcs, npc)
|
|
}
|
|
m.mutex.RUnlock()
|
|
|
|
// Process AI for each NPC
|
|
for _, npc := range npcs {
|
|
if brain := npc.GetBrain(); brain != nil && brain.IsActive() {
|
|
if err := brain.Think(); err != nil && m.logger != nil {
|
|
m.logger.LogError("AI brain error for NPC %d: %v", npc.GetNPCID(), err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ProcessMovement handles movement processing for all NPCs
|
|
func (m *Manager) ProcessMovement() {
|
|
m.mutex.RLock()
|
|
npcs := make([]*NPC, 0, len(m.npcs))
|
|
for _, npc := range m.npcs {
|
|
npcs = append(npcs, npc)
|
|
}
|
|
m.mutex.RUnlock()
|
|
|
|
// Process movement for each NPC
|
|
for _, npc := range npcs {
|
|
// Check pause timer
|
|
if npc.IsPauseMovementTimerActive() {
|
|
continue
|
|
}
|
|
|
|
// Handle runback if needed
|
|
if npc.callRunback && npc.GetRunbackLocation() != nil {
|
|
npc.callRunback = false
|
|
npc.Runback(0, true)
|
|
m.mutex.Lock()
|
|
m.runbackCount++
|
|
m.mutex.Unlock()
|
|
}
|
|
}
|
|
}
|
|
|
|
// GetStatistics returns NPC system statistics
|
|
func (m *Manager) GetStatistics() *NPCStatistics {
|
|
m.mutex.RLock()
|
|
defer m.mutex.RUnlock()
|
|
|
|
// Create AI strategy counts by name
|
|
aiCounts := make(map[string]int)
|
|
for strategy, count := range m.aiStrategyCounts {
|
|
switch strategy {
|
|
case AIStrategyBalanced:
|
|
aiCounts["balanced"] = int(count)
|
|
case AIStrategyOffensive:
|
|
aiCounts["offensive"] = int(count)
|
|
case AIStrategyDefensive:
|
|
aiCounts["defensive"] = int(count)
|
|
default:
|
|
aiCounts[fmt.Sprintf("unknown_%d", strategy)] = int(count)
|
|
}
|
|
}
|
|
|
|
// Calculate average aggro radius
|
|
var totalAggro float32
|
|
npcCount := 0
|
|
for _, npc := range m.npcs {
|
|
totalAggro += npc.GetAggroRadius()
|
|
npcCount++
|
|
}
|
|
var avgAggro float32
|
|
if npcCount > 0 {
|
|
avgAggro = totalAggro / float32(npcCount)
|
|
}
|
|
|
|
// Count NPCs with spells and skills
|
|
npcsWithSpells := 0
|
|
npcsWithSkills := 0
|
|
for _, npc := range m.npcs {
|
|
if npc.HasSpells() {
|
|
npcsWithSpells++
|
|
}
|
|
if len(npc.skills) > 0 {
|
|
npcsWithSkills++
|
|
}
|
|
}
|
|
|
|
return &NPCStatistics{
|
|
TotalNPCs: int(m.totalNPCs),
|
|
NPCsInCombat: int(m.npcsInCombat),
|
|
NPCsWithSpells: npcsWithSpells,
|
|
NPCsWithSkills: npcsWithSkills,
|
|
AIStrategyCounts: aiCounts,
|
|
SpellCastCount: m.spellCastCount,
|
|
SkillUsageCount: m.skillUsageCount,
|
|
RunbackCount: m.runbackCount,
|
|
AverageAggroRadius: avgAggro,
|
|
}
|
|
}
|
|
|
|
// ValidateAllNPCs validates all NPCs in the system
|
|
func (m *Manager) ValidateAllNPCs() []string {
|
|
m.mutex.RLock()
|
|
npcs := make([]*NPC, 0, len(m.npcs))
|
|
for _, npc := range m.npcs {
|
|
npcs = append(npcs, npc)
|
|
}
|
|
m.mutex.RUnlock()
|
|
|
|
var issues []string
|
|
for _, npc := range npcs {
|
|
if !npc.IsValid() {
|
|
issues = append(issues, fmt.Sprintf("NPC %d is invalid: %s", npc.GetNPCID(), npc.String()))
|
|
}
|
|
}
|
|
|
|
return issues
|
|
}
|
|
|
|
// ProcessCommand handles NPC-related commands
|
|
func (m *Manager) ProcessCommand(command string, args []string) (string, error) {
|
|
switch command {
|
|
case "stats":
|
|
return m.handleStatsCommand(args)
|
|
case "validate":
|
|
return m.handleValidateCommand(args)
|
|
case "list":
|
|
return m.handleListCommand(args)
|
|
case "info":
|
|
return m.handleInfoCommand(args)
|
|
case "create":
|
|
return m.handleCreateCommand(args)
|
|
case "remove":
|
|
return m.handleRemoveCommand(args)
|
|
case "search":
|
|
return m.handleSearchCommand(args)
|
|
case "combat":
|
|
return m.handleCombatCommand(args)
|
|
default:
|
|
return "", fmt.Errorf("unknown NPC command: %s", command)
|
|
}
|
|
}
|
|
|
|
// Command handlers
|
|
func (m *Manager) handleStatsCommand(args []string) (string, error) {
|
|
stats := m.GetStatistics()
|
|
|
|
result := "NPC System Statistics:\n"
|
|
result += fmt.Sprintf("Total NPCs: %d\n", stats.TotalNPCs)
|
|
result += fmt.Sprintf("NPCs in Combat: %d\n", stats.NPCsInCombat)
|
|
result += fmt.Sprintf("NPCs with Spells: %d\n", stats.NPCsWithSpells)
|
|
result += fmt.Sprintf("NPCs with Skills: %d\n", stats.NPCsWithSkills)
|
|
result += fmt.Sprintf("Average Aggro Radius: %.2f\n", stats.AverageAggroRadius)
|
|
result += fmt.Sprintf("Spell Casts: %d\n", stats.SpellCastCount)
|
|
result += fmt.Sprintf("Skill Uses: %d\n", stats.SkillUsageCount)
|
|
result += fmt.Sprintf("Runbacks: %d\n", stats.RunbackCount)
|
|
|
|
if len(stats.AIStrategyCounts) > 0 {
|
|
result += "\nAI Strategy Distribution:\n"
|
|
for strategy, count := range stats.AIStrategyCounts {
|
|
result += fmt.Sprintf(" %s: %d\n", strategy, count)
|
|
}
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (m *Manager) handleValidateCommand(args []string) (string, error) {
|
|
issues := m.ValidateAllNPCs()
|
|
|
|
if len(issues) == 0 {
|
|
return "All NPCs are valid.", nil
|
|
}
|
|
|
|
result := fmt.Sprintf("Found %d issues with NPCs:\n", len(issues))
|
|
for i, issue := range issues {
|
|
if i >= 10 { // Limit output
|
|
result += "... (and more)\n"
|
|
break
|
|
}
|
|
result += fmt.Sprintf("%d. %s\n", i+1, issue)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (m *Manager) handleListCommand(args []string) (string, error) {
|
|
m.mutex.RLock()
|
|
npcs := make([]*NPC, 0, len(m.npcs))
|
|
for _, npc := range m.npcs {
|
|
npcs = append(npcs, npc)
|
|
}
|
|
m.mutex.RUnlock()
|
|
|
|
if len(npcs) == 0 {
|
|
return "No NPCs loaded.", nil
|
|
}
|
|
|
|
result := fmt.Sprintf("NPCs (%d):\n", len(npcs))
|
|
count := 0
|
|
for _, npc := range npcs {
|
|
if count >= 20 { // Limit output
|
|
result += "... (and more)\n"
|
|
break
|
|
}
|
|
result += fmt.Sprintf(" %d: %s\n", npc.GetNPCID(), npc.String())
|
|
count++
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (m *Manager) handleInfoCommand(args []string) (string, error) {
|
|
if len(args) == 0 {
|
|
return "", fmt.Errorf("NPC ID required")
|
|
}
|
|
|
|
var npcID int32
|
|
if _, err := fmt.Sscanf(args[0], "%d", &npcID); err != nil {
|
|
return "", fmt.Errorf("invalid NPC ID: %s", args[0])
|
|
}
|
|
|
|
npc := m.GetNPC(npcID)
|
|
if npc == nil {
|
|
return fmt.Sprintf("NPC %d not found.", npcID), nil
|
|
}
|
|
|
|
result := fmt.Sprintf("NPC Information:\n")
|
|
result += fmt.Sprintf("ID: %d\n", npc.GetNPCID())
|
|
result += fmt.Sprintf("Appearance ID: %d\n", npc.GetAppearanceID())
|
|
result += fmt.Sprintf("AI Strategy: %d\n", npc.GetAIStrategy())
|
|
result += fmt.Sprintf("Cast Percentage: %d%%\n", npc.GetCastPercentage())
|
|
result += fmt.Sprintf("Aggro Radius: %.2f\n", npc.GetAggroRadius())
|
|
result += fmt.Sprintf("Has Spells: %v\n", npc.HasSpells())
|
|
result += fmt.Sprintf("Running Back: %v\n", npc.IsRunningBack())
|
|
|
|
if npc.Entity != nil {
|
|
result += fmt.Sprintf("Name: %s\n", npc.Entity.GetName())
|
|
result += fmt.Sprintf("Level: %d\n", npc.Entity.GetLevel())
|
|
result += fmt.Sprintf("Zone: %d\n", npc.Entity.GetZoneID())
|
|
result += fmt.Sprintf("In Combat: %v\n", npc.Entity.GetInCombat())
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (m *Manager) handleCreateCommand(args []string) (string, error) {
|
|
if len(args) < 2 {
|
|
return "", fmt.Errorf("usage: create <template_id> <new_id>")
|
|
}
|
|
|
|
var templateID, newID int32
|
|
if _, err := fmt.Sscanf(args[0], "%d", &templateID); err != nil {
|
|
return "", fmt.Errorf("invalid template ID: %s", args[0])
|
|
}
|
|
if _, err := fmt.Sscanf(args[1], "%d", &newID); err != nil {
|
|
return "", fmt.Errorf("invalid new ID: %s", args[1])
|
|
}
|
|
|
|
npc, err := m.CreateNPCFromTemplate(templateID, newID)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to create NPC: %w", err)
|
|
}
|
|
|
|
return fmt.Sprintf("Successfully created NPC %d from template %d", newID, templateID), nil
|
|
}
|
|
|
|
func (m *Manager) handleRemoveCommand(args []string) (string, error) {
|
|
if len(args) == 0 {
|
|
return "", fmt.Errorf("NPC ID required")
|
|
}
|
|
|
|
var npcID int32
|
|
if _, err := fmt.Sscanf(args[0], "%d", &npcID); err != nil {
|
|
return "", fmt.Errorf("invalid NPC ID: %s", args[0])
|
|
}
|
|
|
|
if err := m.RemoveNPC(npcID); err != nil {
|
|
return "", fmt.Errorf("failed to remove NPC: %w", err)
|
|
}
|
|
|
|
return fmt.Sprintf("Successfully removed NPC %d", npcID), nil
|
|
}
|
|
|
|
func (m *Manager) handleSearchCommand(args []string) (string, error) {
|
|
if len(args) == 0 {
|
|
return "", fmt.Errorf("search term required")
|
|
}
|
|
|
|
searchTerm := strings.ToLower(args[0])
|
|
|
|
m.mutex.RLock()
|
|
var results []*NPC
|
|
for _, npc := range m.npcs {
|
|
if npc.Entity != nil {
|
|
name := strings.ToLower(npc.Entity.GetName())
|
|
if strings.Contains(name, searchTerm) {
|
|
results = append(results, npc)
|
|
}
|
|
}
|
|
}
|
|
m.mutex.RUnlock()
|
|
|
|
if len(results) == 0 {
|
|
return fmt.Sprintf("No NPCs found matching '%s'.", args[0]), nil
|
|
}
|
|
|
|
result := fmt.Sprintf("Found %d NPCs matching '%s':\n", len(results), args[0])
|
|
for i, npc := range results {
|
|
if i >= 20 { // Limit output
|
|
result += "... (and more)\n"
|
|
break
|
|
}
|
|
result += fmt.Sprintf(" %d: %s\n", npc.GetNPCID(), npc.String())
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (m *Manager) handleCombatCommand(args []string) (string, error) {
|
|
result := "Combat Processing Status:\n"
|
|
result += fmt.Sprintf("NPCs in Combat: %d\n", m.npcsInCombat)
|
|
result += fmt.Sprintf("Total Spell Casts: %d\n", m.spellCastCount)
|
|
result += fmt.Sprintf("Total Runbacks: %d\n", m.runbackCount)
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// Helper methods
|
|
func (m *Manager) removeFromZoneIndex(npc *NPC) {
|
|
if npc.Entity == nil {
|
|
return
|
|
}
|
|
|
|
zoneID := npc.Entity.GetZoneID()
|
|
npcs := m.npcsByZone[zoneID]
|
|
|
|
for i, n := range npcs {
|
|
if n == npc {
|
|
// Remove from slice
|
|
m.npcsByZone[zoneID] = append(npcs[:i], npcs[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
|
|
// Clean up empty slices
|
|
if len(m.npcsByZone[zoneID]) == 0 {
|
|
delete(m.npcsByZone, zoneID)
|
|
}
|
|
}
|
|
|
|
func (m *Manager) removeFromAppearanceIndex(npc *NPC) {
|
|
appearanceID := npc.GetAppearanceID()
|
|
npcs := m.npcsByAppearance[appearanceID]
|
|
|
|
for i, n := range npcs {
|
|
if n == npc {
|
|
// Remove from slice
|
|
m.npcsByAppearance[appearanceID] = append(npcs[:i], npcs[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
|
|
// Clean up empty slices
|
|
if len(m.npcsByAppearance[appearanceID]) == 0 {
|
|
delete(m.npcsByAppearance, appearanceID)
|
|
}
|
|
}
|
|
|
|
// SetManagers sets the external system managers
|
|
func (m *Manager) SetManagers(spellMgr SpellManager, skillMgr SkillManager, appearanceMgr AppearanceManager) {
|
|
m.mutex.Lock()
|
|
defer m.mutex.Unlock()
|
|
|
|
m.spellManager = spellMgr
|
|
m.skillManager = skillMgr
|
|
m.appearanceManager = appearanceMgr
|
|
}
|
|
|
|
// Configuration methods
|
|
func (m *Manager) SetMaxNPCs(max int32) {
|
|
m.mutex.Lock()
|
|
defer m.mutex.Unlock()
|
|
m.maxNPCs = max
|
|
}
|
|
|
|
func (m *Manager) SetDefaultAggroRadius(radius float32) {
|
|
m.mutex.Lock()
|
|
defer m.mutex.Unlock()
|
|
m.defaultAggroRadius = radius
|
|
}
|
|
|
|
func (m *Manager) SetAIEnabled(enabled bool) {
|
|
m.mutex.Lock()
|
|
defer m.mutex.Unlock()
|
|
m.enableAI = enabled
|
|
}
|
|
|
|
// GetNPCCount returns the total number of NPCs
|
|
func (m *Manager) GetNPCCount() int32 {
|
|
m.mutex.RLock()
|
|
defer m.mutex.RUnlock()
|
|
return int32(len(m.npcs))
|
|
}
|
|
|
|
// Shutdown gracefully shuts down the manager
|
|
func (m *Manager) Shutdown() {
|
|
if m.logger != nil {
|
|
m.logger.LogInfo("Shutting down NPC manager...")
|
|
}
|
|
|
|
// Stop all AI brains
|
|
m.mutex.Lock()
|
|
for _, npc := range m.npcs {
|
|
if brain := npc.GetBrain(); brain != nil {
|
|
brain.SetActive(false)
|
|
}
|
|
}
|
|
|
|
// Clear all data
|
|
m.npcs = make(map[int32]*NPC)
|
|
m.npcsByZone = make(map[int32][]*NPC)
|
|
m.npcsByAppearance = make(map[int32][]*NPC)
|
|
m.mutex.Unlock()
|
|
} |