context: using System;
  using System.Numerics;
  
  using ACE.Common;
  using ACE.Entity;
  using ACE.Entity.Enum;
  using ACE.Entity.Enum.Properties;
  using ACE.Server.Entity;
  using ACE.Server.Entity.Actions;
  using ACE.Server.Managers;
  using ACE.Server.Physics.Animation;
  using ACE.Server.Physics.Common;
  
  namespace ACE.Server.WorldObjects
  {
      partial class Creature
      {
          /// <summary>
          /// Return to home if target distance exceeds this range
          /// </summary>
          public static readonly float MaxChaseRange = 96.0f;
          public static readonly float MaxChaseRangeSq = MaxChaseRange * MaxChaseRange;
  
          /// <summary>
          /// Determines if a monster is within melee range of target
          /// </summary>
          //public static readonly float MaxMeleeRange = 1.5f;
          public static readonly float MaxMeleeRange = 0.75f;
          //public static readonly float MaxMeleeRange = 1.5f + 0.6f + 0.1f;    // max melee range + distance from + buffer
  
          /// <summary>
          /// The maximum range for a monster missile attack
          /// </summary>
          //public static readonly float MaxMissileRange = 80.0f;
          //public static readonly float MaxMissileRange = 40.0f;   // for testing
  
          /// <summary>
          /// The distance per second from running animation
          /// </summary>
          public float MoveSpeed;
  
          /// <summary>
          /// The run skill via MovementSystem GetRunRate()
          /// </summary>
          public float RunRate;
  
          /// <summary>
          /// Flag indicates monster is turning towards target
          /// </summary>
          public bool IsTurning = false;
  
          /// <summary>
          /// Flag indicates monster is moving towards target
          /// </summary>
          public bool IsMoving = false;
  
          /// <summary>
          /// The last time a movement tick was processed
          /// </summary>
          public double LastMoveTime;
  
          public bool DebugMove;
  
          public double NextMoveTime;
          public double NextCancelTime;
  
          /// <summary>
          /// Starts the process of monster turning towards target
          /// </summary>
          public void StartTurn()
          {
              //if (Timers.RunningTime < NextMoveTime)
              //return;
              if (!MoveReady())
                  return;
  
              if (DebugMove)
                  Console.WriteLine($"{Name} ({Guid}) - StartTurn, ranged={IsRanged}");
  
              if (MoveSpeed == 0.0f)
                  GetMovementSpeed();
  
              //Console.WriteLine($"[{Timers.RunningTime}] - {Name} ({Guid}) - starting turn");
  
              IsTurning = true;
  
              // send network actions
              var targetDist = GetDistanceToTarget();
              var turnTo = IsRanged || (CurrentAttack == CombatType.Magic && targetDist <= GetSpellMaxRange()) || AiImmobile;
              if (turnTo)
                  TurnTo(AttackTarget);
              else
                  MoveTo(AttackTarget, RunRate);
  
              // need turning listener?
              IsTurning = false;
              IsMoving = true;
              LastMoveTime = Timers.RunningTime;
              NextCancelTime = LastMoveTime + ThreadSafeRandom.Next(2, 4);
              moveBit = false;
  
              var mvp = GetMovementParameters();
              if (turnTo)
                  PhysicsObj.TurnToObject(AttackTarget.PhysicsObj, mvp);
              else
                  PhysicsObj.MoveToObject(AttackTarget.PhysicsObj, mvp);
  
              // prevent initial snap
              PhysicsObj.UpdateTime = PhysicsTimer.CurrentTime;
          }
  
          /// <summary>
          /// Called when the TurnTo process has completed
          /// </summary>
          public void OnTurnComplete()
          {
              var dir = Vector3.Normalize(AttackTarget.Location.ToGlobal() - Location.ToGlobal());
              Location.Rotate(dir);
  
              IsTurning = false;
  
              if (!IsRanged)
                  StartMove();
          }
  
          /// <summary>
          /// Starts the process of monster moving towards target
          /// </summary>
          public void StartMove()
          {
              LastMoveTime = Timers.RunningTime;
              IsMoving = true;
          }
  
          /// <summary>
          /// Called when the MoveTo process has completed
          /// </summary>
          public override void OnMoveComplete(WeenieError status)
          {
              if (DebugMove)
                  Console.WriteLine($"{Name} ({Guid}) - OnMoveComplete({status})");
  
              if (status != WeenieError.None)
                  return;
  
              if (AiImmobile && CurrentAttack == CombatType.Melee)
              {
                  var targetDist = GetDistanceToTarget();
                  if (targetDist > MaxRange)
                      ResetAttack();
              }
  
              if (MonsterState == State.Return)
                  Sleep();
  
              PhysicsObj.CachedVelocity = Vector3.Zero;
              IsMoving = false;
          }
  
          /// <summary>
          /// Estimates the time it will take the monster to turn and move towards target
          /// </summary>
          public float EstimateTargetTime()
          {
              return EstimateTurnTo() + EstimateMoveTo();
          }
  
          /// <summary>
          /// Estimates the time it will take the monster to turn towards target
          /// </summary>
          public float EstimateTurnTo()
          {
              return GetRotateDelay(AttackTarget);
          }
  
          /// <summary>
          /// Estimates the time it will take the monster to move towards target
          /// </summary>
          public float EstimateMoveTo()
          {
              return GetDistanceToTarget() / MoveSpeed;
          }
  
          /// <summary>
          /// Returns TRUE if monster is within target melee range
          /// </summary>
          public bool IsMeleeRange()
          {
              return GetDistanceToTarget() <= MaxMeleeRange;
          }
  
          /// <summary>
          /// Returns TRUE if monster in range for current attack type
          /// </summary>
          public bool IsAttackRange()
          {
              return GetDistanceToTarget() <= MaxRange;
          }
  
          /// <summary>
          /// Gets the distance to target, with radius excluded
          /// </summary>
          public float GetDistanceToTarget()
          {
              if (AttackTarget == null)
                  return float.MaxValue;
  
              //var matchIndoors = Location.Indoors == AttackTarget.Location.Indoors;
              //var targetPos = matchIndoors ? AttackTarget.Location.ToGlobal() : AttackTarget.Location.Pos;
              //var sourcePos = matchIndoors ? Location.ToGlobal() : Location.Pos;
  
              //var dist = (targetPos - sourcePos).Length();
              //var radialDist = dist - (AttackTarget.PhysicsObj.GetRadius() + PhysicsObj.GetRadius());
  
              // always use spheres?
              var cylDist = (float)Physics.Common.Position.CylinderDistance(PhysicsObj.GetRadius(), PhysicsObj.GetHeight(), PhysicsObj.Position,
                  AttackTarget.PhysicsObj.GetRadius(), AttackTarget.PhysicsObj.GetHeight(), AttackTarget.PhysicsObj.Position);
  
              if (DebugMove)
                  Console.WriteLine($"{Name}.DistanceToTarget: {cylDist}");
  
              //return radialDist;
              return cylDist;
          }
  
          /// <summary>
          /// Returns the destination position the monster is attempting to move to
          /// to perform a melee attack
          /// </summary>
          public Vector3 GetDestination()
          {
              var dir = Vector3.Normalize(Location.ToGlobal() - AttackTarget.Location.ToGlobal());
              return AttackTarget.Location.Pos + dir * (AttackTarget.PhysicsObj.GetRadius() + PhysicsObj.GetRadius());
          }
  
          /// <summary>
          /// Primary movement handler, determines if target in range
          /// </summary>
          public void Movement()
          {
              //if (!IsRanged)
                  UpdatePosition();
  
              if (MonsterState == State.Awake && GetDistanceToTarget() >= MaxChaseRange)
              {
                  CancelMoveTo();
                  FindNextTarget();
                  return;
              }
  
              if (PhysicsObj.MovementManager.MoveToManager.FailProgressCount > 0 && Timers.RunningTime > NextCancelTime)
                  CancelMoveTo();
          }
  
          public void UpdatePosition(bool netsend = true)
          {
              stopwatch.Restart();
              PhysicsObj.update_object();
              ServerPerformanceMonitor.AddToCumulativeEvent(ServerPerformanceMonitor.CumulativeEventHistoryType.Monster_Navigation_UpdatePosition_PUO, stopwatch.Elapsed.TotalSeconds);
              UpdatePosition_SyncLocation();
  
              if (netsend)
                  SendUpdatePosition();
  
              if (DebugMove)
                  //Console.WriteLine($"{Name} ({Guid}) - UpdatePosition (velocity: {PhysicsObj.CachedVelocity.Length()})");
                  Console.WriteLine($"{Name} ({Guid}) - UpdatePosition: {Location.ToLOCString()}");
  
              if (MonsterState == State.Return && PhysicsObj.MovementManager.MoveToManager.PendingActions.Count == 0)
                  Sleep();
  
              if (MonsterState == State.Awake && IsMoving && PhysicsObj.MovementManager.MoveToManager.PendingActions.Count == 0)
                  IsMoving = false;
          }
  
          /// <summary>
          /// Synchronizes the WorldObject Location with the Physics Location
          /// </summary>
          public void UpdatePosition_SyncLocation()
          {
              // was the position successfully moved to?
              // use the physics position as the source-of-truth?
              var newPos = PhysicsObj.Position;
              if (Location.LandblockId.Raw != newPos.ObjCellID)
              {
                  var prevBlockCell = Location.LandblockId.Raw;
                  var prevBlock = Location.LandblockId.Raw >> 16;
                  var prevCell = Location.LandblockId.Raw & 0xFFFF;
  
                  var newBlockCell = newPos.ObjCellID;
                  var newBlock = newPos.ObjCellID >> 16;
                  var newCell = newPos.ObjCellID & 0xFFFF;
  
                  Location.LandblockId = new LandblockId(newPos.ObjCellID);
  
                  if (prevBlock != newBlock)
                  {
                      LandblockManager.RelocateObjectForPhysics(this, true);
                      //Console.WriteLine($"Relocating {Name} from {prevBlockCell:X8} to {newBlockCell:X8}");
                      //Console.WriteLine("Old position: " + Location.Pos);
                      //Console.WriteLine("New position: " + newPos.Frame.Origin);
                  }
                  //else
                      //Console.WriteLine("Moving " + Name + " to " + Location.LandblockId.Raw.ToString("X8"));
              }
  
              Location.Pos = newPos.Frame.Origin;
              Location.Rotation = newPos.Frame.Orientation;
  
              if (DebugMove)
                  DebugDistance();
          }
  
          public void DebugDistance()
          {
              if (AttackTarget == null) return;
  
              var dist = GetDistanceToTarget();
              var angle = GetAngle(AttackTarget);
              //Console.WriteLine("Dist: " + dist);
              //Console.WriteLine("Angle: " + angle);
          }
  
          public void GetMovementSpeed()
          {
              var moveSpeed = MotionTable.GetRunSpeed(MotionTableId);
              if (moveSpeed == 0) moveSpeed = 2.5f;
              var scale = ObjScale ?? 1.0f;
  
              RunRate = GetRunRate();
  
              MoveSpeed = moveSpeed * RunRate * scale;
  
              //Console.WriteLine(Name + " - Run: " + runSkill + " - RunRate: " + RunRate + " - Move: " + MoveSpeed + " - Scale: " + scale);
          }
  
          /// <summary>
          /// Returns the RunRate that is sent to the client as myRunRate
          /// </summary>
          public float GetRunRate()
          {
              var burden = 0.0f;
  
              // assuming burden only applies to players...
              if (this is Player player)
              {
                  var strength = Strength.Current;
  
                  var capacity = EncumbranceSystem.EncumbranceCapacity((int)strength, player.AugmentationIncreasedCarryingCapacity);
                  burden = EncumbranceSystem.GetBurden(capacity, EncumbranceVal ?? 0);
  
                  // TODO: find this exact formula in client
                  // technically this would be based on when the player releases / presses the movement key after stamina > 0
                  if (player.IsExhausted)
                      burden = 3.0f;
              }
  
              var runSkill = GetCreatureSkill(Skill.Run).Current;
              var runRate = MovementSystem.GetRunRate(burden, (int)runSkill, 1.0f);
  
              return (float)runRate;
          }
  
          /// <summary>
          /// Sets the corpse to the final position
          /// </summary>
          public void SetFinalPosition()
          {
              if (AttackTarget == null) return;
  
  
gt:             var playerDir = AttackTarget.Location.GetCurrentDir();