]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
NPC steering blending (#25666)
authormetalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Wed, 28 Feb 2024 06:41:15 +0000 (17:41 +1100)
committerGitHub <noreply@github.com>
Wed, 28 Feb 2024 06:41:15 +0000 (17:41 +1100)
* NPC steering blending

Significantly more stable than using LastSteerDirection and also AntiStuck never got tripped locally when I was running around. I also left future notes for me to cleanup the pathfinder in future.

* Remove index

Content.Server/NPC/Components/NPCSteeringComponent.cs
Content.Server/NPC/Systems/NPCSteeringSystem.Context.cs
Content.Server/NPC/Systems/NPCSteeringSystem.cs

index 397b7fb032aa275d49d4812062f0b4e1c23c58e6..46fb968be5a5535edb491f34bbab8f357ca27991 100644 (file)
@@ -28,11 +28,11 @@ public sealed partial class NPCSteeringComponent : Component
     [ViewVariables(VVAccess.ReadWrite)]
     public float Radius = 0.35f;
 
-    [ViewVariables]
-    public readonly float[] Interest = new float[SharedNPCSteeringSystem.InterestDirections];
+    [ViewVariables, DataField]
+    public float[] Interest = new float[SharedNPCSteeringSystem.InterestDirections];
 
-    [ViewVariables]
-    public readonly float[] Danger = new float[SharedNPCSteeringSystem.InterestDirections];
+    [ViewVariables, DataField]
+    public float[] Danger = new float[SharedNPCSteeringSystem.InterestDirections];
 
     // TODO: Update radius, also danger points debug only
     public readonly List<Vector2> DangerPoints = new();
@@ -45,21 +45,9 @@ public sealed partial class NPCSteeringComponent : Component
     [DataField("forceMove")]
     public bool ForceMove = false;
 
-    /// <summary>
-    /// Next time we can change our steering direction.
-    /// </summary>
-    [DataField("nextSteer", customTypeSerializer:typeof(TimeOffsetSerializer))]
-    [AutoPausedField]
-    public TimeSpan NextSteer = TimeSpan.Zero;
-
-    [DataField("lastSteerIndex")]
-    public int LastSteerIndex = -1;
-
     [DataField("lastSteerDirection")]
     public Vector2 LastSteerDirection = Vector2.Zero;
 
-    public const int SteeringFrequency = 5;
-
     /// <summary>
     /// Last position we considered for being stuck.
     /// </summary>
index 89e3e64e9fe7dac8930af7bfee244de6efa2b145..7ac6768e35923b22f12679c1b23a2a25f4e7141a 100644 (file)
@@ -9,6 +9,7 @@ using Content.Shared.Movement.Components;
 using Content.Shared.NPC;
 using Content.Shared.Physics;
 using Robust.Shared.Map;
+using Robust.Shared.Physics;
 using Robust.Shared.Physics.Components;
 using ClimbingComponent = Content.Shared.Climbing.Components.ClimbingComponent;
 
@@ -16,7 +17,7 @@ namespace Content.Server.NPC.Systems;
 
 public sealed partial class NPCSteeringSystem
 {
-    private void ApplySeek(float[] interest, Vector2 direction, float weight)
+    private void ApplySeek(Span<float> interest, Vector2 direction, float weight)
     {
         if (weight == 0f || direction == Vector2.Zero)
             return;
@@ -25,13 +26,10 @@ public sealed partial class NPCSteeringSystem
 
         for (var i = 0; i < InterestDirections; i++)
         {
-            if (interest[i].Equals(-1f))
-                continue;
-
             var angle = i * InterestRadians;
             var dot = MathF.Cos(directionAngle - angle);
-            dot = (dot + 1) * 0.5f;
-            interest[i] += dot * weight;
+            dot = (dot + 1f) * 0.5f;
+            interest[i] = Math.Clamp(interest[i] + dot * weight, 0f, 1f);
         }
     }
 
@@ -72,7 +70,7 @@ public sealed partial class NPCSteeringSystem
         TransformComponent xform,
         Angle offsetRot,
         float moveSpeed,
-        float[] interest,
+        Span<float> interest,
         float frameTime,
         ref bool forceSteer)
     {
@@ -274,7 +272,8 @@ public sealed partial class NPCSteeringSystem
         }
 
         // If not in LOS and no path then get a new one fam.
-        if (!inLos && steering.CurrentPath.Count == 0)
+        if ((!inLos && steering.ArriveOnLineOfSight && steering.CurrentPath.Count == 0) ||
+            (!steering.ArriveOnLineOfSight && steering.CurrentPath.Count == 0))
         {
             needsPath = true;
         }
@@ -465,12 +464,12 @@ public sealed partial class NPCSteeringSystem
         int layer,
         int mask,
         TransformComponent xform,
-        float[] danger)
+        Span<float> danger)
     {
-        var objectRadius = 0.15f;
+        var objectRadius = 0.25f;
         var detectionRadius = MathF.Max(0.35f, agentRadius + objectRadius);
         var ents = _entSetPool.Get();
-        _lookup.GetEntitiesInRange(uid, detectionRadius, ents, LookupFlags.Static);
+        _lookup.GetEntitiesInRange(uid, detectionRadius, ents, LookupFlags.Dynamic | LookupFlags.Static);
 
         foreach (var ent in ents)
         {
@@ -478,6 +477,7 @@ public sealed partial class NPCSteeringSystem
             if (!_physicsQuery.TryGetComponent(ent, out var otherBody) ||
                 !otherBody.Hard ||
                 !otherBody.CanCollide ||
+                otherBody.BodyType == BodyType.KinematicController ||
                 (mask & otherBody.CollisionLayer) == 0x0 &&
                 (layer & otherBody.CollisionMask) == 0x0)
             {
@@ -506,7 +506,7 @@ public sealed partial class NPCSteeringSystem
             }
             else
             {
-                weight = distance / detectionRadius;
+                weight = (detectionRadius - distance) / detectionRadius;
             }
 
             if (obstacleDirection == Vector2.Zero)
@@ -541,7 +541,7 @@ public sealed partial class NPCSteeringSystem
         int mask,
         PhysicsComponent body,
         TransformComponent xform,
-        float[] danger)
+        Span<float> danger)
     {
         var objectRadius = 0.25f;
         var detectionRadius = MathF.Max(0.35f, agentRadius + objectRadius);
@@ -614,4 +614,35 @@ public sealed partial class NPCSteeringSystem
     // TODO: Alignment
 
     // TODO: Cohesion
+    private void Blend(NPCSteeringComponent steering, float frameTime, Span<float> interest, Span<float> danger)
+    {
+        /*
+         * Future sloth notes:
+         * Pathfinder cleanup:
+            - Cleanup whatever the fuck is happening in pathfinder
+            - Use Flee for melee behavior / actions and get the seek direction from that rather than bulldozing
+            - Must always have a path
+            - Path should return the full version + the snipped version
+            - Pathfinder needs to do diagonals
+            - Next node is either <current node + 1> or <nearest node + 1> (on the full path)
+            - If greater than <1.5m distance> repath
+         */
+
+        // IDK why I didn't do this sooner but blending is a lot better than lastdir for fixing stuttering.
+        const float BlendWeight = 10f;
+        var blendValue = Math.Min(1f, frameTime * BlendWeight);
+
+        for (var i = 0; i < InterestDirections; i++)
+        {
+            var currentInterest = interest[i];
+            var lastInterest = steering.Interest[i];
+            var interestDiff = (currentInterest - lastInterest) * blendValue;
+            steering.Interest[i] = lastInterest + interestDiff;
+
+            var currentDanger = danger[i];
+            var lastDanger = steering.Danger[i];
+            var dangerDiff = (currentDanger - lastDanger) * blendValue;
+            steering.Danger[i] = lastDanger + dangerDiff;
+        }
+    }
 }
