655 lines
19 KiB
Go
655 lines
19 KiB
Go
package zone
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"sync"
|
|
"time"
|
|
|
|
"eq2emu/internal/spawn"
|
|
)
|
|
|
|
// MobMovementManager handles movement for all NPCs and entities in the zone
|
|
type MobMovementManager struct {
|
|
zone *ZoneServer
|
|
movementSpawns map[int32]*MovementState
|
|
commandQueue map[int32][]*MovementCommand
|
|
stuckSpawns map[int32]*StuckInfo
|
|
processedSpawns map[int32]bool
|
|
lastUpdate time.Time
|
|
isProcessing bool
|
|
mutex sync.RWMutex
|
|
}
|
|
|
|
// MovementState tracks the current movement state of a spawn
|
|
type MovementState struct {
|
|
SpawnID int32
|
|
CurrentCommand *MovementCommand
|
|
CommandQueue []*MovementCommand
|
|
LastPosition *Position
|
|
LastMoveTime time.Time
|
|
IsMoving bool
|
|
IsStuck bool
|
|
StuckCount int
|
|
Speed float32
|
|
MovementMode int8
|
|
TargetPosition *Position
|
|
TargetHeading float32
|
|
PathNodes []*PathNode
|
|
CurrentNodeIndex int
|
|
PauseTime int32
|
|
PauseUntil time.Time
|
|
}
|
|
|
|
// MovementCommand represents a movement instruction
|
|
type MovementCommand struct {
|
|
Type int8
|
|
TargetX float32
|
|
TargetY float32
|
|
TargetZ float32
|
|
TargetHeading float32
|
|
Speed float32
|
|
MovementMode int8
|
|
StuckBehavior int8
|
|
MaxDistance float32
|
|
CompletionFunc func(*spawn.Spawn, bool) // Called when command completes (success bool)
|
|
}
|
|
|
|
// StuckInfo tracks stuck detection for spawns
|
|
type StuckInfo struct {
|
|
Position *Position
|
|
StuckCount int
|
|
LastStuckTime time.Time
|
|
Behavior int8
|
|
AttemptCount int
|
|
}
|
|
|
|
// NewMobMovementManager creates a new movement manager for the zone
|
|
func NewMobMovementManager(zone *ZoneServer) *MobMovementManager {
|
|
return &MobMovementManager{
|
|
zone: zone,
|
|
movementSpawns: make(map[int32]*MovementState),
|
|
commandQueue: make(map[int32][]*MovementCommand),
|
|
stuckSpawns: make(map[int32]*StuckInfo),
|
|
processedSpawns: make(map[int32]bool),
|
|
lastUpdate: time.Now(),
|
|
}
|
|
}
|
|
|
|
// Process handles movement processing for all managed spawns
|
|
func (mm *MobMovementManager) Process() error {
|
|
mm.mutex.Lock()
|
|
defer mm.mutex.Unlock()
|
|
|
|
if mm.isProcessing {
|
|
return nil // Already processing
|
|
}
|
|
|
|
mm.isProcessing = true
|
|
defer func() { mm.isProcessing = false }()
|
|
|
|
now := time.Now()
|
|
deltaTime := now.Sub(mm.lastUpdate).Seconds()
|
|
mm.lastUpdate = now
|
|
|
|
// Process each spawn with movement
|
|
for spawnID, state := range mm.movementSpawns {
|
|
spawn := mm.zone.GetSpawn(spawnID)
|
|
if spawn == nil {
|
|
// Spawn no longer exists, remove from tracking
|
|
delete(mm.movementSpawns, spawnID)
|
|
delete(mm.commandQueue, spawnID)
|
|
delete(mm.stuckSpawns, spawnID)
|
|
continue
|
|
}
|
|
|
|
// Skip if spawn is paused
|
|
if !state.PauseUntil.IsZero() && now.Before(state.PauseUntil) {
|
|
continue
|
|
}
|
|
|
|
// Process current command
|
|
if err := mm.processSpawnMovement(spawn, state, float32(deltaTime)); err != nil {
|
|
log.Printf("%s Error processing movement for spawn %d: %v", LogPrefixMovement, spawnID, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// AddMovementSpawn adds a spawn to movement tracking
|
|
func (mm *MobMovementManager) AddMovementSpawn(spawnID int32) {
|
|
mm.mutex.Lock()
|
|
defer mm.mutex.Unlock()
|
|
mm.addMovementSpawnInternal(spawnID)
|
|
}
|
|
|
|
// addMovementSpawnInternal adds a spawn to movement tracking without acquiring lock
|
|
func (mm *MobMovementManager) addMovementSpawnInternal(spawnID int32) {
|
|
if _, exists := mm.movementSpawns[spawnID]; exists {
|
|
return // Already tracking
|
|
}
|
|
|
|
spawn := mm.zone.GetSpawn(spawnID)
|
|
if spawn == nil {
|
|
return
|
|
}
|
|
|
|
x := spawn.GetX()
|
|
y := spawn.GetY()
|
|
z := spawn.GetZ()
|
|
heading := spawn.GetHeading()
|
|
|
|
mm.movementSpawns[spawnID] = &MovementState{
|
|
SpawnID: spawnID,
|
|
LastPosition: NewPosition(x, y, z, heading),
|
|
LastMoveTime: time.Now(),
|
|
Speed: DefaultRunSpeed,
|
|
MovementMode: MovementModeRun,
|
|
}
|
|
|
|
mm.commandQueue[spawnID] = make([]*MovementCommand, 0)
|
|
|
|
log.Printf("%s Added spawn %d to movement tracking", LogPrefixMovement, spawnID)
|
|
}
|
|
|
|
// RemoveMovementSpawn removes a spawn from movement tracking
|
|
func (mm *MobMovementManager) RemoveMovementSpawn(spawnID int32) {
|
|
mm.mutex.Lock()
|
|
defer mm.mutex.Unlock()
|
|
|
|
delete(mm.movementSpawns, spawnID)
|
|
delete(mm.commandQueue, spawnID)
|
|
delete(mm.stuckSpawns, spawnID)
|
|
delete(mm.processedSpawns, spawnID)
|
|
|
|
log.Printf("%s Removed spawn %d from movement tracking", LogPrefixMovement, spawnID)
|
|
}
|
|
|
|
// MoveTo commands a spawn to move to the specified position
|
|
func (mm *MobMovementManager) MoveTo(spawnID int32, x, y, z float32, speed float32) error {
|
|
command := &MovementCommand{
|
|
Type: MovementCommandMoveTo,
|
|
TargetX: x,
|
|
TargetY: y,
|
|
TargetZ: z,
|
|
Speed: speed,
|
|
MovementMode: MovementModeRun,
|
|
StuckBehavior: StuckBehaviorEvade,
|
|
}
|
|
|
|
return mm.QueueCommand(spawnID, command)
|
|
}
|
|
|
|
// SwimTo commands a spawn to swim to the specified position
|
|
func (mm *MobMovementManager) SwimTo(spawnID int32, x, y, z float32, speed float32) error {
|
|
command := &MovementCommand{
|
|
Type: MovementCommandSwimTo,
|
|
TargetX: x,
|
|
TargetY: y,
|
|
TargetZ: z,
|
|
Speed: speed,
|
|
MovementMode: MovementModeSwim,
|
|
StuckBehavior: StuckBehaviorWarp,
|
|
}
|
|
|
|
return mm.QueueCommand(spawnID, command)
|
|
}
|
|
|
|
// TeleportTo instantly moves a spawn to the specified position
|
|
func (mm *MobMovementManager) TeleportTo(spawnID int32, x, y, z, heading float32) error {
|
|
command := &MovementCommand{
|
|
Type: MovementCommandTeleportTo,
|
|
TargetX: x,
|
|
TargetY: y,
|
|
TargetZ: z,
|
|
TargetHeading: heading,
|
|
Speed: 0, // Instant
|
|
MovementMode: MovementModeRun,
|
|
}
|
|
|
|
return mm.QueueCommand(spawnID, command)
|
|
}
|
|
|
|
// RotateTo commands a spawn to rotate to the specified heading
|
|
func (mm *MobMovementManager) RotateTo(spawnID int32, heading float32, speed float32) error {
|
|
command := &MovementCommand{
|
|
Type: MovementCommandRotateTo,
|
|
TargetHeading: heading,
|
|
Speed: speed,
|
|
MovementMode: MovementModeRun,
|
|
}
|
|
|
|
return mm.QueueCommand(spawnID, command)
|
|
}
|
|
|
|
// StopMoving commands a spawn to stop all movement
|
|
func (mm *MobMovementManager) StopMoving(spawnID int32) error {
|
|
command := &MovementCommand{
|
|
Type: MovementCommandStop,
|
|
}
|
|
|
|
return mm.QueueCommand(spawnID, command)
|
|
}
|
|
|
|
// EvadeCombat commands a spawn to evade and return to its spawn point
|
|
func (mm *MobMovementManager) EvadeCombat(spawnID int32) error {
|
|
spawn := mm.zone.GetSpawn(spawnID)
|
|
if spawn == nil {
|
|
return fmt.Errorf("spawn %d not found", spawnID)
|
|
}
|
|
|
|
// Get spawn's original position (would need to be stored somewhere)
|
|
// For now, use current position as placeholder
|
|
x := spawn.GetX()
|
|
y := spawn.GetY()
|
|
z := spawn.GetZ()
|
|
heading := spawn.GetHeading()
|
|
|
|
command := &MovementCommand{
|
|
Type: MovementCommandEvadeCombat,
|
|
TargetX: x,
|
|
TargetY: y,
|
|
TargetZ: z,
|
|
TargetHeading: heading,
|
|
Speed: DefaultRunSpeed * 1.5, // Faster when evading
|
|
MovementMode: MovementModeRun,
|
|
StuckBehavior: StuckBehaviorWarp,
|
|
}
|
|
|
|
return mm.QueueCommand(spawnID, command)
|
|
}
|
|
|
|
// QueueCommand adds a movement command to the spawn's queue
|
|
func (mm *MobMovementManager) QueueCommand(spawnID int32, command *MovementCommand) error {
|
|
mm.mutex.Lock()
|
|
defer mm.mutex.Unlock()
|
|
|
|
// Ensure spawn is being tracked
|
|
if _, exists := mm.movementSpawns[spawnID]; !exists {
|
|
mm.addMovementSpawnInternal(spawnID)
|
|
}
|
|
|
|
// Add command to queue
|
|
if _, exists := mm.commandQueue[spawnID]; !exists {
|
|
mm.commandQueue[spawnID] = make([]*MovementCommand, 0)
|
|
}
|
|
|
|
mm.commandQueue[spawnID] = append(mm.commandQueue[spawnID], command)
|
|
|
|
log.Printf("%s Queued movement command %d for spawn %d", LogPrefixMovement, command.Type, spawnID)
|
|
return nil
|
|
}
|
|
|
|
// ClearCommands clears all queued commands for a spawn
|
|
func (mm *MobMovementManager) ClearCommands(spawnID int32) {
|
|
mm.mutex.Lock()
|
|
defer mm.mutex.Unlock()
|
|
|
|
if queue, exists := mm.commandQueue[spawnID]; exists {
|
|
mm.commandQueue[spawnID] = queue[:0] // Clear slice but keep capacity
|
|
}
|
|
|
|
if state, exists := mm.movementSpawns[spawnID]; exists {
|
|
state.CurrentCommand = nil
|
|
state.IsMoving = false
|
|
state.PathNodes = nil
|
|
state.CurrentNodeIndex = 0
|
|
}
|
|
}
|
|
|
|
// IsMoving returns whether a spawn is currently moving
|
|
func (mm *MobMovementManager) IsMoving(spawnID int32) bool {
|
|
mm.mutex.RLock()
|
|
defer mm.mutex.RUnlock()
|
|
|
|
if state, exists := mm.movementSpawns[spawnID]; exists {
|
|
return state.IsMoving
|
|
}
|
|
return false
|
|
}
|
|
|
|
// GetMovementState returns the current movement state for a spawn
|
|
func (mm *MobMovementManager) GetMovementState(spawnID int32) *MovementState {
|
|
mm.mutex.RLock()
|
|
defer mm.mutex.RUnlock()
|
|
|
|
if state, exists := mm.movementSpawns[spawnID]; exists {
|
|
// Return a copy to avoid race conditions
|
|
return &MovementState{
|
|
SpawnID: state.SpawnID,
|
|
IsMoving: state.IsMoving,
|
|
IsStuck: state.IsStuck,
|
|
Speed: state.Speed,
|
|
MovementMode: state.MovementMode,
|
|
CurrentNodeIndex: state.CurrentNodeIndex,
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Private methods
|
|
|
|
func (mm *MobMovementManager) processSpawnMovement(spawn *spawn.Spawn, state *MovementState, deltaTime float32) error {
|
|
// Get next command if not currently executing one
|
|
if state.CurrentCommand == nil {
|
|
state.CurrentCommand = mm.getNextCommand(state.SpawnID)
|
|
if state.CurrentCommand == nil {
|
|
state.IsMoving = false
|
|
return nil // No commands to process
|
|
}
|
|
}
|
|
|
|
// Process current command
|
|
completed, err := mm.processMovementCommand(spawn, state, deltaTime)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// If command completed, call completion function and get next command
|
|
if completed {
|
|
if state.CurrentCommand.CompletionFunc != nil {
|
|
state.CurrentCommand.CompletionFunc(spawn, true)
|
|
}
|
|
state.CurrentCommand = nil
|
|
state.IsMoving = false
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (mm *MobMovementManager) processMovementCommand(spawn *spawn.Spawn, state *MovementState, deltaTime float32) (bool, error) {
|
|
command := state.CurrentCommand
|
|
if command == nil {
|
|
return true, nil
|
|
}
|
|
|
|
switch command.Type {
|
|
case MovementCommandMoveTo:
|
|
return mm.processMoveTo(spawn, state, command, deltaTime)
|
|
case MovementCommandSwimTo:
|
|
return mm.processSwimTo(spawn, state, command, deltaTime)
|
|
case MovementCommandTeleportTo:
|
|
return mm.processTeleportTo(spawn, state, command)
|
|
case MovementCommandRotateTo:
|
|
return mm.processRotateTo(spawn, state, command, deltaTime)
|
|
case MovementCommandStop:
|
|
return mm.processStop(spawn, state, command)
|
|
case MovementCommandEvadeCombat:
|
|
return mm.processEvadeCombat(spawn, state, command, deltaTime)
|
|
default:
|
|
return true, fmt.Errorf("unknown movement command type: %d", command.Type)
|
|
}
|
|
}
|
|
|
|
func (mm *MobMovementManager) processMoveTo(spawn *spawn.Spawn, state *MovementState, command *MovementCommand, deltaTime float32) (bool, error) {
|
|
currentX := spawn.GetX()
|
|
currentY := spawn.GetY()
|
|
currentZ := spawn.GetZ()
|
|
|
|
// Calculate distance to target
|
|
distanceToTarget := Distance3D(currentX, currentY, currentZ, command.TargetX, command.TargetY, command.TargetZ)
|
|
|
|
// Check if we've reached the target
|
|
if distanceToTarget <= 0.5 { // Close enough threshold
|
|
return true, nil
|
|
}
|
|
|
|
// Check for stuck condition
|
|
if mm.checkStuck(spawn, state) {
|
|
return mm.handleStuck(spawn, state, command)
|
|
}
|
|
|
|
// Calculate movement
|
|
speed := command.Speed
|
|
if speed <= 0 {
|
|
speed = state.Speed
|
|
}
|
|
|
|
maxMove := speed * deltaTime
|
|
if maxMove > distanceToTarget {
|
|
maxMove = distanceToTarget
|
|
}
|
|
|
|
// Calculate direction
|
|
dx := command.TargetX - currentX
|
|
dy := command.TargetY - currentY
|
|
dz := command.TargetZ - currentZ
|
|
|
|
// Normalize direction
|
|
distance := Distance3D(0, 0, 0, dx, dy, dz)
|
|
if distance > 0 {
|
|
dx /= distance
|
|
dy /= distance
|
|
dz /= distance
|
|
}
|
|
|
|
// Calculate new position
|
|
newX := currentX + dx*maxMove
|
|
newY := currentY + dy*maxMove
|
|
newZ := currentZ + dz*maxMove
|
|
|
|
// Calculate heading to target
|
|
newHeading := CalculateHeading(currentX, currentY, command.TargetX, command.TargetY)
|
|
|
|
// Update spawn position
|
|
spawn.SetX(newX)
|
|
spawn.SetY(newY, false)
|
|
spawn.SetZ(newZ)
|
|
spawn.SetHeadingFromFloat(newHeading)
|
|
|
|
// Update state
|
|
state.LastPosition.Set(newX, newY, newZ, newHeading)
|
|
state.LastMoveTime = time.Now()
|
|
state.IsMoving = true
|
|
|
|
// Mark spawn as changed for client updates
|
|
mm.zone.markSpawnChanged(spawn.GetID())
|
|
|
|
return false, nil // Not completed yet
|
|
}
|
|
|
|
func (mm *MobMovementManager) processSwimTo(spawn *spawn.Spawn, state *MovementState, command *MovementCommand, deltaTime float32) (bool, error) {
|
|
// Similar to MoveTo but with different movement mode
|
|
return mm.processMoveTo(spawn, state, command, deltaTime)
|
|
}
|
|
|
|
func (mm *MobMovementManager) processTeleportTo(spawn *spawn.Spawn, state *MovementState, command *MovementCommand) (bool, error) {
|
|
// Instant teleport
|
|
spawn.SetX(command.TargetX)
|
|
spawn.SetY(command.TargetY, false)
|
|
spawn.SetZ(command.TargetZ)
|
|
spawn.SetHeadingFromFloat(command.TargetHeading)
|
|
|
|
// Update state
|
|
state.LastPosition.Set(command.TargetX, command.TargetY, command.TargetZ, command.TargetHeading)
|
|
state.LastMoveTime = time.Now()
|
|
state.IsMoving = false
|
|
|
|
// Mark spawn as changed
|
|
mm.zone.markSpawnChanged(spawn.GetID())
|
|
|
|
log.Printf("%s Teleported spawn %d to (%.2f, %.2f, %.2f)",
|
|
LogPrefixMovement, spawn.GetID(), command.TargetX, command.TargetY, command.TargetZ)
|
|
|
|
return true, nil // Completed immediately
|
|
}
|
|
|
|
func (mm *MobMovementManager) processRotateTo(spawn *spawn.Spawn, state *MovementState, command *MovementCommand, deltaTime float32) (bool, error) {
|
|
currentHeading := spawn.GetHeading()
|
|
|
|
// Calculate heading difference
|
|
headingDiff := HeadingDifference(currentHeading, command.TargetHeading)
|
|
|
|
// Check if we've reached the target heading
|
|
if abs(headingDiff) <= 1.0 { // Close enough threshold
|
|
spawn.SetHeadingFromFloat(command.TargetHeading)
|
|
mm.zone.markSpawnChanged(spawn.GetID())
|
|
return true, nil
|
|
}
|
|
|
|
// Calculate rotation speed
|
|
rotationSpeed := command.Speed
|
|
if rotationSpeed <= 0 {
|
|
rotationSpeed = 90.0 // Default rotation speed in heading units per second
|
|
}
|
|
|
|
maxRotation := rotationSpeed * deltaTime
|
|
|
|
// Determine rotation direction and amount
|
|
var rotation float32
|
|
if abs(headingDiff) <= maxRotation {
|
|
rotation = headingDiff
|
|
} else if headingDiff > 0 {
|
|
rotation = maxRotation
|
|
} else {
|
|
rotation = -maxRotation
|
|
}
|
|
|
|
// Apply rotation
|
|
newHeading := NormalizeHeading(currentHeading + rotation)
|
|
spawn.SetHeadingFromFloat(newHeading)
|
|
|
|
// Update state
|
|
state.LastPosition.Heading = newHeading
|
|
state.LastMoveTime = time.Now()
|
|
|
|
// Mark spawn as changed
|
|
mm.zone.markSpawnChanged(spawn.GetID())
|
|
|
|
return false, nil // Not completed yet
|
|
}
|
|
|
|
func (mm *MobMovementManager) processStop(spawn *spawn.Spawn, state *MovementState, command *MovementCommand) (bool, error) {
|
|
state.IsMoving = false
|
|
state.PathNodes = nil
|
|
state.CurrentNodeIndex = 0
|
|
|
|
log.Printf("%s Stopped movement for spawn %d", LogPrefixMovement, spawn.GetID())
|
|
return true, nil
|
|
}
|
|
|
|
func (mm *MobMovementManager) processEvadeCombat(spawn *spawn.Spawn, state *MovementState, command *MovementCommand, deltaTime float32) (bool, error) {
|
|
// Similar to MoveTo but with evade behavior
|
|
return mm.processMoveTo(spawn, state, command, deltaTime)
|
|
}
|
|
|
|
func (mm *MobMovementManager) getNextCommand(spawnID int32) *MovementCommand {
|
|
if queue, exists := mm.commandQueue[spawnID]; exists && len(queue) > 0 {
|
|
command := queue[0]
|
|
mm.commandQueue[spawnID] = queue[1:] // Remove first command
|
|
return command
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (mm *MobMovementManager) checkStuck(spawn *spawn.Spawn, state *MovementState) bool {
|
|
currentX := spawn.GetX()
|
|
currentY := spawn.GetY()
|
|
currentZ := spawn.GetZ()
|
|
|
|
// Check if spawn has moved significantly since last update
|
|
if state.LastPosition != nil {
|
|
distance := Distance3D(currentX, currentY, currentZ, state.LastPosition.X, state.LastPosition.Y, state.LastPosition.Z)
|
|
if distance < 0.1 && time.Since(state.LastMoveTime) > time.Second*2 {
|
|
// Spawn hasn't moved much in 2 seconds
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (mm *MobMovementManager) handleStuck(spawn *spawn.Spawn, state *MovementState, command *MovementCommand) (bool, error) {
|
|
spawnID := spawn.GetID()
|
|
|
|
// Get or create stuck info
|
|
stuckInfo, exists := mm.stuckSpawns[spawnID]
|
|
if !exists {
|
|
currentX := spawn.GetX()
|
|
currentY := spawn.GetY()
|
|
currentZ := spawn.GetZ()
|
|
currentHeading := spawn.GetHeading()
|
|
stuckInfo = &StuckInfo{
|
|
Position: NewPosition(currentX, currentY, currentZ, currentHeading),
|
|
StuckCount: 0,
|
|
LastStuckTime: time.Now(),
|
|
Behavior: command.StuckBehavior,
|
|
}
|
|
mm.stuckSpawns[spawnID] = stuckInfo
|
|
}
|
|
|
|
stuckInfo.StuckCount++
|
|
stuckInfo.AttemptCount++
|
|
|
|
log.Printf("%s Spawn %d is stuck (count: %d)", LogPrefixMovement, spawnID, stuckInfo.StuckCount)
|
|
|
|
switch stuckInfo.Behavior {
|
|
case StuckBehaviorNone:
|
|
return true, nil // Give up
|
|
|
|
case StuckBehaviorRun:
|
|
// Try to move around the obstacle
|
|
return mm.handleStuckWithRun(spawn, state, command, stuckInfo)
|
|
|
|
case StuckBehaviorWarp:
|
|
// Teleport to target
|
|
return mm.handleStuckWithWarp(spawn, state, command, stuckInfo)
|
|
|
|
case StuckBehaviorEvade:
|
|
// Return to spawn point
|
|
return mm.handleStuckWithEvade(spawn, state, command, stuckInfo)
|
|
|
|
default:
|
|
return true, nil // Unknown behavior, give up
|
|
}
|
|
}
|
|
|
|
func (mm *MobMovementManager) handleStuckWithRun(spawn *spawn.Spawn, state *MovementState, command *MovementCommand, stuckInfo *StuckInfo) (bool, error) {
|
|
if stuckInfo.AttemptCount > 5 {
|
|
return true, nil // Give up after 5 attempts
|
|
}
|
|
|
|
// Try a slightly different path
|
|
// Add some randomness to the movement
|
|
offsetX := float32((time.Now().UnixNano()%100 - 50)) / 50.0 * 2.0
|
|
offsetY := float32((time.Now().UnixNano()%100 - 50)) / 50.0 * 2.0
|
|
|
|
newTargetX := command.TargetX + offsetX
|
|
newTargetY := command.TargetY + offsetY
|
|
|
|
// Update command with new target
|
|
command.TargetX = newTargetX
|
|
command.TargetY = newTargetY
|
|
|
|
log.Printf("%s Trying alternate path for stuck spawn %d", LogPrefixMovement, spawn.GetID())
|
|
return false, nil // Continue with modified command
|
|
}
|
|
|
|
func (mm *MobMovementManager) handleStuckWithWarp(spawn *spawn.Spawn, state *MovementState, command *MovementCommand, stuckInfo *StuckInfo) (bool, error) {
|
|
// Teleport directly to target
|
|
spawn.SetX(command.TargetX)
|
|
spawn.SetY(command.TargetY, false)
|
|
spawn.SetZ(command.TargetZ)
|
|
spawn.SetHeadingFromFloat(command.TargetHeading)
|
|
mm.zone.markSpawnChanged(spawn.GetID())
|
|
|
|
log.Printf("%s Warped stuck spawn %d to target", LogPrefixMovement, spawn.GetID())
|
|
return true, nil // Command completed
|
|
}
|
|
|
|
func (mm *MobMovementManager) handleStuckWithEvade(spawn *spawn.Spawn, state *MovementState, command *MovementCommand, stuckInfo *StuckInfo) (bool, error) {
|
|
// Return to original position (evade)
|
|
if stuckInfo.Position != nil {
|
|
spawn.SetX(stuckInfo.Position.X)
|
|
spawn.SetY(stuckInfo.Position.Y, false)
|
|
spawn.SetZ(stuckInfo.Position.Z)
|
|
spawn.SetHeadingFromFloat(stuckInfo.Position.Heading)
|
|
mm.zone.markSpawnChanged(spawn.GetID())
|
|
|
|
log.Printf("%s Evaded stuck spawn %d to original position", LogPrefixMovement, spawn.GetID())
|
|
}
|
|
|
|
return true, nil // Command completed
|
|
}
|