[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();
[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>
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;
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;
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);
}
}
TransformComponent xform,
Angle offsetRot,
float moveSpeed,
- float[] interest,
+ Span<float> interest,
float frameTime,
ref bool forceSteer)
{
}
// 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;
}
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)
{
if (!_physicsQuery.TryGetComponent(ent, out var otherBody) ||
!otherBody.Hard ||
!otherBody.CanCollide ||
+ otherBody.BodyType == BodyType.KinematicController ||
(mask & otherBody.CollisionLayer) == 0x0 &&
(layer & otherBody.CollisionMask) == 0x0)
{
}
else
{
- weight = distance / detectionRadius;
+ weight = (detectionRadius - distance) / detectionRadius;
}
if (obstacleDirection == Vector2.Zero)
int mask,
PhysicsComponent body,
TransformComponent xform,
- float[] danger)
+ Span<float> danger)
{
var objectRadius = 0.25f;
var detectionRadius = MathF.Max(0.35f, agentRadius + objectRadius);
// 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;
+ }
+ }
}
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;
using Robust.Shared.Utility;
using Content.Shared.Prying.Systems;
using Microsoft.Extensions.ObjectPool;
-using Robust.Shared.Threading;
namespace Content.Server.NPC.Systems;
return;
}
- var interest = steering.Interest;
- var danger = steering.Danger;
var agentRadius = steering.Radius;
var worldPos = _transform.GetWorldPosition(xform);
var (layer, mask) = _physics.GetHardCollision(uid);
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);
SetDirection(mover, steering, Vector2.Zero);
return;
}
+
DebugTools.Assert(!float.IsNaN(interest[0]));
// Don't steer too frequently to avoid twitchiness.
// 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;
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;
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)
{
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);
}