]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
NPC obstacle fixes (#15645)
authormetalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Sat, 22 Apr 2023 08:57:19 +0000 (18:57 +1000)
committerGitHub <noreply@github.com>
Sat, 22 Apr 2023 08:57:19 +0000 (18:57 +1000)
Content.Server/NPC/Systems/NPCSteeringSystem.Context.cs
Content.Server/NPC/Systems/NPCSteeringSystem.cs

index e8ef3c0cb325fdb3d8f6a67e52c25711fa1a639d..0897d8ff1ef606904559df7f56a6f2dddedcbdd3 100644 (file)
@@ -98,7 +98,7 @@ public sealed partial class NPCSteeringSystem
         // TODO: Consider melee range or the likes.
         else
         {
-            arrivalDistance = SharedInteractionSystem.InteractionRange - 0.65f;
+            arrivalDistance = SharedInteractionSystem.InteractionRange - 0.05f;
         }
 
         // Check if mapids match.
@@ -126,6 +126,12 @@ public sealed partial class NPCSteeringSystem
                 // Breaking behaviours and the likes.
                 lock (_obstacles)
                 {
+                    // We're still coming to a stop so wait for the do_after.
+                    if (body.LinearVelocity.LengthSquared > 0.01f)
+                    {
+                        return true;
+                    }
+
                     status = TryHandleFlags(uid, steering, node, bodyQuery);
                 }
 
index 7ab92316480bf397c0a5672d7cfe0ce292199659..2ccdb7c6f2d3b116bf81b600047039a56cb3c21b 100644 (file)
@@ -28,453 +28,452 @@ using Robust.Shared.Threading;
 using Robust.Shared.Timing;
 using Robust.Shared.Utility;
 
-namespace Content.Server.NPC.Systems
+namespace Content.Server.NPC.Systems;
+
+public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem
 {
-    public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem
+    /*
+     * We use context steering to determine which way to move.
+     * This involves creating an array of possible directions and assigning a value for the desireability of each direction.
+     *
+     * There's multiple ways to implement this, e.g. you can average all directions, or you can choose the highest direction
+     * , or you can remove the danger map entirely and only having an interest map (AKA game endeavour).
+     * See http://www.gameaipro.com/GameAIPro2/GameAIPro2_Chapter18_Context_Steering_Behavior-Driven_Steering_at_the_Macro_Scale.pdf
+     * (though in their case it was for an F1 game so used context steering across the width of the road).
+     */
+
+    [Dependency] private readonly IAdminManager _admin = default!;
+    [Dependency] private readonly IConfigurationManager _configManager = default!;
+    [Dependency] private readonly IGameTiming _timing = default!;
+    [Dependency] private readonly IMapManager _mapManager = default!;
+    [Dependency] private readonly IParallelManager _parallel = default!;
+    [Dependency] private readonly IRobustRandom _random = default!;
+    [Dependency] private readonly DoAfterSystem _doAfter = default!;
+    [Dependency] private readonly DoorSystem _doors = default!;
+    [Dependency] private readonly EntityLookupSystem _lookup = default!;
+    [Dependency] private readonly FactionSystem _faction = default!;
+    [Dependency] private readonly PathfindingSystem _pathfindingSystem = default!;
+    [Dependency] private readonly SharedInteractionSystem _interaction = default!;
+    [Dependency] private readonly SharedMeleeWeaponSystem _melee = default!;
+    [Dependency] private readonly SharedMoverController _mover = default!;
+    [Dependency] private readonly SharedPhysicsSystem _physics = default!;
+    [Dependency] private readonly SharedTransformSystem _transform = default!;
+    [Dependency] private readonly SharedCombatModeSystem _combat = default!;
+
+    /// <summary>
+    /// Enabled antistuck detection so if an NPC is in the same spot for a while it will re-path.
+    /// </summary>
+    public bool AntiStuck = true;
+
+    private bool _enabled;
+
+    private bool _pathfinding = true;
+
+    public static readonly Vector2[] Directions = new Vector2[InterestDirections];
+
+    private readonly HashSet<ICommonSession> _subscribedSessions = new();
+
+    private object _obstacles = new();
+
+    private ISawmill _sawmill = default!;
+
+    public override void Initialize()
     {
-        /*
-         * We use context steering to determine which way to move.
-         * This involves creating an array of possible directions and assigning a value for the desireability of each direction.
-         *
-         * There's multiple ways to implement this, e.g. you can average all directions, or you can choose the highest direction
-         * , or you can remove the danger map entirely and only having an interest map (AKA game endeavour).
-         * See http://www.gameaipro.com/GameAIPro2/GameAIPro2_Chapter18_Context_Steering_Behavior-Driven_Steering_at_the_Macro_Scale.pdf
-         * (though in their case it was for an F1 game so used context steering across the width of the road).
-         */
-
-        [Dependency] private readonly IAdminManager _admin = default!;
-        [Dependency] private readonly IConfigurationManager _configManager = default!;
-        [Dependency] private readonly IGameTiming _timing = default!;
-        [Dependency] private readonly IMapManager _mapManager = default!;
-        [Dependency] private readonly IParallelManager _parallel = default!;
-        [Dependency] private readonly IRobustRandom _random = default!;
-        [Dependency] private readonly DoAfterSystem _doAfter = default!;
-        [Dependency] private readonly DoorSystem _doors = default!;
-        [Dependency] private readonly EntityLookupSystem _lookup = default!;
-        [Dependency] private readonly FactionSystem _faction = default!;
-        [Dependency] private readonly PathfindingSystem _pathfindingSystem = default!;
-        [Dependency] private readonly SharedInteractionSystem _interaction = default!;
-        [Dependency] private readonly SharedMeleeWeaponSystem _melee = default!;
-        [Dependency] private readonly SharedMoverController _mover = default!;
-        [Dependency] private readonly SharedPhysicsSystem _physics = default!;
-        [Dependency] private readonly SharedTransformSystem _transform = default!;
-        [Dependency] private readonly SharedCombatModeSystem _combat = default!;
-
-        /// <summary>
-        /// Enabled antistuck detection so if an NPC is in the same spot for a while it will re-path.
-        /// </summary>
-        public bool AntiStuck = true;
-
-        private bool _enabled;
-
-        private bool _pathfinding = true;
-
-        public static readonly Vector2[] Directions = new Vector2[InterestDirections];
-
-        private readonly HashSet<ICommonSession> _subscribedSessions = new();
-
-        private object _obstacles = new();
-
-        private ISawmill _sawmill = default!;
-
-        public override void Initialize()
-        {
-            base.Initialize();
-            _sawmill = Logger.GetSawmill("npc.steering");
+        base.Initialize();
+        _sawmill = Logger.GetSawmill("npc.steering");
 #if DEBUG
-            _sawmill.Level = LogLevel.Warning;
+        _sawmill.Level = LogLevel.Warning;
 #else
             _sawmill.Level = LogLevel.Debug;
 #endif
 
-            for (var i = 0; i < InterestDirections; i++)
-            {
-                Directions[i] = new Angle(InterestRadians * i).ToVec();
-            }
+        for (var i = 0; i < InterestDirections; i++)
+        {
+            Directions[i] = new Angle(InterestRadians * i).ToVec();
+        }
 
-            UpdatesBefore.Add(typeof(SharedPhysicsSystem));
-            _configManager.OnValueChanged(CCVars.NPCEnabled, SetNPCEnabled, true);
-            _configManager.OnValueChanged(CCVars.NPCPathfinding, SetNPCPathfinding, true);
+        UpdatesBefore.Add(typeof(SharedPhysicsSystem));
+        _configManager.OnValueChanged(CCVars.NPCEnabled, SetNPCEnabled, true);
+        _configManager.OnValueChanged(CCVars.NPCPathfinding, SetNPCPathfinding, true);
 
-            SubscribeLocalEvent<NPCSteeringComponent, ComponentShutdown>(OnSteeringShutdown);
-            SubscribeLocalEvent<NPCSteeringComponent, EntityUnpausedEvent>(OnSteeringUnpaused);
-            SubscribeNetworkEvent<RequestNPCSteeringDebugEvent>(OnDebugRequest);
-        }
+        SubscribeLocalEvent<NPCSteeringComponent, ComponentShutdown>(OnSteeringShutdown);
+        SubscribeLocalEvent<NPCSteeringComponent, EntityUnpausedEvent>(OnSteeringUnpaused);
+        SubscribeNetworkEvent<RequestNPCSteeringDebugEvent>(OnDebugRequest);
+    }
 
-        private void SetNPCEnabled(bool obj)
+    private void SetNPCEnabled(bool obj)
+    {
+        if (!obj)
         {
-            if (!obj)
+            foreach (var (comp, mover) in EntityQuery<NPCSteeringComponent, InputMoverComponent>())
             {
-                foreach (var (comp, mover) in EntityQuery<NPCSteeringComponent, InputMoverComponent>())
-                {
-                    mover.CurTickSprintMovement = Vector2.Zero;
-                    comp.PathfindToken?.Cancel();
-                    comp.PathfindToken = null;
-                }
+                mover.CurTickSprintMovement = Vector2.Zero;
+                comp.PathfindToken?.Cancel();
+                comp.PathfindToken = null;
             }
-
-            _enabled = obj;
         }
 
-        private void SetNPCPathfinding(bool value)
-        {
-            _pathfinding = value;
+        _enabled = obj;
+    }
 
-            if (!_pathfinding)
+    private void SetNPCPathfinding(bool value)
+    {
+        _pathfinding = value;
+
+        if (!_pathfinding)
+        {
+            foreach (var comp in EntityQuery<NPCSteeringComponent>(true))
             {
-                foreach (var comp in EntityQuery<NPCSteeringComponent>(true))
-                {
-                    comp.PathfindToken?.Cancel();
-                    comp.PathfindToken = null;
-                }
+                comp.PathfindToken?.Cancel();
+                comp.PathfindToken = null;
             }
         }
+    }
 
-        public override void Shutdown()
-        {
-            base.Shutdown();
-            _configManager.UnsubValueChanged(CCVars.NPCEnabled, SetNPCEnabled);
-            _configManager.UnsubValueChanged(CCVars.NPCPathfinding, SetNPCPathfinding);
-        }
+    public override void Shutdown()
+    {
+        base.Shutdown();
+        _configManager.UnsubValueChanged(CCVars.NPCEnabled, SetNPCEnabled);
+        _configManager.UnsubValueChanged(CCVars.NPCPathfinding, SetNPCPathfinding);
+    }
 
-        private void OnDebugRequest(RequestNPCSteeringDebugEvent msg, EntitySessionEventArgs args)
-        {
-            if (!_admin.IsAdmin((IPlayerSession) args.SenderSession))
-                return;
+    private void OnDebugRequest(RequestNPCSteeringDebugEvent msg, EntitySessionEventArgs args)
+    {
+        if (!_admin.IsAdmin((IPlayerSession) args.SenderSession))
+            return;
 
-            if (msg.Enabled)
-                _subscribedSessions.Add(args.SenderSession);
-            else
-                _subscribedSessions.Remove(args.SenderSession);
-        }
+        if (msg.Enabled)
+            _subscribedSessions.Add(args.SenderSession);
+        else
+            _subscribedSessions.Remove(args.SenderSession);
+    }
 
-        private void OnSteeringShutdown(EntityUid uid, NPCSteeringComponent component, ComponentShutdown args)
+    private void OnSteeringShutdown(EntityUid uid, NPCSteeringComponent component, ComponentShutdown args)
+    {
+        // Cancel any active pathfinding jobs as they're irrelevant.
+        component.PathfindToken?.Cancel();
+        component.PathfindToken = null;
+    }
+
+    private void OnSteeringUnpaused(EntityUid uid, NPCSteeringComponent component, ref EntityUnpausedEvent args)
+    {
+        component.LastStuckTime += args.PausedTime;
+        component.NextSteer += args.PausedTime;
+    }
+
+    /// <summary>
+    /// Adds the AI to the steering system to move towards a specific target
+    /// </summary>
+    public NPCSteeringComponent Register(EntityUid uid, EntityCoordinates coordinates, NPCSteeringComponent? component = null)
+    {
+        if (Resolve(uid, ref component, false))
         {
-            // Cancel any active pathfinding jobs as they're irrelevant.
             component.PathfindToken?.Cancel();
             component.PathfindToken = null;
+            component.CurrentPath.Clear();
         }
-
-        private void OnSteeringUnpaused(EntityUid uid, NPCSteeringComponent component, ref EntityUnpausedEvent args)
+        else
         {
-            component.LastStuckTime += args.PausedTime;
-            component.NextSteer += args.PausedTime;
+            component = AddComp<NPCSteeringComponent>(uid);
+            component.Flags = _pathfindingSystem.GetFlags(uid);
         }
 
-        /// <summary>
-        /// Adds the AI to the steering system to move towards a specific target
-        /// </summary>
-        public NPCSteeringComponent Register(EntityUid uid, EntityCoordinates coordinates, NPCSteeringComponent? component = null)
-        {
-            if (Resolve(uid, ref component, false))
-            {
-                component.PathfindToken?.Cancel();
-                component.PathfindToken = null;
-                component.CurrentPath.Clear();
-            }
-            else
-            {
-                component = AddComp<NPCSteeringComponent>(uid);
-                component.Flags = _pathfindingSystem.GetFlags(uid);
-            }
-
-            ResetStuck(component, Transform(uid).Coordinates);
-            component.Coordinates = coordinates;
-            return component;
-        }
+        ResetStuck(component, Transform(uid).Coordinates);
+        component.Coordinates = coordinates;
+        return component;
+    }
 
-        /// <summary>
-        /// Attempts to register the entity. Does nothing if the coordinates already registered.
-        /// </summary>
-        public bool TryRegister(EntityUid uid, EntityCoordinates coordinates, NPCSteeringComponent? component = null)
+    /// <summary>
+    /// Attempts to register the entity. Does nothing if the coordinates already registered.
+    /// </summary>
+    public bool TryRegister(EntityUid uid, EntityCoordinates coordinates, NPCSteeringComponent? component = null)
+    {
+        if (Resolve(uid, ref component, false) && component.Coordinates.Equals(coordinates))
         {
-            if (Resolve(uid, ref component, false) && component.Coordinates.Equals(coordinates))
-            {
-                return false;
-            }
-
-            Register(uid, coordinates, component);
-            return true;
+            return false;
         }
 
-        /// <summary>
-        /// Stops the steering behavior for the AI and cleans up.
-        /// </summary>
-        public void Unregister(EntityUid uid, NPCSteeringComponent? component = null)
-        {
-            if (!Resolve(uid, ref component, false))
-                return;
+        Register(uid, coordinates, component);
+        return true;
+    }
 
-            if (EntityManager.TryGetComponent(uid, out InputMoverComponent? controller))
-            {
-                controller.CurTickSprintMovement = Vector2.Zero;
-            }
+    /// <summary>
+    /// Stops the steering behavior for the AI and cleans up.
+    /// </summary>
+    public void Unregister(EntityUid uid, NPCSteeringComponent? component = null)
+    {
+        if (!Resolve(uid, ref component, false))
+            return;
 
-            component.PathfindToken?.Cancel();
-            component.PathfindToken = null;
-            RemComp<NPCSteeringComponent>(uid);
+        if (EntityManager.TryGetComponent(uid, out InputMoverComponent? controller))
+        {
+            controller.CurTickSprintMovement = Vector2.Zero;
         }
 
-        public override void Update(float frameTime)
-        {
-            base.Update(frameTime);
+        component.PathfindToken?.Cancel();
+        component.PathfindToken = null;
+        RemComp<NPCSteeringComponent>(uid);
+    }
 
-            if (!_enabled)
-                return;
+    public override void Update(float frameTime)
+    {
+        base.Update(frameTime);
 
-            // Not every mob has the modifier component so do it as a separate query.
-            var bodyQuery = GetEntityQuery<PhysicsComponent>();
-            var modifierQuery = GetEntityQuery<MovementSpeedModifierComponent>();
-            var xformQuery = GetEntityQuery<TransformComponent>();
+        if (!_enabled)
+            return;
 
-            var npcs = EntityQuery<ActiveNPCComponent, NPCSteeringComponent, InputMoverComponent, TransformComponent>()
-                .Select(o => (o.Item1.Owner, o.Item2, o.Item3, o.Item4)).ToArray();
+        // Not every mob has the modifier component so do it as a separate query.
+        var bodyQuery = GetEntityQuery<PhysicsComponent>();
+        var modifierQuery = GetEntityQuery<MovementSpeedModifierComponent>();
+        var xformQuery = GetEntityQuery<TransformComponent>();
 
-            // Dependency issues across threads.
-            var options = new ParallelOptions
-            {
-                MaxDegreeOfParallelism = 1,
-            };
-            var curTime = _timing.CurTime;
+        var npcs = EntityQuery<ActiveNPCComponent, NPCSteeringComponent, InputMoverComponent, TransformComponent>()
+            .Select(o => (o.Item1.Owner, o.Item2, o.Item3, o.Item4)).ToArray();
 
-            Parallel.For(0, npcs.Length, options, i =>
-            {
-                var (uid, steering, mover, xform) = npcs[i];
-                Steer(uid, steering, mover, xform, modifierQuery, bodyQuery, xformQuery, frameTime, curTime);
-            });
+        // Dependency issues across threads.
+        var options = new ParallelOptions
+        {
+            MaxDegreeOfParallelism = 1,
+        };
+        var curTime = _timing.CurTime;
 
+        Parallel.For(0, npcs.Length, options, i =>
+        {
+            var (uid, steering, mover, xform) = npcs[i];
+            Steer(uid, steering, mover, xform, modifierQuery, bodyQuery, xformQuery, frameTime, curTime);
+        });
 
-            if (_subscribedSessions.Count > 0)
-            {
-                var data = new List<NPCSteeringDebugData>(npcs.Length);
-
-                foreach (var (uid, steering, mover, _) in npcs)
-                {
-                    data.Add(new NPCSteeringDebugData(
-                        uid,
-                        mover.CurTickSprintMovement,
-                        steering.Interest,
-                        steering.Danger,
-                        steering.DangerPoints));
-                }
-
-                var filter = Filter.Empty();
-                filter.AddPlayers(_subscribedSessions);
-
-                RaiseNetworkEvent(new NPCSteeringDebugEvent(data), filter);
-            }
-        }
 
-        private void SetDirection(InputMoverComponent component, NPCSteeringComponent steering, Vector2 value, bool clear = true)
+        if (_subscribedSessions.Count > 0)
         {
-            if (clear && value.Equals(Vector2.Zero))
+            var data = new List<NPCSteeringDebugData>(npcs.Length);
+
+            foreach (var (uid, steering, mover, _) in npcs)
             {
-                steering.CurrentPath.Clear();
+                data.Add(new NPCSteeringDebugData(
+                    uid,
+                    mover.CurTickSprintMovement,
+                    steering.Interest,
+                    steering.Danger,
+                    steering.DangerPoints));
             }
 
-            component.CurTickSprintMovement = value;
-            component.LastInputTick = _timing.CurTick;
-            component.LastInputSubTick = ushort.MaxValue;
+            var filter = Filter.Empty();
+            filter.AddPlayers(_subscribedSessions);
+
+            RaiseNetworkEvent(new NPCSteeringDebugEvent(data), filter);
         }
+    }
 
-        /// <summary>
-        /// Go through each steerer and combine their vectors
-        /// </summary>
-        private void Steer(
-            EntityUid uid,
-            NPCSteeringComponent steering,
-            InputMoverComponent mover,
-            TransformComponent xform,
-            EntityQuery<MovementSpeedModifierComponent> modifierQuery,
-            EntityQuery<PhysicsComponent> bodyQuery,
-            EntityQuery<TransformComponent> xformQuery,
-            float frameTime,
-            TimeSpan curTime)
+    private void SetDirection(InputMoverComponent component, NPCSteeringComponent steering, Vector2 value, bool clear = true)
+    {
+        if (clear && value.Equals(Vector2.Zero))
         {
-            if (Deleted(steering.Coordinates.EntityId))
-            {
-                SetDirection(mover, steering, Vector2.Zero);
-                steering.Status = SteeringStatus.NoPath;
-                return;
-            }
+            steering.CurrentPath.Clear();
+        }
 
-            // No path set from pathfinding or the likes.
-            if (steering.Status == SteeringStatus.NoPath)
-            {
-                SetDirection(mover, steering, Vector2.Zero);
-                return;
-            }
+        component.CurTickSprintMovement = value;
+        component.LastInputTick = _timing.CurTick;
+        component.LastInputSubTick = ushort.MaxValue;
+    }
 
-            // Can't move at all, just noop input.
-            if (!mover.CanMove)
-            {
-                SetDirection(mover, steering, Vector2.Zero);
-                steering.Status = SteeringStatus.NoPath;
-                return;
-            }
+    /// <summary>
+    /// Go through each steerer and combine their vectors
+    /// </summary>
+    private void Steer(
+        EntityUid uid,
+        NPCSteeringComponent steering,
+        InputMoverComponent mover,
+        TransformComponent xform,
+        EntityQuery<MovementSpeedModifierComponent> modifierQuery,
+        EntityQuery<PhysicsComponent> bodyQuery,
+        EntityQuery<TransformComponent> xformQuery,
+        float frameTime,
+        TimeSpan curTime)
+    {
+        if (Deleted(steering.Coordinates.EntityId))
+        {
+            SetDirection(mover, steering, Vector2.Zero);
+            steering.Status = SteeringStatus.NoPath;
+            return;
+        }
 
-            var interest = steering.Interest;
-            var danger = steering.Danger;
-            var agentRadius = steering.Radius;
-            var worldPos = _transform.GetWorldPosition(xform, xformQuery);
-            var (layer, mask) = _physics.GetHardCollision(uid);
-
-            // Use rotation relative to parent to rotate our context vectors by.
-            var offsetRot = -_mover.GetParentGridAngle(mover);
-            modifierQuery.TryGetComponent(uid, out var modifier);
-            var moveSpeed = GetSprintSpeed(uid, modifier);
-            var body = bodyQuery.GetComponent(uid);
-            var dangerPoints = steering.DangerPoints;
-            dangerPoints.Clear();
-
-            for (var i = 0; i < InterestDirections; i++)
-            {
-                steering.Interest[i] = 0f;
-                steering.Danger[i] = 0f;
-            }
+        // No path set from pathfinding or the likes.
+        if (steering.Status == SteeringStatus.NoPath)
+        {
+            SetDirection(mover, steering, Vector2.Zero);
+            return;
+        }
 
-            var ev = new NPCSteeringEvent(steering, interest, danger, agentRadius, offsetRot, worldPos);
-            RaiseLocalEvent(uid, ref ev);
-            // If seek has arrived at the target node for example then immediately re-steer.
-            var forceSteer = true;
+        // Can't move at all, just noop input.
+        if (!mover.CanMove)
+        {
+            SetDirection(mover, steering, Vector2.Zero);
+            steering.Status = SteeringStatus.NoPath;
+            return;
+        }
 
-            if (steering.CanSeek && !TrySeek(uid, mover, steering, body, xform, offsetRot, moveSpeed, interest, bodyQuery, frameTime, ref forceSteer))
-            {
-                SetDirection(mover, steering, Vector2.Zero);
-                return;
-            }
-            DebugTools.Assert(!float.IsNaN(interest[0]));
+        var interest = steering.Interest;
+        var danger = steering.Danger;
+        var agentRadius = steering.Radius;
+        var worldPos = _transform.GetWorldPosition(xform, xformQuery);
+        var (layer, mask) = _physics.GetHardCollision(uid);
+
+        // Use rotation relative to parent to rotate our context vectors by.
+        var offsetRot = -_mover.GetParentGridAngle(mover);
+        modifierQuery.TryGetComponent(uid, out var modifier);
+        var moveSpeed = GetSprintSpeed(uid, modifier);
+        var body = bodyQuery.GetComponent(uid);
+        var dangerPoints = steering.DangerPoints;
+        dangerPoints.Clear();
+
+        for (var i = 0; i < InterestDirections; i++)
+        {
+            steering.Interest[i] = 0f;
+            steering.Danger[i] = 0f;
+        }
 
-            // Avoid static objects like walls
-            CollisionAvoidance(uid, offsetRot, worldPos, agentRadius, layer, mask, xform, danger, dangerPoints, bodyQuery, xformQuery);
-            DebugTools.Assert(!float.IsNaN(danger[0]));
+        var ev = new NPCSteeringEvent(steering, interest, danger, agentRadius, offsetRot, worldPos);
+        RaiseLocalEvent(uid, ref ev);
+        // If seek has arrived at the target node for example then immediately re-steer.
+        var forceSteer = true;
 
-            Separation(uid, offsetRot, worldPos, agentRadius, layer, mask, body, xform, danger, bodyQuery, xformQuery);
+        if (steering.CanSeek && !TrySeek(uid, mover, steering, body, xform, offsetRot, moveSpeed, interest, bodyQuery, frameTime, ref forceSteer))
+        {
+            SetDirection(mover, steering, Vector2.Zero);
+            return;
+        }
+        DebugTools.Assert(!float.IsNaN(interest[0]));
 
-            // Remove the danger map from the interest map.
-            var desiredDirection = -1;
-            var desiredValue = 0f;
+        // Avoid static objects like walls
+        CollisionAvoidance(uid, offsetRot, worldPos, agentRadius, layer, mask, xform, danger, dangerPoints, bodyQuery, xformQuery);
+        DebugTools.Assert(!float.IsNaN(danger[0]));
 
-            for (var i = 0; i < InterestDirections; i++)
-            {
-                var adjustedValue = Math.Clamp(interest[i] - danger[i], 0f, 1f);
+        Separation(uid, offsetRot, worldPos, agentRadius, layer, mask, body, xform, danger, bodyQuery, xformQuery);
 
-                if (adjustedValue > desiredValue)
-                {
-                    desiredDirection = i;
-                    desiredValue = adjustedValue;
-                }
-            }
+        // Remove the danger map from the interest map.
+        var desiredDirection = -1;
+        var desiredValue = 0f;
 
-            var resultDirection = Vector2.Zero;
+        for (var i = 0; i < InterestDirections; i++)
+        {
+            var adjustedValue = Math.Clamp(interest[i] - danger[i], 0f, 1f);
 
-            if (desiredDirection != -1)
+            if (adjustedValue > desiredValue)
             {
-                resultDirection = new Angle(desiredDirection * InterestRadians).ToVec();
+                desiredDirection = i;
+                desiredValue = adjustedValue;
             }
+        }
 
-            // Don't steer too frequently to avoid twitchiness.
-            // This should also implicitly solve tie situations.
-            // I think doing this after all the ops above is best?
-            // Originally I had it way above but sometimes mobs would overshoot their tile targets.
-
-            if (!forceSteer && steering.NextSteer > curTime)
-            {
-                SetDirection(mover, steering, steering.LastSteerDirection, false);
-                return;
-            }
+        var resultDirection = Vector2.Zero;
 
-            steering.NextSteer = curTime + TimeSpan.FromSeconds(1f / NPCSteeringComponent.SteeringFrequency);
-            steering.LastSteerDirection = resultDirection;
-            DebugTools.Assert(!float.IsNaN(resultDirection.X));
-            SetDirection(mover, steering, resultDirection, false);
+        if (desiredDirection != -1)
+        {
+            resultDirection = new Angle(desiredDirection * InterestRadians).ToVec();
         }
 
-        private EntityCoordinates GetCoordinates(PathPoly poly)
-        {
-            if (!poly.IsValid())
-                return EntityCoordinates.Invalid;
+        // Don't steer too frequently to avoid twitchiness.
+        // This should also implicitly solve tie situations.
+        // I think doing this after all the ops above is best?
+        // Originally I had it way above but sometimes mobs would overshoot their tile targets.
 
-            return new EntityCoordinates(poly.GraphUid, poly.Box.Center);
+        if (!forceSteer && steering.NextSteer > curTime)
+        {
+            SetDirection(mover, steering, steering.LastSteerDirection, false);
+            return;
         }
 
-        /// <summary>
-        /// Get a new job from the pathfindingsystem
-        /// </summary>
-        private async void RequestPath(EntityUid uid, NPCSteeringComponent steering, TransformComponent xform, float targetDistance)
-        {
-            // If we already have a pathfinding request then don't grab another.
-            // If we're in range then just beeline them; this can avoid stutter stepping and is an easy way to look nicer.
-            if (steering.Pathfind || targetDistance < steering.RepathRange)
-                return;
-
-            // Short-circuit with no path.
-            var targetPoly = _pathfindingSystem.GetPoly(steering.Coordinates);
-
-            // If this still causes issues future sloth adjust the collision mask.
-            // Thanks past sloth I already realised.
-            if (targetPoly != null &&
-                steering.Coordinates.Position.Equals(Vector2.Zero) &&
-                TryComp<PhysicsComponent>(uid, out var physics) &&
-                _interaction.InRangeUnobstructed(uid, steering.Coordinates.EntityId, range: 30f, (CollisionGroup) physics.CollisionMask))
-            {
-                steering.CurrentPath.Clear();
-                // Enqueue our poly as it will be pruned later.
-                var ourPoly = _pathfindingSystem.GetPoly(xform.Coordinates);
+        steering.NextSteer = curTime + TimeSpan.FromSeconds(1f / NPCSteeringComponent.SteeringFrequency);
+        steering.LastSteerDirection = resultDirection;
+        DebugTools.Assert(!float.IsNaN(resultDirection.X));
+        SetDirection(mover, steering, resultDirection, false);
+    }
+
+    private EntityCoordinates GetCoordinates(PathPoly poly)
+    {
+        if (!poly.IsValid())
+            return EntityCoordinates.Invalid;
 
-                if (ourPoly != null)
-                {
-                    steering.CurrentPath.Enqueue(ourPoly);
-                }
+        return new EntityCoordinates(poly.GraphUid, poly.Box.Center);
+    }
+
+    /// <summary>
+    /// Get a new job from the pathfindingsystem
+    /// </summary>
+    private async void RequestPath(EntityUid uid, NPCSteeringComponent steering, TransformComponent xform, float targetDistance)
+    {
+        // If we already have a pathfinding request then don't grab another.
+        // If we're in range then just beeline them; this can avoid stutter stepping and is an easy way to look nicer.
+        if (steering.Pathfind || targetDistance < steering.RepathRange)
+            return;
+
+        // Short-circuit with no path.
+        var targetPoly = _pathfindingSystem.GetPoly(steering.Coordinates);
+
+        // If this still causes issues future sloth adjust the collision mask.
+        // Thanks past sloth I already realised.
+        if (targetPoly != null &&
+            steering.Coordinates.Position.Equals(Vector2.Zero) &&
+            TryComp<PhysicsComponent>(uid, out var physics) &&
+            _interaction.InRangeUnobstructed(uid, steering.Coordinates.EntityId, range: 30f, (CollisionGroup) physics.CollisionMask))
+        {
+            steering.CurrentPath.Clear();
+            // Enqueue our poly as it will be pruned later.
+            var ourPoly = _pathfindingSystem.GetPoly(xform.Coordinates);
 
-                steering.CurrentPath.Enqueue(targetPoly);
-                return;
+            if (ourPoly != null)
+            {
+                steering.CurrentPath.Enqueue(ourPoly);
             }
 
-            steering.PathfindToken = new CancellationTokenSource();
+            steering.CurrentPath.Enqueue(targetPoly);
+            return;
+        }
 
-            var flags = _pathfindingSystem.GetFlags(uid);
+        steering.PathfindToken = new CancellationTokenSource();
 
-            var result = await _pathfindingSystem.GetPathSafe(
-                uid,
-                xform.Coordinates,
-                steering.Coordinates,
-                steering.Range,
-                steering.PathfindToken.Token,
-                flags);
+        var flags = _pathfindingSystem.GetFlags(uid);
 
-            steering.PathfindToken = null;
+        var result = await _pathfindingSystem.GetPathSafe(
+            uid,
+            xform.Coordinates,
+            steering.Coordinates,
+            steering.Range,
+            steering.PathfindToken.Token,
+            flags);
 
-            if (result.Result == PathResult.NoPath)
-            {
-                steering.CurrentPath.Clear();
-                steering.FailedPathCount++;
+        steering.PathfindToken = null;
 
-                if (steering.FailedPathCount >= NPCSteeringComponent.FailedPathLimit)
-                {
-                    steering.Status = SteeringStatus.NoPath;
-                }
+        if (result.Result == PathResult.NoPath)
+        {
+            steering.CurrentPath.Clear();
+            steering.FailedPathCount++;
 
-                return;
+            if (steering.FailedPathCount >= NPCSteeringComponent.FailedPathLimit)
+            {
+                steering.Status = SteeringStatus.NoPath;
             }
 
-            var targetPos = steering.Coordinates.ToMap(EntityManager, _transform);
-            var ourPos = xform.MapPosition;
-
-            PrunePath(uid, ourPos, targetPos.Position - ourPos.Position, result.Path);
-            steering.CurrentPath = result.Path;
+            return;
         }
 
-        // TODO: Move these to movercontroller
+        var targetPos = steering.Coordinates.ToMap(EntityManager, _transform);
+        var ourPos = xform.MapPosition;
 
-        private float GetSprintSpeed(EntityUid uid, MovementSpeedModifierComponent? modifier = null)
-        {
-            if (!Resolve(uid, ref modifier, false))
-            {
-                return MovementSpeedModifierComponent.DefaultBaseSprintSpeed;
-            }
+        PrunePath(uid, ourPos, targetPos.Position - ourPos.Position, result.Path);
+        steering.CurrentPath = result.Path;
+    }
+
+    // TODO: Move these to movercontroller
 
-            return modifier.CurrentSprintSpeed;
+    private float GetSprintSpeed(EntityUid uid, MovementSpeedModifierComponent? modifier = null)
+    {
+        if (!Resolve(uid, ref modifier, false))
+        {
+            return MovementSpeedModifierComponent.DefaultBaseSprintSpeed;
         }
+
+        return modifier.CurrentSprintSpeed;
     }
 }