using System.Numerics; using System.Threading; using Content.Server.NPC.Pathfinding; using Content.Shared.DoAfter; using Content.Shared.NPC; using Robust.Shared.Map; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; namespace Content.Server.NPC.Components; /// /// Added to NPCs that are moving. /// [RegisterComponent, AutoGenerateComponentPause] public sealed partial class NPCSteeringComponent : Component { #region Context Steering /// /// Used to override seeking behavior for context steering. /// [ViewVariables] public bool CanSeek = true; /// /// Radius for collision avoidance. /// [ViewVariables(VVAccess.ReadWrite)] public float Radius = 0.35f; [ViewVariables, DataField] public float[] Interest = new float[SharedNPCSteeringSystem.InterestDirections]; [ViewVariables, DataField] public float[] Danger = new float[SharedNPCSteeringSystem.InterestDirections]; // TODO: Update radius, also danger points debug only public readonly List DangerPoints = new(); #endregion /// /// Set to true from other systems if you wish to force the NPC to move closer. /// [DataField("forceMove")] public bool ForceMove = false; [DataField("lastSteerDirection")] public Vector2 LastSteerDirection = Vector2.Zero; /// /// Last position we considered for being stuck. /// [DataField("lastStuckCoordinates")] public EntityCoordinates LastStuckCoordinates; [DataField("lastStuckTime", customTypeSerializer:typeof(TimeOffsetSerializer))] [AutoPausedField] public TimeSpan LastStuckTime; public const float StuckDistance = 1f; /// /// Have we currently requested a path. /// [ViewVariables] public bool Pathfind => PathfindToken != null; /// /// Are we considered arrived if we have line of sight of the target. /// [DataField("arriveOnLineOfSight")] public bool ArriveOnLineOfSight = false; /// /// How long the target has been in line of sight if applicable. /// [DataField("lineOfSightTimer")] public float LineOfSightTimer = 0f; [DataField("lineOfSightTimeRequired")] public float LineOfSightTimeRequired = 0.5f; [ViewVariables] public CancellationTokenSource? PathfindToken = null; /// /// Current path we're following to our coordinates. /// [ViewVariables] public Queue CurrentPath = new(); /// /// End target that we're trying to move to. /// [ViewVariables(VVAccess.ReadWrite)] public EntityCoordinates Coordinates; /// /// How close are we trying to get to the coordinates before being considered in range. /// [ViewVariables(VVAccess.ReadWrite)] public float Range = 0.2f; /// /// How far does the last node in the path need to be before considering re-pathfinding. /// [ViewVariables(VVAccess.ReadWrite)] public float RepathRange = 1.5f; public const int FailedPathLimit = 3; /// /// How many times we've failed to pathfind. Once this hits the limit we'll stop steering. /// [ViewVariables] public int FailedPathCount; [ViewVariables] public SteeringStatus Status = SteeringStatus.Moving; [ViewVariables(VVAccess.ReadWrite)] public PathFlags Flags = PathFlags.None; /// /// If the NPC is using a do_after to clear an obstacle. /// [DataField("doAfterId")] public DoAfterId? DoAfterId = null; } public enum SteeringStatus : byte { /// /// If we can't reach the target (e.g. different map). /// NoPath, /// /// Are we moving towards our target /// Moving, /// /// Are we currently in range of our target. /// InRange, }