index aca2411d8a08213a4773e1c3df0c03e755408382..c00375d6488e9d7dbba1a48b634e2ccba6feda12 100644 (file)
@@ -3,7 +3,6 @@ using System.Threading;
 using System.Threading.Tasks;
 using Content.Server.Administration.Managers;
 using Content.Server.DoAfter;
-using Content.Server.Doors.Systems;
 using Content.Server.NPC.Components;
 using Content.Server.NPC.Events;
 using Content.Server.NPC.Pathfinding;
@@ -28,7 +27,6 @@ using Robust.Shared.Timing;
 using Robust.Shared.Utility;
 using Content.Shared.Prying.Systems;
 using Microsoft.Extensions.ObjectPool;
-using Robust.Shared.Threading;
 
 namespace Content.Server.NPC.Systems;
 
@@ -315,8 +313,6 @@ public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem
             return;
         }
 
-        var interest = steering.Interest;
-        var danger = steering.Danger;
         var agentRadius = steering.Radius;
         var worldPos = _transform.GetWorldPosition(xform);
         var (layer, mask) = _physics.GetHardCollision(uid);
@@ -328,13 +324,10 @@ public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem
         var body = _physicsQuery.GetComponent(uid);
         var dangerPoints = steering.DangerPoints;
         dangerPoints.Clear();
+        Span<float> interest = stackalloc float[InterestDirections];
+        Span<float> danger = stackalloc float[InterestDirections];
 
-        for (var i = 0; i < InterestDirections; i++)
-        {
-            steering.Interest[i] = 0f;
-            steering.Danger[i] = 0f;
-        }
-
+        // TODO: This should be fly
         steering.CanSeek = true;
 
         var ev = new NPCSteeringEvent(steering, xform, worldPos, offsetRot);
@@ -347,6 +340,7 @@ public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem
             SetDirection(mover, steering, Vector2.Zero);
             return;
         }
+
         DebugTools.Assert(!float.IsNaN(interest[0]));
 
         // Don't steer too frequently to avoid twitchiness.
@@ -354,7 +348,7 @@ public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem
         // 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)
+        if (!forceSteer)
         {
             SetDirection(mover, steering, steering.LastSteerDirection, false);
             return;
@@ -366,11 +360,8 @@ public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem
 
         Separation(uid, offsetRot, worldPos, agentRadius, layer, mask, body, xform, danger);
 
-        // Prioritise whichever direction we went last tick if it's a tie-breaker.
-        if (steering.LastSteerIndex != -1)
-        {
-            interest[steering.LastSteerIndex] *= 1.1f;
-        }
+        // Blend last and current tick
+        Blend(steering, frameTime, interest, danger);
 
         // Remove the danger map from the interest map.
         var desiredDirection = -1;
@@ -378,7 +369,7 @@ public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem
 
         for (var i = 0; i < InterestDirections; i++)
         {
-            var adjustedValue = Math.Clamp(interest[i] - danger[i], 0f, 1f);
+            var adjustedValue = Math.Clamp(steering.Interest[i] - steering.Danger[i], 0f, 1f);
 
             if (adjustedValue > desiredValue)
             {
@@ -394,9 +385,7 @@ public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem
             resultDirection = new Angle(desiredDirection * InterestRadians).ToVec();
         }
 
-        steering.NextSteer = curTime + TimeSpan.FromSeconds(1f / NPCSteeringComponent.SteeringFrequency);
         steering.LastSteerDirection = resultDirection;
-        steering.LastSteerIndex = desiredDirection;
         DebugTools.Assert(!float.IsNaN(resultDirection.X));
         SetDirection(mover, steering, resultDirection, false);
     }