internal void FollowPath(Vector2D weaponPos, IMovementPath path)
        {
            if (!IsWalkingEnabled)
            {
                return;
            }

            if (!currentWaypoint.HasValue || currentWaypoint.Value.DistanceTo(weaponPos) < WAYPOINT_RADIUS)
            {
                currentWaypoint = path.NextPoint;
                Api.Logger.Dev("Automaton: Next waypoint is " + currentWaypoint);
            }

            if (!currentWaypoint.HasValue)
            {
                Api.Logger.Error("Automaton: Failed to get next waypoint");
                return;
            }

            Vector2D diff = currentWaypoint.Value - weaponPos;

            var moveModes = CharacterMoveModesHelper.CalculateMoveModes(diff) | CharacterMoveModes.ModifierRun; // Running will yield us more LP/minute (by miniscule amount, though)
            var command   = new CharacterInputUpdate(moveModes, 0);                                             // Ugh, too lazy to look for usages to understand whether `0` is "up" or "right". Probably "right", but I won't mess with trigonometry, forgive me.

            ((PlayerCharacter)CurrentCharacter.ProtoCharacter).ClientSetInput(command);
        }
        /// <summary>
        /// Stop everything.
        /// </summary>
        public override void Stop()
        {
            if (attackInProgress)
            {
                attackInProgress = false;
                StopItemUse();
            }
            rememberedPath  = null;
            currentWaypoint = null;

            // ClientComponentPathRenderer.IsDrawing = false;
        }
        private IMovementPath FindTarget()
        {
            ICharacter user           = CurrentCharacter;
            double     searchDistance = GetCurrentWeaponRange() * 25;

            if (rememberedPath != null && !rememberedPath.Target.IsDestroyed && rememberedPath.Target.PhysicsBody.Position.DistanceTo(user.Position) < searchDistance)
            {
                return(rememberedPath);
            }

            using var objectsVisible = this.CurrentCharacter.PhysicsBody.PhysicsSpace
                                       .TestCircle(position: user.Position,
                                                   radius: searchDistance,    // I don't know what units the game uses, so let's stick with the search radius ~25 times as far as the weapon can hit. // UPD: Apparently it uses units. So, 1 tile is unit.
                                                   collisionGroup: CollisionGroups.HitboxMelee,
                                                   sendDebugEvent: false);

            // I'd rather chain these methods, but it seems like `using` has some relation to IDisposable. I'm not sure of consequences not using it might cause, so I'll copy the code. It's not like I want to keep your memory free from leaks or anything.
            var sortedVisibleObjects = objectsVisible?.AsList()
                                       ?.Where(t => this.EnabledEntityList.Contains(t.PhysicsBody?.AssociatedWorldObject?.ProtoGameObject))
                                       ?.Where(t => t.PhysicsBody?.AssociatedWorldObject is IStaticWorldObject)
                                       ?.Where(t => this.AdditionalValidation(t.PhysicsBody?.AssociatedWorldObject as IStaticWorldObject))
                                                                                                            // ?.Where(t => this.CheckIsVisible(t.PhysicsBody, weaponPos))
                                       ?.OrderBy(obj => obj.PhysicsBody.Position.DistanceTo(user.Position)) // Get closest ones
                                       ?.Take(10)                                                           // But take only 10 of them to reduce the load onto the pathfinder and eliminate the possibility of freezes.
                                                                                                            // ?.ToList() // Create a snapshot of the collection for the parallel executor
                                                                                                            // ?.AsParallel() // Faster paths calculation that uses more than 1 core.
                                       ?.Select(tgt => pathfinder.GetPath(user, tgt.PhysicsBody.AssociatedWorldObject))
                                                                                                            // ?.AsSequential()
                                       ?.OrderBy(path => path.Length)
                                       ?.ToList();

            if (sortedVisibleObjects == null || sortedVisibleObjects.Count == 0)
            {
                return(null);
            }

            rememberedPath = sortedVisibleObjects[0];

            ClientComponentPathRenderer.Instance.SetPoints(sortedVisibleObjects[0].Points);

            return(rememberedPath);
        }
        private void FindAndAttackTarget()
        {
            var fromPos = CurrentCharacter.Position; // or + GetWeaponOffset()?

            IMovementPath path = FindTarget();

            if (path == null)
            {
                return;
            }

            bool canAlreadyHit = GeometryHelper.GetCenterPosition(path.Target.PhysicsBody).DistanceTo(fromPos) < this.GetCurrentWeaponRange() / 1.1; // Get a bit closer to the target than maximal range.

            if (!canAlreadyHit)
            {
                FollowPath(fromPos, path);
                return; // Can safely ignore code below, because if we have to move to the closest object to attack it, we definitely cannot hit it.
            }

            // Api.Logger.Dev("Automaton: Got to the target! " + path.Target);
            var targetPoint = GeometryHelper.GetCenterPosition(path.Target.PhysicsBody);

            if (this.CheckForObstacles(path.Target.PhysicsBody as IStaticWorldObject, targetPoint, GetCurrentWeaponRange()))
            // if (true)
            {
                // Api.Logger.Dev("Automaton: Attacking the target! " + path.Target);
                this.AttackTarget(path.Target.PhysicsBody as IStaticWorldObject, targetPoint);
                this.attackInProgress = true;
                ClientTimersSystem.AddAction(this.GetCurrentWeaponAttackDelay(), () =>
                {
                    if (this.attackInProgress)
                    {
                        this.attackInProgress = false;
                        this.rememberedPath   = null;
                        this.StopItemUse();
                        this.FindAndAttackTarget();
                    }
                });
                return;
            }
        }