570 lines
15 KiB
Go
570 lines
15 KiB
Go
package npc
|
|
|
|
// Database interface for NPC persistence
|
|
type Database interface {
|
|
LoadAllNPCs() ([]*NPC, error)
|
|
SaveNPC(npc *NPC) error
|
|
DeleteNPC(npcID int32) error
|
|
LoadNPCSpells(npcID int32) ([]*NPCSpell, error)
|
|
SaveNPCSpells(npcID int32, spells []*NPCSpell) error
|
|
LoadNPCSkills(npcID int32) (map[string]*Skill, error)
|
|
SaveNPCSkills(npcID int32, skills map[string]*Skill) error
|
|
}
|
|
|
|
// Logger interface for NPC logging
|
|
type Logger interface {
|
|
LogInfo(message string, args ...interface{})
|
|
LogError(message string, args ...interface{})
|
|
LogDebug(message string, args ...interface{})
|
|
LogWarning(message string, args ...interface{})
|
|
}
|
|
|
|
// Client interface for NPC-related client operations
|
|
type Client interface {
|
|
GetPlayer() Player
|
|
GetVersion() int16
|
|
SendNPCUpdate(npcData []byte) error
|
|
SendCombatUpdate(combatData []byte) error
|
|
SendSpellCast(spellData []byte) error
|
|
}
|
|
|
|
// Player interface for NPC-related player operations
|
|
type Player interface {
|
|
GetCharacterID() int32
|
|
GetName() string
|
|
GetLevel() int8
|
|
GetZoneID() int32
|
|
GetX() float32
|
|
GetY() float32
|
|
GetZ() float32
|
|
IsInCombat() bool
|
|
GetTarget() *NPC
|
|
SendMessage(message string)
|
|
}
|
|
|
|
// Zone interface for NPC zone operations
|
|
type Zone interface {
|
|
GetZoneID() int32
|
|
GetNPCs() []*NPC
|
|
AddNPC(npc *NPC) error
|
|
RemoveNPC(npcID int32) error
|
|
GetPlayersInRange(x, y, z, radius float32) []Player
|
|
ProcessEntityCommand(command string, client Client, target *NPC) error
|
|
CallSpawnScript(npc *NPC, scriptType string, args ...interface{}) error
|
|
}
|
|
|
|
// SpellManager interface for spell system integration
|
|
type SpellManager interface {
|
|
GetSpell(spellID int32, tier int8) Spell
|
|
CastSpell(caster *NPC, target interface{}, spell Spell) error
|
|
GetSpellEffect(entity interface{}, spellID int32) SpellEffect
|
|
ProcessSpell(spell Spell, caster *NPC, target interface{}) error
|
|
}
|
|
|
|
// Spell interface for spell data
|
|
type Spell interface {
|
|
GetSpellID() int32
|
|
GetName() string
|
|
GetTier() int8
|
|
GetRange() float32
|
|
GetMinRange() float32
|
|
GetPowerRequired() int32
|
|
IsFriendlySpell() bool
|
|
IsToggleSpell() bool
|
|
GetCastTime() int32
|
|
GetRecastTime() int32
|
|
}
|
|
|
|
// SpellEffect interface for active spell effects
|
|
type SpellEffect interface {
|
|
GetSpellID() int32
|
|
GetTier() int8
|
|
GetDuration() int32
|
|
GetRemainingTime() int32
|
|
IsExpired() bool
|
|
}
|
|
|
|
// SkillManager interface for skill system integration
|
|
type SkillManager interface {
|
|
GetSkill(skillID int32) MasterSkill
|
|
GetSkillByName(name string) MasterSkill
|
|
ApplySkillBonus(entity interface{}, skillID int32, bonus float32) error
|
|
RemoveSkillBonus(entity interface{}, skillID int32, bonus float32) error
|
|
}
|
|
|
|
// MasterSkill interface for skill definitions
|
|
type MasterSkill interface {
|
|
GetSkillID() int32
|
|
GetName() string
|
|
GetDescription() string
|
|
GetMaxValue() int16
|
|
}
|
|
|
|
// AppearanceManager interface for appearance system integration
|
|
type AppearanceManager interface {
|
|
GetAppearance(appearanceID int32) Appearance
|
|
GetAppearancesByName(name string) []Appearance
|
|
RandomizeAppearance(npc *NPC, flags int32) error
|
|
}
|
|
|
|
// Appearance interface for appearance data
|
|
type Appearance interface {
|
|
GetAppearanceID() int32
|
|
GetName() string
|
|
GetModelType() int16
|
|
GetRace() int16
|
|
GetGender() int8
|
|
}
|
|
|
|
// MovementManager interface for movement system integration
|
|
type MovementManager interface {
|
|
StartMovement(npc *NPC, x, y, z float32) error
|
|
StopMovement(npc *NPC) error
|
|
SetSpeed(npc *NPC, speed float32) error
|
|
NavigateToLocation(npc *NPC, x, y, z float32) error
|
|
IsMoving(npc *NPC) bool
|
|
}
|
|
|
|
// CombatManager interface for combat system integration
|
|
type CombatManager interface {
|
|
StartCombat(npc *NPC, target interface{}) error
|
|
EndCombat(npc *NPC) error
|
|
ProcessCombatRound(npc *NPC) error
|
|
CalculateDamage(attacker *NPC, target interface{}) int32
|
|
ApplyDamage(target interface{}, damage int32) error
|
|
}
|
|
|
|
// NPCAware interface for entities that can interact with NPCs
|
|
type NPCAware interface {
|
|
GetNPC() *NPC
|
|
IsNPC() bool
|
|
HandleNPCInteraction(npc *NPC, interactionType string) error
|
|
ReceiveNPCCommand(npc *NPC, command string) error
|
|
}
|
|
|
|
// EntityAdapter provides NPC functionality for entities
|
|
type EntityAdapter struct {
|
|
npc *NPC
|
|
logger Logger
|
|
}
|
|
|
|
// NewEntityAdapter creates a new entity adapter
|
|
func NewEntityAdapter(npc *NPC, logger Logger) *EntityAdapter {
|
|
return &EntityAdapter{
|
|
npc: npc,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// GetNPC returns the associated NPC
|
|
func (ea *EntityAdapter) GetNPC() *NPC {
|
|
return ea.npc
|
|
}
|
|
|
|
// IsNPC always returns true for entity adapters
|
|
func (ea *EntityAdapter) IsNPC() bool {
|
|
return true
|
|
}
|
|
|
|
// HandleNPCInteraction processes interactions with other NPCs
|
|
func (ea *EntityAdapter) HandleNPCInteraction(otherNPC *NPC, interactionType string) error {
|
|
if ea.npc == nil || otherNPC == nil {
|
|
return fmt.Errorf("invalid NPC for interaction")
|
|
}
|
|
|
|
// Handle different interaction types
|
|
switch interactionType {
|
|
case "aggro":
|
|
return ea.handleAggroInteraction(otherNPC)
|
|
case "assist":
|
|
return ea.handleAssistInteraction(otherNPC)
|
|
case "trade":
|
|
return ea.handleTradeInteraction(otherNPC)
|
|
default:
|
|
if ea.logger != nil {
|
|
ea.logger.LogWarning("Unknown NPC interaction type: %s", interactionType)
|
|
}
|
|
return fmt.Errorf("unknown interaction type: %s", interactionType)
|
|
}
|
|
}
|
|
|
|
// ReceiveNPCCommand processes commands from other NPCs
|
|
func (ea *EntityAdapter) ReceiveNPCCommand(otherNPC *NPC, command string) error {
|
|
if ea.npc == nil || otherNPC == nil {
|
|
return fmt.Errorf("invalid NPC for command")
|
|
}
|
|
|
|
// Process the command
|
|
switch command {
|
|
case "follow":
|
|
return ea.handleFollowCommand(otherNPC)
|
|
case "attack":
|
|
return ea.handleAttackCommand(otherNPC)
|
|
case "retreat":
|
|
return ea.handleRetreatCommand(otherNPC)
|
|
default:
|
|
if ea.logger != nil {
|
|
ea.logger.LogWarning("Unknown NPC command: %s", command)
|
|
}
|
|
return fmt.Errorf("unknown command: %s", command)
|
|
}
|
|
}
|
|
|
|
// handleAggroInteraction processes aggro interactions
|
|
func (ea *EntityAdapter) handleAggroInteraction(otherNPC *NPC) error {
|
|
// TODO: Implement aggro logic between NPCs
|
|
if ea.logger != nil {
|
|
ea.logger.LogDebug("NPC %d received aggro from NPC %d",
|
|
ea.npc.GetNPCID(), otherNPC.GetNPCID())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// handleAssistInteraction processes assist interactions
|
|
func (ea *EntityAdapter) handleAssistInteraction(otherNPC *NPC) error {
|
|
// TODO: Implement assist logic between NPCs
|
|
if ea.logger != nil {
|
|
ea.logger.LogDebug("NPC %d received assist request from NPC %d",
|
|
ea.npc.GetNPCID(), otherNPC.GetNPCID())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// handleTradeInteraction processes trade interactions
|
|
func (ea *EntityAdapter) handleTradeInteraction(otherNPC *NPC) error {
|
|
// TODO: Implement trade logic between NPCs
|
|
if ea.logger != nil {
|
|
ea.logger.LogDebug("NPC %d received trade request from NPC %d",
|
|
ea.npc.GetNPCID(), otherNPC.GetNPCID())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// handleFollowCommand processes follow commands
|
|
func (ea *EntityAdapter) handleFollowCommand(otherNPC *NPC) error {
|
|
// TODO: Implement follow logic
|
|
if ea.logger != nil {
|
|
ea.logger.LogDebug("NPC %d received follow command from NPC %d",
|
|
ea.npc.GetNPCID(), otherNPC.GetNPCID())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// handleAttackCommand processes attack commands
|
|
func (ea *EntityAdapter) handleAttackCommand(otherNPC *NPC) error {
|
|
// TODO: Implement attack logic
|
|
if ea.logger != nil {
|
|
ea.logger.LogDebug("NPC %d received attack command from NPC %d",
|
|
ea.npc.GetNPCID(), otherNPC.GetNPCID())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// handleRetreatCommand processes retreat commands
|
|
func (ea *EntityAdapter) handleRetreatCommand(otherNPC *NPC) error {
|
|
// TODO: Implement retreat logic
|
|
if ea.logger != nil {
|
|
ea.logger.LogDebug("NPC %d received retreat command from NPC %d",
|
|
ea.npc.GetNPCID(), otherNPC.GetNPCID())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SpellCasterAdapter provides spell casting functionality for NPCs
|
|
type SpellCasterAdapter struct {
|
|
npc *NPC
|
|
spellManager SpellManager
|
|
logger Logger
|
|
}
|
|
|
|
// NewSpellCasterAdapter creates a new spell caster adapter
|
|
func NewSpellCasterAdapter(npc *NPC, spellManager SpellManager, logger Logger) *SpellCasterAdapter {
|
|
return &SpellCasterAdapter{
|
|
npc: npc,
|
|
spellManager: spellManager,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// GetNextSpell selects the next spell to cast based on AI strategy
|
|
func (sca *SpellCasterAdapter) GetNextSpell(target interface{}, distance float32) Spell {
|
|
if sca.npc == nil || sca.spellManager == nil {
|
|
return nil
|
|
}
|
|
|
|
// Check cast-on-aggro spells first
|
|
if !sca.npc.castOnAggroCompleted {
|
|
spell := sca.getNextCastOnAggroSpell(target)
|
|
if spell != nil {
|
|
return spell
|
|
}
|
|
sca.npc.castOnAggroCompleted = true
|
|
}
|
|
|
|
// Get spells based on AI strategy
|
|
strategy := sca.npc.GetAIStrategy()
|
|
return sca.getNextSpellByStrategy(target, distance, strategy)
|
|
}
|
|
|
|
// GetNextBuffSpell selects the next buff spell to cast
|
|
func (sca *SpellCasterAdapter) GetNextBuffSpell(target interface{}) Spell {
|
|
if sca.npc == nil || sca.spellManager == nil {
|
|
return nil
|
|
}
|
|
|
|
// Check cast-on-spawn spells first
|
|
castOnSpells := sca.npc.castOnSpells[CastOnSpawn]
|
|
for _, npcSpell := range castOnSpells {
|
|
spell := sca.spellManager.GetSpell(npcSpell.GetSpellID(), npcSpell.GetTier())
|
|
if spell != nil {
|
|
// Check if target already has this effect
|
|
if effect := sca.spellManager.GetSpellEffect(target, spell.GetSpellID()); effect != nil {
|
|
if effect.GetTier() < spell.GetTier() {
|
|
return spell // Upgrade existing effect
|
|
}
|
|
} else {
|
|
return spell // New effect
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check regular spells for buffs
|
|
for _, npcSpell := range sca.npc.spells {
|
|
spell := sca.spellManager.GetSpell(npcSpell.GetSpellID(), npcSpell.GetTier())
|
|
if spell != nil && spell.IsFriendlySpell() && spell.IsToggleSpell() {
|
|
// Check if target already has this effect
|
|
if effect := sca.spellManager.GetSpellEffect(target, spell.GetSpellID()); effect != nil {
|
|
if effect.GetTier() < spell.GetTier() {
|
|
return spell // Upgrade existing effect
|
|
}
|
|
} else {
|
|
return spell // New effect
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CastSpell attempts to cast a spell
|
|
func (sca *SpellCasterAdapter) CastSpell(target interface{}, spell Spell) error {
|
|
if sca.npc == nil || sca.spellManager == nil || spell == nil {
|
|
return fmt.Errorf("invalid parameters for spell casting")
|
|
}
|
|
|
|
// Check casting conditions
|
|
if err := sca.checkCastingConditions(spell); err != nil {
|
|
return fmt.Errorf("casting conditions not met: %w", err)
|
|
}
|
|
|
|
// Cast the spell
|
|
if err := sca.spellManager.CastSpell(sca.npc, target, spell); err != nil {
|
|
return fmt.Errorf("failed to cast spell: %w", err)
|
|
}
|
|
|
|
if sca.logger != nil {
|
|
sca.logger.LogDebug("NPC %d cast spell %s (%d)",
|
|
sca.npc.GetNPCID(), spell.GetName(), spell.GetSpellID())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// getNextCastOnAggroSpell selects cast-on-aggro spells
|
|
func (sca *SpellCasterAdapter) getNextCastOnAggroSpell(target interface{}) Spell {
|
|
castOnSpells := sca.npc.castOnSpells[CastOnAggro]
|
|
for _, npcSpell := range castOnSpells {
|
|
spell := sca.spellManager.GetSpell(npcSpell.GetSpellID(), npcSpell.GetTier())
|
|
if spell != nil {
|
|
// Check if target doesn't already have this effect
|
|
if effect := sca.spellManager.GetSpellEffect(target, spell.GetSpellID()); effect == nil {
|
|
return spell
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// getNextSpellByStrategy selects spells based on AI strategy
|
|
func (sca *SpellCasterAdapter) getNextSpellByStrategy(target interface{}, distance float32, strategy int8) Spell {
|
|
// TODO: Implement more sophisticated spell selection based on strategy
|
|
|
|
for _, npcSpell := range sca.npc.spells {
|
|
// Check HP ratio requirements
|
|
if npcSpell.GetRequiredHPRatio() != 0 {
|
|
// TODO: Implement HP ratio checking
|
|
}
|
|
|
|
spell := sca.spellManager.GetSpell(npcSpell.GetSpellID(), npcSpell.GetTier())
|
|
if spell == nil {
|
|
continue
|
|
}
|
|
|
|
// Check strategy compatibility
|
|
if strategy == AIStrategyOffensive && spell.IsFriendlySpell() {
|
|
continue
|
|
}
|
|
if strategy == AIStrategyDefensive && !spell.IsFriendlySpell() {
|
|
continue
|
|
}
|
|
|
|
// Check range and power requirements
|
|
if distance <= spell.GetRange() && distance >= spell.GetMinRange() {
|
|
// TODO: Check power requirements
|
|
return spell
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// checkCastingConditions validates spell casting conditions
|
|
func (sca *SpellCasterAdapter) checkCastingConditions(spell Spell) error {
|
|
if sca.npc.Entity == nil {
|
|
return fmt.Errorf("NPC entity is nil")
|
|
}
|
|
|
|
// TODO: Implement power checking, cooldown checking, etc.
|
|
|
|
return nil
|
|
}
|
|
|
|
// CombatAdapter provides combat functionality for NPCs
|
|
type CombatAdapter struct {
|
|
npc *NPC
|
|
combatManager CombatManager
|
|
logger Logger
|
|
}
|
|
|
|
// NewCombatAdapter creates a new combat adapter
|
|
func NewCombatAdapter(npc *NPC, combatManager CombatManager, logger Logger) *CombatAdapter {
|
|
return &CombatAdapter{
|
|
npc: npc,
|
|
combatManager: combatManager,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// EnterCombat handles entering combat state
|
|
func (ca *CombatAdapter) EnterCombat(target interface{}) error {
|
|
if ca.npc == nil {
|
|
return fmt.Errorf("NPC is nil")
|
|
}
|
|
|
|
// Start combat through combat manager
|
|
if ca.combatManager != nil {
|
|
if err := ca.combatManager.StartCombat(ca.npc, target); err != nil {
|
|
return fmt.Errorf("failed to start combat: %w", err)
|
|
}
|
|
}
|
|
|
|
// Update NPC state
|
|
ca.npc.InCombat(true)
|
|
|
|
if ca.logger != nil {
|
|
ca.logger.LogDebug("NPC %d entered combat", ca.npc.GetNPCID())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ExitCombat handles exiting combat state
|
|
func (ca *CombatAdapter) ExitCombat() error {
|
|
if ca.npc == nil {
|
|
return fmt.Errorf("NPC is nil")
|
|
}
|
|
|
|
// End combat through combat manager
|
|
if ca.combatManager != nil {
|
|
if err := ca.combatManager.EndCombat(ca.npc); err != nil {
|
|
return fmt.Errorf("failed to end combat: %w", err)
|
|
}
|
|
}
|
|
|
|
// Update NPC state
|
|
ca.npc.InCombat(false)
|
|
|
|
if ca.logger != nil {
|
|
ca.logger.LogDebug("NPC %d exited combat", ca.npc.GetNPCID())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ProcessCombat handles combat processing
|
|
func (ca *CombatAdapter) ProcessCombat() error {
|
|
if ca.npc == nil {
|
|
return fmt.Errorf("NPC is nil")
|
|
}
|
|
|
|
if ca.combatManager != nil {
|
|
return ca.combatManager.ProcessCombatRound(ca.npc)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// MovementAdapter provides movement functionality for NPCs
|
|
type MovementAdapter struct {
|
|
npc *NPC
|
|
movementManager MovementManager
|
|
logger Logger
|
|
}
|
|
|
|
// NewMovementAdapter creates a new movement adapter
|
|
func NewMovementAdapter(npc *NPC, movementManager MovementManager, logger Logger) *MovementAdapter {
|
|
return &MovementAdapter{
|
|
npc: npc,
|
|
movementManager: movementManager,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// MoveToLocation moves the NPC to a specific location
|
|
func (ma *MovementAdapter) MoveToLocation(x, y, z float32) error {
|
|
if ma.npc == nil {
|
|
return fmt.Errorf("NPC is nil")
|
|
}
|
|
|
|
if ma.movementManager != nil {
|
|
return ma.movementManager.NavigateToLocation(ma.npc, x, y, z)
|
|
}
|
|
|
|
return fmt.Errorf("movement manager not available")
|
|
}
|
|
|
|
// StopMovement stops the NPC's movement
|
|
func (ma *MovementAdapter) StopMovement() error {
|
|
if ma.npc == nil {
|
|
return fmt.Errorf("NPC is nil")
|
|
}
|
|
|
|
if ma.movementManager != nil {
|
|
return ma.movementManager.StopMovement(ma.npc)
|
|
}
|
|
|
|
return fmt.Errorf("movement manager not available")
|
|
}
|
|
|
|
// IsMoving checks if the NPC is currently moving
|
|
func (ma *MovementAdapter) IsMoving() bool {
|
|
if ma.npc == nil || ma.movementManager == nil {
|
|
return false
|
|
}
|
|
|
|
return ma.movementManager.IsMoving(ma.npc)
|
|
}
|
|
|
|
// RunbackToSpawn moves the NPC back to its spawn location
|
|
func (ma *MovementAdapter) RunbackToSpawn() error {
|
|
if ma.npc == nil {
|
|
return fmt.Errorf("NPC is nil")
|
|
}
|
|
|
|
runbackLocation := ma.npc.GetRunbackLocation()
|
|
if runbackLocation == nil {
|
|
return fmt.Errorf("no runback location set")
|
|
}
|
|
|
|
return ma.MoveToLocation(runbackLocation.X, runbackLocation.Y, runbackLocation.Z)
|
|
} |