package npc import ( "sync" "time" "eq2emu/internal/entity" ) // NPCSpell represents a spell configuration for NPCs type NPCSpell struct { ListID int32 // Spell list identifier SpellID int32 // Spell ID from master spell list Tier int8 // Spell tier CastOnSpawn bool // Cast when NPC spawns CastOnInitialAggro bool // Cast when first entering combat RequiredHPRatio int8 // HP ratio requirement for casting (-100 to 100) mutex sync.RWMutex } // NewNPCSpell creates a new NPCSpell func NewNPCSpell() *NPCSpell { return &NPCSpell{ ListID: 0, SpellID: 0, Tier: 1, CastOnSpawn: false, CastOnInitialAggro: false, RequiredHPRatio: 0, } } // Copy creates a deep copy of the NPCSpell func (ns *NPCSpell) Copy() *NPCSpell { ns.mutex.RLock() defer ns.mutex.RUnlock() return &NPCSpell{ ListID: ns.ListID, SpellID: ns.SpellID, Tier: ns.Tier, CastOnSpawn: ns.CastOnSpawn, CastOnInitialAggro: ns.CastOnInitialAggro, RequiredHPRatio: ns.RequiredHPRatio, } } // Getters func (ns *NPCSpell) GetListID() int32 { ns.mutex.RLock() defer ns.mutex.RUnlock() return ns.ListID } func (ns *NPCSpell) GetSpellID() int32 { ns.mutex.RLock() defer ns.mutex.RUnlock() return ns.SpellID } func (ns *NPCSpell) GetTier() int8 { ns.mutex.RLock() defer ns.mutex.RUnlock() return ns.Tier } func (ns *NPCSpell) GetCastOnSpawn() bool { ns.mutex.RLock() defer ns.mutex.RUnlock() return ns.CastOnSpawn } func (ns *NPCSpell) GetCastOnInitialAggro() bool { ns.mutex.RLock() defer ns.mutex.RUnlock() return ns.CastOnInitialAggro } func (ns *NPCSpell) GetRequiredHPRatio() int8 { ns.mutex.RLock() defer ns.mutex.RUnlock() return ns.RequiredHPRatio } // Setters func (ns *NPCSpell) SetListID(id int32) { ns.mutex.Lock() defer ns.mutex.Unlock() ns.ListID = id } func (ns *NPCSpell) SetSpellID(id int32) { ns.mutex.Lock() defer ns.mutex.Unlock() ns.SpellID = id } func (ns *NPCSpell) SetTier(tier int8) { ns.mutex.Lock() defer ns.mutex.Unlock() ns.Tier = tier } func (ns *NPCSpell) SetCastOnSpawn(cast bool) { ns.mutex.Lock() defer ns.mutex.Unlock() ns.CastOnSpawn = cast } func (ns *NPCSpell) SetCastOnInitialAggro(cast bool) { ns.mutex.Lock() defer ns.mutex.Unlock() ns.CastOnInitialAggro = cast } func (ns *NPCSpell) SetRequiredHPRatio(ratio int8) { ns.mutex.Lock() defer ns.mutex.Unlock() ns.RequiredHPRatio = ratio } // SkillBonus represents a skill bonus from spells type SkillBonus struct { SpellID int32 // Spell providing the bonus Skills map[int32]*SkillBonusValue // Map of skill ID to bonus value mutex sync.RWMutex } // SkillBonusValue represents the actual bonus value for a skill type SkillBonusValue struct { SkillID int32 // Skill receiving the bonus Value float32 // Bonus amount } // NewSkillBonus creates a new SkillBonus func NewSkillBonus(spellID int32) *SkillBonus { return &SkillBonus{ SpellID: spellID, Skills: make(map[int32]*SkillBonusValue), } } // AddSkill adds a skill bonus func (sb *SkillBonus) AddSkill(skillID int32, value float32) { sb.mutex.Lock() defer sb.mutex.Unlock() sb.Skills[skillID] = &SkillBonusValue{ SkillID: skillID, Value: value, } } // RemoveSkill removes a skill bonus func (sb *SkillBonus) RemoveSkill(skillID int32) bool { sb.mutex.Lock() defer sb.mutex.Unlock() if _, exists := sb.Skills[skillID]; exists { delete(sb.Skills, skillID) return true } return false } // GetSkills returns a copy of all skill bonuses func (sb *SkillBonus) GetSkills() map[int32]*SkillBonusValue { sb.mutex.RLock() defer sb.mutex.RUnlock() result := make(map[int32]*SkillBonusValue) for id, bonus := range sb.Skills { result[id] = &SkillBonusValue{ SkillID: bonus.SkillID, Value: bonus.Value, } } return result } // MovementLocation represents a movement destination for runback type MovementLocation struct { X float32 // X coordinate Y float32 // Y coordinate Z float32 // Z coordinate GridID int32 // Grid location ID Stage int32 // Movement stage ResetHPOnRunback bool // Whether to reset HP when reaching location UseNavPath bool // Whether to use navigation pathfinding Mapped bool // Whether location is mapped } // NewMovementLocation creates a new MovementLocation func NewMovementLocation(x, y, z float32, gridID int32) *MovementLocation { return &MovementLocation{ X: x, Y: y, Z: z, GridID: gridID, Stage: 0, ResetHPOnRunback: false, UseNavPath: false, Mapped: false, } } // Copy creates a deep copy of the MovementLocation func (ml *MovementLocation) Copy() *MovementLocation { return &MovementLocation{ X: ml.X, Y: ml.Y, Z: ml.Z, GridID: ml.GridID, Stage: ml.Stage, ResetHPOnRunback: ml.ResetHPOnRunback, UseNavPath: ml.UseNavPath, Mapped: ml.Mapped, } } // NPC represents a non-player character extending Entity type NPC struct { *entity.Entity // Embedded entity for combat capabilities // Core NPC properties appearanceID int32 // Appearance ID for client display npcID int32 // NPC database ID aiStrategy int8 // AI strategy (balanced/offensive/defensive) attackType int8 // Attack type preference castPercentage int8 // Percentage chance to cast spells maxPetLevel int8 // Maximum pet level // Combat and movement aggroRadius float32 // Aggro detection radius baseAggroRadius float32 // Base aggro radius (for resets) runback *MovementLocation // Runback location when leaving combat runningBack bool // Currently running back to spawn point runbackHeadingDir1 int16 // Original heading direction 1 runbackHeadingDir2 int16 // Original heading direction 2 pauseTimer *Timer // Movement pause timer // Spell and skill management primarySpellList int32 // Primary spell list ID secondarySpellList int32 // Secondary spell list ID primarySkillList int32 // Primary skill list ID secondarySkillList int32 // Secondary skill list ID equipmentListID int32 // Equipment list ID skills map[string]*Skill // NPC skills by name spells []*NPCSpell // Available spells castOnSpells map[int8][]*NPCSpell // Spells to cast by trigger type skillBonuses map[int32]*SkillBonus // Skill bonuses from spells hasSpells bool // Whether NPC has any spells castOnAggroCompleted bool // Whether cast-on-aggro spells are done // Brain/AI system (placeholder for now) brain Brain // AI brain for decision making // Shard system (for cross-server functionality) shardID int32 // Shard identifier shardCharID int32 // Character ID on shard shardCreatedTimestamp int64 // Timestamp when created on shard // Thread safety mutex sync.RWMutex // Main NPC mutex brainMutex sync.RWMutex // Brain-specific mutex // Atomic flags for thread-safe state management callRunback bool // Flag to trigger runback } // Timer represents a simple timer for NPC operations type Timer struct { duration time.Duration startTime time.Time enabled bool mutex sync.RWMutex } // NewTimer creates a new timer func NewTimer() *Timer { return &Timer{ enabled: false, } } // Start starts the timer with the given duration func (t *Timer) Start(durationMS int32, reset bool) { t.mutex.Lock() defer t.mutex.Unlock() if reset || !t.enabled { t.duration = time.Duration(durationMS) * time.Millisecond t.startTime = time.Now() t.enabled = true } } // Check checks if the timer has expired func (t *Timer) Check() bool { t.mutex.RLock() defer t.mutex.RUnlock() if !t.enabled { return false } return time.Since(t.startTime) >= t.duration } // Enabled returns whether the timer is currently enabled func (t *Timer) Enabled() bool { t.mutex.RLock() defer t.mutex.RUnlock() return t.enabled } // Disable disables the timer func (t *Timer) Disable() { t.mutex.Lock() defer t.mutex.Unlock() t.enabled = false } // Skill represents an NPC skill (simplified from C++ version) type Skill struct { SkillID int32 // Skill identifier Name string // Skill name CurrentVal int16 // Current skill value MaxVal int16 // Maximum skill value mutex sync.RWMutex } // NewSkill creates a new skill func NewSkill(id int32, name string, current, max int16) *Skill { return &Skill{ SkillID: id, Name: name, CurrentVal: current, MaxVal: max, } } // GetCurrentVal returns the current skill value func (s *Skill) GetCurrentVal() int16 { s.mutex.RLock() defer s.mutex.RUnlock() return s.CurrentVal } // SetCurrentVal sets the current skill value func (s *Skill) SetCurrentVal(val int16) { s.mutex.Lock() defer s.mutex.Unlock() s.CurrentVal = val } // IncreaseSkill increases the skill value (with random chance) func (s *Skill) IncreaseSkill() bool { s.mutex.Lock() defer s.mutex.Unlock() if s.CurrentVal < s.MaxVal { s.CurrentVal++ return true } return false } // Brain interface represents the AI brain system (placeholder) type Brain interface { Think() error GetBody() *NPC SetBody(*NPC) IsActive() bool SetActive(bool) } // DefaultBrain provides a simple brain implementation type DefaultBrain struct { npc *NPC active bool mutex sync.RWMutex } // NewDefaultBrain creates a new default brain func NewDefaultBrain(npc *NPC) *DefaultBrain { return &DefaultBrain{ npc: npc, active: true, } } // Think processes AI logic (placeholder implementation) func (b *DefaultBrain) Think() error { // TODO: Implement AI thinking logic return nil } // GetBody returns the NPC this brain controls func (b *DefaultBrain) GetBody() *NPC { b.mutex.RLock() defer b.mutex.RUnlock() return b.npc } // SetBody sets the NPC this brain controls func (b *DefaultBrain) SetBody(npc *NPC) { b.mutex.Lock() defer b.mutex.Unlock() b.npc = npc } // IsActive returns whether the brain is active func (b *DefaultBrain) IsActive() bool { b.mutex.RLock() defer b.mutex.RUnlock() return b.active } // SetActive sets the brain's active state func (b *DefaultBrain) SetActive(active bool) { b.mutex.Lock() defer b.mutex.Unlock() b.active = active } // NPCStatistics contains NPC system statistics type NPCStatistics struct { TotalNPCs int `json:"total_npcs"` NPCsInCombat int `json:"npcs_in_combat"` NPCsWithSpells int `json:"npcs_with_spells"` NPCsWithSkills int `json:"npcs_with_skills"` AIStrategyCounts map[string]int `json:"ai_strategy_counts"` SpellCastCount int64 `json:"spell_cast_count"` SkillUsageCount int64 `json:"skill_usage_count"` RunbackCount int64 `json:"runback_count"` AverageAggroRadius float32 `json:"average_aggro_radius"` }