eq2go/internal/npc/types.go

440 lines
12 KiB
Go

package npc
import (
"sync"
"time"
"eq2emu/internal/common"
"eq2emu/internal/entity"
"eq2emu/internal/spawn"
)
// 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"`
}