422 lines
9.9 KiB
Go
422 lines
9.9 KiB
Go
package ai
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// AI tick constants
|
|
const (
|
|
DefaultThinkTick int32 = 250 // Default think tick in milliseconds (1/4 second)
|
|
FastThinkTick int32 = 100 // Fast think tick for active AI
|
|
SlowThinkTick int32 = 1000 // Slow think tick for idle AI
|
|
BlankBrainTick int32 = 50000 // Very slow tick for blank brain
|
|
MaxThinkTick int32 = 60000 // Maximum think tick (1 minute)
|
|
)
|
|
|
|
// Combat constants
|
|
const (
|
|
MaxChaseDistance float32 = 150.0 // Default max chase distance
|
|
MaxCombatRange float32 = 25.0 // Default max combat range
|
|
RunbackThreshold float32 = 1.0 // Distance threshold for runback
|
|
)
|
|
|
|
// Hate system constants
|
|
const (
|
|
MinHateValue int32 = 1 // Minimum hate value (0 or negative is invalid)
|
|
MaxHateValue int32 = 2147483647 // Maximum hate value (INT_MAX)
|
|
DefaultHateValue int32 = 100 // Default hate amount
|
|
MaxHateListSize int = 100 // Maximum entities in hate list
|
|
)
|
|
|
|
// Encounter system constants
|
|
const (
|
|
MaxEncounterSize int = 50 // Maximum entities in encounter list
|
|
)
|
|
|
|
// Spell recovery constants
|
|
const (
|
|
SpellRecoveryBuffer int32 = 2000 // Additional recovery time buffer (2 seconds)
|
|
)
|
|
|
|
// Brain type constants for identification
|
|
const (
|
|
BrainTypeDefault int8 = 0
|
|
BrainTypeCombatPet int8 = 1
|
|
BrainTypeNonCombatPet int8 = 2
|
|
BrainTypeBlank int8 = 3
|
|
BrainTypeCustom int8 = 4
|
|
BrainTypeDumbFire int8 = 5
|
|
)
|
|
|
|
// Pet movement constants
|
|
const (
|
|
PetMovementFollow int8 = 0
|
|
PetMovementStay int8 = 1
|
|
PetMovementGuard int8 = 2
|
|
)
|
|
|
|
// Encounter state constants
|
|
const (
|
|
EncounterStateAvailable int8 = 0
|
|
EncounterStateLocked int8 = 1
|
|
EncounterStateBroken int8 = 2
|
|
)
|
|
|
|
// Combat decision constants
|
|
const (
|
|
MeleeAttackChance int = 70 // Base chance for melee attack
|
|
SpellCastChance int = 30 // Base chance for spell casting
|
|
BuffCheckChance int = 50 // Chance to check for buffs
|
|
)
|
|
|
|
// AI state flags
|
|
const (
|
|
AIStateIdle int32 = 0
|
|
AIStateCombat int32 = 1
|
|
AIStateFollowing int32 = 2
|
|
AIStateRunback int32 = 3
|
|
AIStateCasting int32 = 4
|
|
AIStateMoving int32 = 5
|
|
)
|
|
|
|
// Debug levels
|
|
const (
|
|
DebugLevelNone int8 = 0
|
|
DebugLevelBasic int8 = 1
|
|
DebugLevelDetailed int8 = 2
|
|
DebugLevelVerbose int8 = 3
|
|
)
|
|
|
|
// Timer constants
|
|
const (
|
|
MillisecondsPerSecond int32 = 1000
|
|
RecoveryTimeMultiple int32 = 10 // Multiply cast/recovery times by 10
|
|
)
|
|
|
|
// MovementLocation represents a location for movement/runback
|
|
type MovementLocation struct {
|
|
X float32
|
|
Y float32
|
|
Z float32
|
|
GridID int32
|
|
Stage int32
|
|
}
|
|
|
|
// Brain interface defines all AI brain capabilities
|
|
type Brain interface {
|
|
// Core brain functions
|
|
Think() error
|
|
GetBrainType() int8
|
|
IsActive() bool
|
|
SetActive(bool)
|
|
GetLastThink() int64
|
|
SetLastThink(int64)
|
|
GetThinkTick() int32
|
|
SetThinkTick(int32)
|
|
|
|
// Hate management
|
|
AddHate(entityID, hate int32)
|
|
GetHate(entityID int32) int32
|
|
GetMostHated() int32
|
|
ClearHate()
|
|
ClearHateForEntity(entityID int32)
|
|
|
|
// Encounter management
|
|
AddToEncounter(entityID, characterID int32, isPlayer, isBot bool) bool
|
|
RemoveFromEncounter(entityID int32)
|
|
IsInEncounter(entityID int32) bool
|
|
ClearEncounter()
|
|
GetEncounterSize() int
|
|
|
|
// State management
|
|
GetState() int32
|
|
SetState(int32)
|
|
HasRecovered() bool
|
|
SetSpellRecovery(int64)
|
|
|
|
// Statistics
|
|
GetStatistics() *BrainStatistics
|
|
|
|
// Debugging
|
|
SetDebugLevel(int8)
|
|
GetDebugLevel() int8
|
|
}
|
|
|
|
// Logger interface for AI logging
|
|
type Logger interface {
|
|
LogInfo(message string, args ...any)
|
|
LogError(message string, args ...any)
|
|
LogDebug(message string, args ...any)
|
|
LogWarning(message string, args ...any)
|
|
}
|
|
|
|
// NPC interface defines the required NPC functionality for AI
|
|
type NPC interface {
|
|
// Basic NPC information
|
|
GetID() int32
|
|
GetName() string
|
|
GetHP() int32
|
|
GetTotalHP() int32
|
|
SetHP(int32)
|
|
IsAlive() bool
|
|
|
|
// Combat state
|
|
GetInCombat() bool
|
|
InCombat(bool)
|
|
GetTarget() Entity
|
|
SetTarget(Entity)
|
|
|
|
// Pet functionality
|
|
IsPet() bool
|
|
GetOwner() Entity
|
|
|
|
// Movement and positioning
|
|
GetX() float32
|
|
GetY() float32
|
|
GetZ() float32
|
|
GetDistance(Entity) float32
|
|
FaceTarget(Entity, bool)
|
|
IsFollowing() bool
|
|
SetFollowing(bool)
|
|
GetFollowTarget() Spawn
|
|
SetFollowTarget(Spawn, float32)
|
|
CalculateRunningLocation(bool)
|
|
ClearRunningLocations()
|
|
|
|
// Runback functionality
|
|
IsRunningBack() bool
|
|
GetRunbackLocation() *MovementLocation
|
|
GetRunbackDistance() float32
|
|
Runback(float32)
|
|
ShouldCallRunback() bool
|
|
SetCallRunback(bool)
|
|
|
|
// Status effects
|
|
IsMezzedOrStunned() bool
|
|
IsCasting() bool
|
|
IsDazed() bool
|
|
IsFeared() bool
|
|
IsStifled() bool
|
|
InWater() bool
|
|
IsWaterCreature() bool
|
|
IsFlyingCreature() bool
|
|
|
|
// Combat mechanics
|
|
AttackAllowed(Entity) bool
|
|
PrimaryWeaponReady() bool
|
|
SecondaryWeaponReady() bool
|
|
SetPrimaryLastAttackTime(int64)
|
|
SetSecondaryLastAttackTime(int64)
|
|
MeleeAttack(Entity, float32, bool)
|
|
|
|
// Spell casting
|
|
GetCastPercentage() int8
|
|
GetNextSpell(Entity, float32) Spell
|
|
GetNextBuffSpell(Spawn) Spell
|
|
SetCastOnAggroCompleted(bool)
|
|
CheckLoS(Entity) bool
|
|
|
|
// Movement pausing
|
|
IsPauseMovementTimerActive() bool
|
|
|
|
// Encounter state
|
|
SetEncounterState(int8)
|
|
|
|
// Scripts
|
|
GetSpawnScript() string
|
|
|
|
// Utility
|
|
KillSpawn(NPC)
|
|
}
|
|
|
|
// Entity interface for combat entities
|
|
type Entity interface {
|
|
Spawn
|
|
GetID() int32
|
|
GetName() string
|
|
GetHP() int32
|
|
GetTotalHP() int32
|
|
IsPlayer() bool
|
|
IsBot() bool
|
|
IsPet() bool
|
|
GetOwner() Entity
|
|
InWater() bool
|
|
}
|
|
|
|
// Spawn interface for basic spawn functionality
|
|
type Spawn interface {
|
|
GetID() int32
|
|
GetName() string
|
|
GetX() float32
|
|
GetY() float32
|
|
GetZ() float32
|
|
}
|
|
|
|
// Spell interface for spell data
|
|
type Spell interface {
|
|
GetSpellID() int32
|
|
GetName() string
|
|
IsFriendlySpell() bool
|
|
GetCastTime() int32
|
|
GetRecoveryTime() int32
|
|
GetRange() float32
|
|
GetMinRange() float32
|
|
}
|
|
|
|
// Zone interface for zone-related AI operations
|
|
type Zone interface {
|
|
GetSpawnByID(int32) Spawn
|
|
ProcessSpell(spell Spell, caster NPC, target Spawn) error
|
|
CallSpawnScript(npc NPC, scriptType string, args ...any) error
|
|
}
|
|
|
|
// Packet types for AI communication
|
|
type AIPacketType uint8
|
|
|
|
const (
|
|
AIPacketTypeUpdate AIPacketType = 1
|
|
AIPacketTypeStateUpdate AIPacketType = 2
|
|
AIPacketTypeHateUpdate AIPacketType = 3
|
|
AIPacketTypeEncounter AIPacketType = 4
|
|
AIPacketTypeCommand AIPacketType = 5
|
|
AIPacketTypeBrainType AIPacketType = 6
|
|
AIPacketTypeStatistics AIPacketType = 7
|
|
AIPacketTypeDebugUpdate AIPacketType = 8
|
|
)
|
|
|
|
// AIPacket represents an AI communication packet
|
|
type AIPacket struct {
|
|
Type AIPacketType `json:"type"`
|
|
EntityID int32 `json:"entity_id"`
|
|
Timestamp int64 `json:"timestamp"`
|
|
Data map[string]interface{} `json:"data"`
|
|
}
|
|
|
|
// AIClient interface for AI communication
|
|
type AIClient interface {
|
|
SendPacket(packet *AIPacket) error
|
|
BroadcastPacket(packet *AIPacket) error
|
|
}
|
|
|
|
// AIStatistics contains AI system statistics
|
|
type AIStatistics struct {
|
|
TotalBrains int `json:"total_brains"`
|
|
ActiveBrains int `json:"active_brains"`
|
|
TotalThinks int64 `json:"total_thinks"`
|
|
BrainsByType map[string]int `json:"brains_by_type"`
|
|
}
|
|
|
|
// BrainStatistics contains brain performance statistics
|
|
type BrainStatistics struct {
|
|
ThinkCycles int64 `json:"think_cycles"`
|
|
SpellsCast int64 `json:"spells_cast"`
|
|
MeleeAttacks int64 `json:"melee_attacks"`
|
|
HateEvents int64 `json:"hate_events"`
|
|
EncounterEvents int64 `json:"encounter_events"`
|
|
AverageThinkTime float64 `json:"average_think_time_ms"`
|
|
LastThinkTime int64 `json:"last_think_time"`
|
|
TotalActiveTime int64 `json:"total_active_time_ms"`
|
|
}
|
|
|
|
// NewBrainStatistics creates new brain statistics
|
|
func NewBrainStatistics() *BrainStatistics {
|
|
return &BrainStatistics{
|
|
ThinkCycles: 0,
|
|
SpellsCast: 0,
|
|
MeleeAttacks: 0,
|
|
HateEvents: 0,
|
|
EncounterEvents: 0,
|
|
AverageThinkTime: 0.0,
|
|
LastThinkTime: time.Now().UnixMilli(),
|
|
TotalActiveTime: 0,
|
|
}
|
|
}
|
|
|
|
// CreateBrain creates the appropriate brain type for an NPC
|
|
func CreateBrain(npc NPC, brainType int8, logger Logger, options ...any) Brain {
|
|
switch brainType {
|
|
case BrainTypeCombatPet:
|
|
return NewCombatPetBrain(npc, logger)
|
|
|
|
case BrainTypeNonCombatPet:
|
|
return NewNonCombatPetBrain(npc, logger)
|
|
|
|
case BrainTypeBlank:
|
|
return NewBlankBrain(npc, logger)
|
|
|
|
case BrainTypeCustom:
|
|
if len(options) > 0 {
|
|
if customAI, ok := options[0].(CustomAI); ok {
|
|
return NewCustomBrain(npc, logger, customAI)
|
|
}
|
|
}
|
|
return NewBaseBrain(npc, logger) // Fallback to default
|
|
|
|
case BrainTypeDumbFire:
|
|
if len(options) >= 2 {
|
|
if target, ok := options[0].(Entity); ok {
|
|
if expireTime, ok := options[1].(int32); ok {
|
|
return NewDumbFirePetBrain(npc, target, expireTime, logger)
|
|
}
|
|
}
|
|
}
|
|
return NewBaseBrain(npc, logger) // Fallback to default
|
|
|
|
default:
|
|
return NewBaseBrain(npc, logger)
|
|
}
|
|
}
|
|
|
|
// Helper functions
|
|
|
|
// getBrainTypeName returns the string name for a brain type
|
|
func getBrainTypeName(brainType int8) string {
|
|
switch brainType {
|
|
case BrainTypeDefault:
|
|
return "default"
|
|
case BrainTypeCombatPet:
|
|
return "combat_pet"
|
|
case BrainTypeNonCombatPet:
|
|
return "non_combat_pet"
|
|
case BrainTypeBlank:
|
|
return "blank"
|
|
case BrainTypeCustom:
|
|
return "custom"
|
|
case BrainTypeDumbFire:
|
|
return "dumbfire"
|
|
default:
|
|
return "unknown"
|
|
}
|
|
}
|
|
|
|
// currentTimeMillis returns current time in milliseconds
|
|
func currentTimeMillis() int64 {
|
|
return time.Now().UnixMilli()
|
|
}
|
|
|
|
// CreateAIPacket creates a new AI packet
|
|
func CreateAIPacket(packetType AIPacketType, entityID int32, data map[string]interface{}) *AIPacket {
|
|
return &AIPacket{
|
|
Type: packetType,
|
|
EntityID: entityID,
|
|
Timestamp: currentTimeMillis(),
|
|
Data: data,
|
|
}
|
|
}
|
|
|
|
// ToJSON converts AI packet to JSON
|
|
func (p *AIPacket) ToJSON() ([]byte, error) {
|
|
return json.Marshal(p)
|
|
}
|
|
|
|
// FromJSON creates AI packet from JSON
|
|
func FromJSON(data []byte) (*AIPacket, error) {
|
|
var packet AIPacket
|
|
err := json.Unmarshal(data, &packet)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal AI packet: %w", err)
|
|
}
|
|
return &packet, nil
|
|
} |