664 lines
17 KiB
Go
664 lines
17 KiB
Go
package ai
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// BaseBrain implements the basic AI brain functionality
|
|
type BaseBrain struct {
|
|
npc NPC
|
|
logger Logger
|
|
brainType int8
|
|
state *BrainState
|
|
hateList *HateList
|
|
encounter *EncounterList
|
|
statistics *BrainStatistics
|
|
}
|
|
|
|
// NewBaseBrain creates a new base brain
|
|
func NewBaseBrain(npc NPC, logger Logger) *BaseBrain {
|
|
return &BaseBrain{
|
|
npc: npc,
|
|
logger: logger,
|
|
brainType: BrainTypeDefault,
|
|
state: NewBrainState(),
|
|
hateList: NewHateList(),
|
|
encounter: NewEncounterList(),
|
|
statistics: NewBrainStatistics(),
|
|
}
|
|
}
|
|
|
|
// Think implements the main AI logic
|
|
func (bb *BaseBrain) Think() error {
|
|
if bb.npc == nil {
|
|
return fmt.Errorf("brain has no body")
|
|
}
|
|
|
|
// Update last think time
|
|
currentTime := time.Now().UnixMilli()
|
|
bb.state.SetLastThink(currentTime)
|
|
bb.statistics.ThinkCycles++
|
|
|
|
// Skip thinking if mezzzed, stunned, feared, or dazed
|
|
if bb.npc.IsMezzedOrStunned() || bb.npc.IsFeared() || bb.npc.IsDazed() {
|
|
return nil
|
|
}
|
|
|
|
// Check if we need to runback
|
|
if bb.npc.IsRunningBack() || bb.npc.ShouldCallRunback() {
|
|
return bb.handleRunback()
|
|
}
|
|
|
|
// Get most hated target
|
|
mostHatedID := bb.hateList.GetMostHated()
|
|
var target Entity
|
|
|
|
if mostHatedID > 0 {
|
|
target = bb.getEntityByID(mostHatedID)
|
|
if target == nil {
|
|
// Target no longer exists, remove from hate list
|
|
bb.hateList.RemoveHate(mostHatedID)
|
|
mostHatedID = bb.hateList.GetMostHated()
|
|
if mostHatedID > 0 {
|
|
target = bb.getEntityByID(mostHatedID)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle combat state
|
|
if target != nil && bb.npc.GetInCombat() {
|
|
return bb.handleCombat(target)
|
|
} else if bb.npc.GetInCombat() {
|
|
// No target but still in combat - exit combat
|
|
bb.npc.InCombat(false)
|
|
bb.npc.SetTarget(nil)
|
|
bb.state.SetState(AIStateIdle)
|
|
|
|
if bb.logger != nil && bb.state.GetDebugLevel() >= DebugLevelBasic {
|
|
bb.logger.LogDebug("NPC %s exiting combat - no targets", bb.npc.GetName())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Handle idle state
|
|
return bb.handleIdle()
|
|
}
|
|
|
|
// handleCombat handles combat AI logic
|
|
func (bb *BaseBrain) handleCombat(target Entity) error {
|
|
if target == nil {
|
|
return nil
|
|
}
|
|
|
|
// Set target if not already set
|
|
if bb.npc.GetTarget() != target {
|
|
bb.npc.SetTarget(target)
|
|
bb.npc.FaceTarget(target, false)
|
|
}
|
|
|
|
distance := bb.npc.GetDistance(target)
|
|
|
|
// Check if target is too far away
|
|
maxRange := bb.getMaxCombatRange()
|
|
if distance > maxRange {
|
|
// Try to move closer or runback if too far
|
|
if distance > MaxChaseDistance {
|
|
if bb.npc.ShouldCallRunback() {
|
|
bb.npc.Runback(bb.npc.GetRunbackDistance())
|
|
return nil
|
|
}
|
|
} else {
|
|
bb.MoveCloser(target)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
bb.state.SetState(AIStateCombat)
|
|
|
|
// Try to cast spells first if we have line of sight
|
|
if bb.npc.CheckLoS(target) && !bb.npc.IsCasting() {
|
|
if bb.HasRecovered() && bb.ProcessSpell(target, distance) {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Try melee combat
|
|
bb.ProcessMelee(target, distance)
|
|
|
|
return nil
|
|
}
|
|
|
|
// handleIdle handles idle AI logic
|
|
func (bb *BaseBrain) handleIdle() error {
|
|
bb.state.SetState(AIStateIdle)
|
|
|
|
// Set slower think tick when idle
|
|
bb.state.SetThinkTick(SlowThinkTick)
|
|
|
|
return nil
|
|
}
|
|
|
|
// handleRunback handles runback logic
|
|
func (bb *BaseBrain) handleRunback() error {
|
|
// Clear hate and encounter when running back
|
|
bb.hateList.Clear()
|
|
bb.encounter.Clear()
|
|
|
|
// Set target to nil
|
|
bb.npc.SetTarget(nil)
|
|
bb.npc.InCombat(false)
|
|
|
|
bb.state.SetState(AIStateRunback)
|
|
|
|
if bb.logger != nil && bb.state.GetDebugLevel() >= DebugLevelDetailed {
|
|
bb.logger.LogDebug("NPC %s running back", bb.npc.GetName())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ProcessSpell processes spell casting for the brain
|
|
func (bb *BaseBrain) ProcessSpell(target Entity, distance float32) bool {
|
|
if bb.npc == nil || target == nil {
|
|
return false
|
|
}
|
|
|
|
// Check if we can cast
|
|
if bb.npc.IsCasting() || !bb.HasRecovered() {
|
|
return false
|
|
}
|
|
|
|
// Get next spell to cast
|
|
spell := bb.npc.GetNextSpell(target, distance)
|
|
if spell == nil {
|
|
return false
|
|
}
|
|
|
|
// Check range
|
|
if distance < spell.GetMinRange() || distance > spell.GetRange() {
|
|
return false
|
|
}
|
|
|
|
// Cast the spell
|
|
if bb.logger != nil && bb.state.GetDebugLevel() >= DebugLevelDetailed {
|
|
bb.logger.LogDebug("NPC %s casting spell %s on %s",
|
|
bb.npc.GetName(), spell.GetName(), target.GetName())
|
|
}
|
|
|
|
// Set spell recovery time
|
|
recoveryTime := time.Now().UnixMilli() + int64(spell.GetRecoveryTime()*RecoveryTimeMultiple) + int64(SpellRecoveryBuffer)
|
|
bb.state.SetSpellRecovery(recoveryTime)
|
|
bb.state.SetState(AIStateCasting)
|
|
bb.statistics.SpellsCast++
|
|
|
|
return true
|
|
}
|
|
|
|
// ProcessMelee processes melee combat for the brain
|
|
func (bb *BaseBrain) ProcessMelee(target Entity, distance float32) {
|
|
if bb.npc == nil || target == nil {
|
|
return
|
|
}
|
|
|
|
// Check if we can attack
|
|
if !bb.npc.AttackAllowed(target) {
|
|
return
|
|
}
|
|
|
|
// Try primary weapon
|
|
if bb.npc.PrimaryWeaponReady() {
|
|
if bb.logger != nil && bb.state.GetDebugLevel() >= DebugLevelDetailed {
|
|
bb.logger.LogDebug("NPC %s melee attacking %s with primary weapon",
|
|
bb.npc.GetName(), target.GetName())
|
|
}
|
|
|
|
bb.npc.MeleeAttack(target, distance, true)
|
|
bb.npc.SetPrimaryLastAttackTime(time.Now().UnixMilli())
|
|
bb.statistics.MeleeAttacks++
|
|
return
|
|
}
|
|
|
|
// Try secondary weapon
|
|
if bb.npc.SecondaryWeaponReady() {
|
|
if bb.logger != nil && bb.state.GetDebugLevel() >= DebugLevelDetailed {
|
|
bb.logger.LogDebug("NPC %s melee attacking %s with secondary weapon",
|
|
bb.npc.GetName(), target.GetName())
|
|
}
|
|
|
|
bb.npc.MeleeAttack(target, distance, false)
|
|
bb.npc.SetSecondaryLastAttackTime(time.Now().UnixMilli())
|
|
bb.statistics.MeleeAttacks++
|
|
}
|
|
}
|
|
|
|
// MoveCloser moves the NPC closer to the target
|
|
func (bb *BaseBrain) MoveCloser(target Entity) {
|
|
if bb.npc == nil || target == nil {
|
|
return
|
|
}
|
|
|
|
// Face the target
|
|
bb.npc.FaceTarget(target, false)
|
|
|
|
// Calculate running location towards target
|
|
bb.npc.CalculateRunningLocation(true)
|
|
bb.state.SetState(AIStateMoving)
|
|
|
|
if bb.logger != nil && bb.state.GetDebugLevel() >= DebugLevelDetailed {
|
|
bb.logger.LogDebug("NPC %s moving closer to %s", bb.npc.GetName(), target.GetName())
|
|
}
|
|
}
|
|
|
|
// Brain interface implementation
|
|
|
|
// GetBrainType returns the brain type
|
|
func (bb *BaseBrain) GetBrainType() int8 {
|
|
return bb.brainType
|
|
}
|
|
|
|
// IsActive returns whether the brain is active
|
|
func (bb *BaseBrain) IsActive() bool {
|
|
return bb.state.IsActive()
|
|
}
|
|
|
|
// SetActive sets the brain's active state
|
|
func (bb *BaseBrain) SetActive(active bool) {
|
|
bb.state.SetActive(active)
|
|
}
|
|
|
|
// GetLastThink returns the timestamp of the last think cycle
|
|
func (bb *BaseBrain) GetLastThink() int64 {
|
|
return bb.state.GetLastThink()
|
|
}
|
|
|
|
// SetLastThink sets the timestamp of the last think cycle
|
|
func (bb *BaseBrain) SetLastThink(timestamp int64) {
|
|
bb.state.SetLastThink(timestamp)
|
|
}
|
|
|
|
// GetThinkTick returns the think tick interval
|
|
func (bb *BaseBrain) GetThinkTick() int32 {
|
|
return bb.state.GetThinkTick()
|
|
}
|
|
|
|
// SetThinkTick sets the think tick interval
|
|
func (bb *BaseBrain) SetThinkTick(tick int32) {
|
|
bb.state.SetThinkTick(tick)
|
|
}
|
|
|
|
// Hate management
|
|
|
|
// AddHate adds hate for an entity
|
|
func (bb *BaseBrain) AddHate(entityID, hate int32) {
|
|
bb.hateList.AddHate(entityID, hate)
|
|
bb.statistics.HateEvents++
|
|
|
|
// Enter combat if not already
|
|
if !bb.npc.GetInCombat() {
|
|
bb.npc.InCombat(true)
|
|
bb.state.SetThinkTick(FastThinkTick)
|
|
}
|
|
|
|
if bb.logger != nil && bb.state.GetDebugLevel() >= DebugLevelDetailed {
|
|
bb.logger.LogDebug("NPC %s gained %d hate for entity %d", bb.npc.GetName(), hate, entityID)
|
|
}
|
|
}
|
|
|
|
// GetHate returns hate for an entity
|
|
func (bb *BaseBrain) GetHate(entityID int32) int32 {
|
|
return bb.hateList.GetHate(entityID)
|
|
}
|
|
|
|
// GetMostHated returns the most hated entity ID
|
|
func (bb *BaseBrain) GetMostHated() int32 {
|
|
return bb.hateList.GetMostHated()
|
|
}
|
|
|
|
// ClearHate clears all hate
|
|
func (bb *BaseBrain) ClearHate() {
|
|
bb.hateList.Clear()
|
|
|
|
if bb.logger != nil && bb.state.GetDebugLevel() >= DebugLevelBasic {
|
|
bb.logger.LogDebug("NPC %s cleared all hate", bb.npc.GetName())
|
|
}
|
|
}
|
|
|
|
// ClearHateForEntity clears hate for a specific entity
|
|
func (bb *BaseBrain) ClearHateForEntity(entityID int32) {
|
|
bb.hateList.RemoveHate(entityID)
|
|
|
|
if bb.logger != nil && bb.state.GetDebugLevel() >= DebugLevelDetailed {
|
|
bb.logger.LogDebug("NPC %s cleared hate for entity %d", bb.npc.GetName(), entityID)
|
|
}
|
|
}
|
|
|
|
// Encounter management
|
|
|
|
// AddToEncounter adds an entity to the encounter
|
|
func (bb *BaseBrain) AddToEncounter(entityID, characterID int32, isPlayer, isBot bool) bool {
|
|
success := bb.encounter.AddEntity(entityID, characterID, isPlayer, isBot)
|
|
if success {
|
|
bb.statistics.EncounterEvents++
|
|
|
|
if bb.logger != nil && bb.state.GetDebugLevel() >= DebugLevelDetailed {
|
|
bb.logger.LogDebug("NPC %s added entity %d to encounter", bb.npc.GetName(), entityID)
|
|
}
|
|
}
|
|
return success
|
|
}
|
|
|
|
// RemoveFromEncounter removes an entity from the encounter
|
|
func (bb *BaseBrain) RemoveFromEncounter(entityID int32) {
|
|
bb.encounter.RemoveEntity(entityID)
|
|
|
|
if bb.logger != nil && bb.state.GetDebugLevel() >= DebugLevelDetailed {
|
|
bb.logger.LogDebug("NPC %s removed entity %d from encounter", bb.npc.GetName(), entityID)
|
|
}
|
|
}
|
|
|
|
// IsInEncounter checks if an entity is in the encounter
|
|
func (bb *BaseBrain) IsInEncounter(entityID int32) bool {
|
|
return bb.encounter.IsEntityInEncounter(entityID)
|
|
}
|
|
|
|
// ClearEncounter clears the encounter
|
|
func (bb *BaseBrain) ClearEncounter() {
|
|
bb.encounter.Clear()
|
|
|
|
if bb.logger != nil && bb.state.GetDebugLevel() >= DebugLevelBasic {
|
|
bb.logger.LogDebug("NPC %s cleared encounter", bb.npc.GetName())
|
|
}
|
|
}
|
|
|
|
// GetEncounterSize returns the encounter size
|
|
func (bb *BaseBrain) GetEncounterSize() int {
|
|
return bb.encounter.Size()
|
|
}
|
|
|
|
// State management
|
|
|
|
// GetState returns the current AI state
|
|
func (bb *BaseBrain) GetState() int32 {
|
|
return bb.state.GetState()
|
|
}
|
|
|
|
// SetState sets the current AI state
|
|
func (bb *BaseBrain) SetState(state int32) {
|
|
bb.state.SetState(state)
|
|
}
|
|
|
|
// HasRecovered checks if the brain has recovered from spell casting
|
|
func (bb *BaseBrain) HasRecovered() bool {
|
|
return bb.state.HasRecovered()
|
|
}
|
|
|
|
// SetSpellRecovery sets the spell recovery time
|
|
func (bb *BaseBrain) SetSpellRecovery(timestamp int64) {
|
|
bb.state.SetSpellRecovery(timestamp)
|
|
}
|
|
|
|
// GetStatistics returns brain statistics
|
|
func (bb *BaseBrain) GetStatistics() *BrainStatistics {
|
|
return bb.statistics
|
|
}
|
|
|
|
// GetDebugLevel returns the debug level
|
|
func (bb *BaseBrain) GetDebugLevel() int8 {
|
|
return bb.state.GetDebugLevel()
|
|
}
|
|
|
|
// SetDebugLevel sets the debug level
|
|
func (bb *BaseBrain) SetDebugLevel(level int8) {
|
|
bb.state.SetDebugLevel(level)
|
|
}
|
|
|
|
// Helper methods
|
|
|
|
// getEntityByID gets an entity by ID (placeholder - would be implemented by zone)
|
|
func (bb *BaseBrain) getEntityByID(entityID int32) Entity {
|
|
// This would typically call into the zone system to get the entity
|
|
// For now, return nil - concrete implementations should override this
|
|
return nil
|
|
}
|
|
|
|
// getMaxCombatRange returns the maximum combat range
|
|
func (bb *BaseBrain) getMaxCombatRange() float32 {
|
|
return MaxCombatRange
|
|
}
|
|
|
|
// Pet Brain Implementations
|
|
|
|
// CombatPetBrain extends the base brain for combat pets
|
|
type CombatPetBrain struct {
|
|
*BaseBrain
|
|
}
|
|
|
|
// NewCombatPetBrain creates a new combat pet brain
|
|
func NewCombatPetBrain(npc NPC, logger Logger) *CombatPetBrain {
|
|
brain := &CombatPetBrain{
|
|
BaseBrain: NewBaseBrain(npc, logger),
|
|
}
|
|
brain.brainType = BrainTypeCombatPet
|
|
return brain
|
|
}
|
|
|
|
// Think implements pet-specific AI logic
|
|
func (cpb *CombatPetBrain) Think() error {
|
|
// Call parent Think() for default combat behavior
|
|
if err := cpb.BaseBrain.Think(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Additional pet-specific logic
|
|
if cpb.npc.GetInCombat() || !cpb.npc.IsPet() || cpb.npc.IsMezzedOrStunned() {
|
|
return nil
|
|
}
|
|
|
|
if cpb.logger != nil && cpb.state.GetDebugLevel() >= DebugLevelDetailed {
|
|
cpb.logger.LogDebug("Combat pet AI for %s", cpb.npc.GetName())
|
|
}
|
|
|
|
// Check if owner has stay command set
|
|
owner := cpb.npc.GetOwner()
|
|
if owner != nil && owner.IsPlayer() {
|
|
// TODO: Check player's pet movement setting
|
|
// if player.GetInfoStruct().GetPetMovement() == PetMovementStay {
|
|
// return nil
|
|
// }
|
|
}
|
|
|
|
// Follow owner
|
|
if owner != nil {
|
|
cpb.npc.SetTarget(owner)
|
|
distance := cpb.npc.GetDistance(owner)
|
|
|
|
maxRange := cpb.getMaxCombatRange()
|
|
if distance > maxRange {
|
|
cpb.MoveCloser(owner)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// NonCombatPetBrain handles non-combat pets (cosmetic pets)
|
|
type NonCombatPetBrain struct {
|
|
*BaseBrain
|
|
}
|
|
|
|
// NewNonCombatPetBrain creates a new non-combat pet brain
|
|
func NewNonCombatPetBrain(npc NPC, logger Logger) *NonCombatPetBrain {
|
|
brain := &NonCombatPetBrain{
|
|
BaseBrain: NewBaseBrain(npc, logger),
|
|
}
|
|
brain.brainType = BrainTypeNonCombatPet
|
|
return brain
|
|
}
|
|
|
|
// Think implements non-combat pet AI (just following)
|
|
func (ncpb *NonCombatPetBrain) Think() error {
|
|
// Non-combat pets don't do combat AI
|
|
if !ncpb.npc.IsPet() || ncpb.npc.IsMezzedOrStunned() {
|
|
return nil
|
|
}
|
|
|
|
if ncpb.logger != nil && ncpb.state.GetDebugLevel() >= DebugLevelDetailed {
|
|
ncpb.logger.LogDebug("Non-combat pet AI for %s", ncpb.npc.GetName())
|
|
}
|
|
|
|
// Just follow owner
|
|
owner := ncpb.npc.GetOwner()
|
|
if owner != nil {
|
|
ncpb.npc.SetTarget(owner)
|
|
distance := ncpb.npc.GetDistance(owner)
|
|
|
|
maxRange := ncpb.getMaxCombatRange()
|
|
if distance > maxRange {
|
|
ncpb.MoveCloser(owner)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// BlankBrain provides a minimal AI that does nothing
|
|
type BlankBrain struct {
|
|
*BaseBrain
|
|
}
|
|
|
|
// NewBlankBrain creates a new blank brain
|
|
func NewBlankBrain(npc NPC, logger Logger) *BlankBrain {
|
|
brain := &BlankBrain{
|
|
BaseBrain: NewBaseBrain(npc, logger),
|
|
}
|
|
brain.brainType = BrainTypeBlank
|
|
brain.SetThinkTick(BlankBrainTick) // Very slow tick
|
|
return brain
|
|
}
|
|
|
|
// Think does nothing for blank brains
|
|
func (bb *BlankBrain) Think() error {
|
|
// Blank brain does nothing
|
|
return nil
|
|
}
|
|
|
|
// DumbFirePetBrain handles dumbfire pets (temporary combat pets)
|
|
type DumbFirePetBrain struct {
|
|
*BaseBrain
|
|
expireTime int64
|
|
}
|
|
|
|
// NewDumbFirePetBrain creates a new dumbfire pet brain
|
|
func NewDumbFirePetBrain(npc NPC, target Entity, expireTimeMS int32, logger Logger) *DumbFirePetBrain {
|
|
brain := &DumbFirePetBrain{
|
|
BaseBrain: NewBaseBrain(npc, logger),
|
|
expireTime: time.Now().UnixMilli() + int64(expireTimeMS),
|
|
}
|
|
brain.brainType = BrainTypeDumbFire
|
|
|
|
// Add maximum hate for the target
|
|
if target != nil {
|
|
brain.AddHate(target.GetID(), MaxHateValue)
|
|
}
|
|
|
|
return brain
|
|
}
|
|
|
|
// AddHate only allows hate for the initial target
|
|
func (dfpb *DumbFirePetBrain) AddHate(entityID int32, hate int32) {
|
|
// Only add hate if we don't already have a target
|
|
if dfpb.GetMostHated() == 0 {
|
|
dfpb.BaseBrain.AddHate(entityID, hate)
|
|
}
|
|
}
|
|
|
|
// Think implements dumbfire pet AI
|
|
func (dfpb *DumbFirePetBrain) Think() error {
|
|
// Check if expired
|
|
if time.Now().UnixMilli() > dfpb.expireTime {
|
|
if dfpb.npc != nil && dfpb.npc.GetHP() > 0 {
|
|
if dfpb.logger != nil {
|
|
dfpb.logger.LogDebug("Dumbfire pet %s expired", dfpb.npc.GetName())
|
|
}
|
|
dfpb.npc.KillSpawn(dfpb.npc)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Get target
|
|
targetID := dfpb.GetMostHated()
|
|
if targetID == 0 {
|
|
// No target, kill self
|
|
if dfpb.npc != nil && dfpb.npc.GetHP() > 0 {
|
|
if dfpb.logger != nil {
|
|
dfpb.logger.LogDebug("Dumbfire pet %s has no target", dfpb.npc.GetName())
|
|
}
|
|
dfpb.npc.KillSpawn(dfpb.npc)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
target := dfpb.getEntityByID(targetID)
|
|
if target == nil {
|
|
// Target no longer exists, kill self
|
|
if dfpb.npc != nil && dfpb.npc.GetHP() > 0 {
|
|
dfpb.npc.KillSpawn(dfpb.npc)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Skip if mezzed or stunned
|
|
if dfpb.npc.IsMezzedOrStunned() {
|
|
return nil
|
|
}
|
|
|
|
// Set target if not already set
|
|
if dfpb.npc.GetTarget() != target {
|
|
dfpb.npc.SetTarget(target)
|
|
dfpb.npc.FaceTarget(target, false)
|
|
}
|
|
|
|
// Enter combat if not already
|
|
if !dfpb.npc.GetInCombat() {
|
|
dfpb.npc.CalculateRunningLocation(true)
|
|
dfpb.npc.InCombat(true)
|
|
}
|
|
|
|
distance := dfpb.npc.GetDistance(target)
|
|
|
|
// Try to cast spells if we have line of sight
|
|
if dfpb.npc.CheckLoS(target) && !dfpb.npc.IsCasting() &&
|
|
(!dfpb.HasRecovered() || !dfpb.ProcessSpell(target, distance)) {
|
|
|
|
if dfpb.logger != nil && dfpb.state.GetDebugLevel() >= DebugLevelDetailed {
|
|
dfpb.logger.LogDebug("Dumbfire pet %s attempting melee on %s",
|
|
dfpb.npc.GetName(), target.GetName())
|
|
}
|
|
|
|
dfpb.npc.FaceTarget(target, false)
|
|
dfpb.ProcessMelee(target, distance)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetExpireTime returns when this dumbfire pet will expire
|
|
func (dfpb *DumbFirePetBrain) GetExpireTime() int64 {
|
|
return dfpb.expireTime
|
|
}
|
|
|
|
// SetExpireTime sets when this dumbfire pet will expire
|
|
func (dfpb *DumbFirePetBrain) SetExpireTime(expireTime int64) {
|
|
dfpb.expireTime = expireTime
|
|
}
|
|
|
|
// IsExpired checks if the dumbfire pet has expired
|
|
func (dfpb *DumbFirePetBrain) IsExpired() bool {
|
|
return time.Now().UnixMilli() > dfpb.expireTime
|
|
}
|
|
|
|
// ExtendExpireTime extends the expire time by the given duration
|
|
func (dfpb *DumbFirePetBrain) ExtendExpireTime(durationMS int32) {
|
|
dfpb.expireTime += int64(durationMS)
|
|
} |