NPC refactor (#10122)
Co-authored-by: metalgearsloth <metalgearsloth@gmail.com>
This commit is contained in:
7
Content.Server/NPC/Components/ActiveNPCComponent.cs
Normal file
7
Content.Server/NPC/Components/ActiveNPCComponent.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Content.Server.NPC.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Added to NPCs that are actively being updated.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class ActiveNPCComponent : Component {}
|
||||
17
Content.Server/NPC/Components/AiFactionPrototype.cs
Normal file
17
Content.Server/NPC/Components/AiFactionPrototype.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.NPC.Components
|
||||
{
|
||||
[Prototype("aiFaction")]
|
||||
public sealed class AiFactionPrototype : IPrototype
|
||||
{
|
||||
// These are immutable so any dynamic changes aren't saved back over.
|
||||
// AiFactionSystem will just read these and then store them.
|
||||
[ViewVariables]
|
||||
[IdDataField]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
[DataField("hostile")]
|
||||
public IReadOnlyList<string> Hostile { get; private set; } = new List<string>();
|
||||
}
|
||||
}
|
||||
11
Content.Server/NPC/Components/AiFactionTagComponent.cs
Normal file
11
Content.Server/NPC/Components/AiFactionTagComponent.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Content.Server.NPC.Systems;
|
||||
|
||||
namespace Content.Server.NPC.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class AiFactionTagComponent : Component
|
||||
{
|
||||
[DataField("factions")]
|
||||
public Faction Factions { get; set; } = Faction.None;
|
||||
}
|
||||
}
|
||||
11
Content.Server/NPC/Components/NPCAvoidanceComponent.cs
Normal file
11
Content.Server/NPC/Components/NPCAvoidanceComponent.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace Content.Server.NPC.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Should this entity be considered for collision avoidance
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class NPCAvoidanceComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("enabled")]
|
||||
public bool Enabled = true;
|
||||
}
|
||||
13
Content.Server/NPC/Components/NPCComponent.cs
Normal file
13
Content.Server/NPC/Components/NPCComponent.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Content.Shared.NPC;
|
||||
|
||||
namespace Content.Server.NPC.Components
|
||||
{
|
||||
public abstract class NPCComponent : SharedNPCComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains all of the world data for a particular NPC in terms of how it sees the world.
|
||||
/// </summary>
|
||||
[ViewVariables, DataField("blackboard", customTypeSerializer: typeof(NPCBlackboardSerializer))]
|
||||
public NPCBlackboard Blackboard = new();
|
||||
}
|
||||
}
|
||||
47
Content.Server/NPC/Components/NPCMeleeCombatComponent.cs
Normal file
47
Content.Server/NPC/Components/NPCMeleeCombatComponent.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
namespace Content.Server.NPC.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Added to NPCs whenever they're in melee combat so they can be handled by the dedicated system.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class NPCMeleeCombatComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Weapon we're using to attack the target. Can also be ourselves.
|
||||
/// </summary>
|
||||
[ViewVariables] public EntityUid Weapon;
|
||||
|
||||
[ViewVariables]
|
||||
public EntityUid Target;
|
||||
|
||||
[ViewVariables]
|
||||
public CombatStatus Status = CombatStatus.Normal;
|
||||
}
|
||||
|
||||
public enum CombatStatus : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// The target isn't in LOS anymore.
|
||||
/// </summary>
|
||||
NotInSight,
|
||||
|
||||
/// <summary>
|
||||
/// Due to some generic reason we are unable to attack the target.
|
||||
/// </summary>
|
||||
Unspecified,
|
||||
|
||||
/// <summary>
|
||||
/// Set if we can't reach the target for whatever reason.
|
||||
/// </summary>
|
||||
TargetUnreachable,
|
||||
|
||||
/// <summary>
|
||||
/// Set if the weapon we were assigned is no longer valid.
|
||||
/// </summary>
|
||||
NoWeapon,
|
||||
|
||||
/// <summary>
|
||||
/// No dramas.
|
||||
/// </summary>
|
||||
Normal,
|
||||
}
|
||||
11
Content.Server/NPC/Components/NPCPathfindPointComponent.cs
Normal file
11
Content.Server/NPC/Components/NPCPathfindPointComponent.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace Content.Server.NPC.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class NPCPathfindPointComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Next point for the NPC to head to.
|
||||
/// </summary>
|
||||
// [ViewVariables(VVAccess.ReadWrite), DataField("nextPoint")]
|
||||
// public EntityUid? NextPoint;
|
||||
}
|
||||
38
Content.Server/NPC/Components/NPCRVOComponent.cs
Normal file
38
Content.Server/NPC/Components/NPCRVOComponent.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using Content.Server.NPC.Systems;
|
||||
|
||||
namespace Content.Server.NPC.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Stores data for RVO collision avoidance
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class NPCRVOComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Maximum number of dynamic neighbors to consider for collision avoidance.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("maxNeighbors")]
|
||||
public int MaxNeighbors = 5;
|
||||
|
||||
/// <summary>
|
||||
/// Time horizon to consider for dynamic neighbor collision
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)] public float TimeHorizon = 3f;
|
||||
|
||||
/// <summary>
|
||||
/// Time horizon to consider for static neighbor collision.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)] public float ObstacleTimeHorizon = 3f;
|
||||
|
||||
/// <summary>
|
||||
/// Range considered for neighbor agents
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("neighborRange")]
|
||||
public float NeighborRange = 3f;
|
||||
|
||||
[ViewVariables]
|
||||
public readonly HashSet<EntityUid> ObstacleNeighbors = new();
|
||||
|
||||
[ViewVariables]
|
||||
public readonly HashSet<EntityUid> AgentNeighbors = new();
|
||||
}
|
||||
57
Content.Server/NPC/Components/NPCRangedCombatComponent.cs
Normal file
57
Content.Server/NPC/Components/NPCRangedCombatComponent.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using Content.Server.NPC.Systems;
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Server.NPC.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Added to an NPC doing ranged combat.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class NPCRangedCombatComponent : Component
|
||||
{
|
||||
[ViewVariables]
|
||||
public EntityUid Target;
|
||||
|
||||
[ViewVariables]
|
||||
public CombatStatus Status = CombatStatus.Normal;
|
||||
|
||||
// Most of the below is to deal with turrets.
|
||||
|
||||
/// <summary>
|
||||
/// If null it will instantly turn.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)] public Angle? RotationSpeed;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum distance, between our rotation and the target's, to consider shooting it.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Angle AccuracyThreshold = Angle.FromDegrees(30);
|
||||
|
||||
/// <summary>
|
||||
/// How long until the last line of sight check.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float LOSAccumulator = 0f;
|
||||
|
||||
/// <summary>
|
||||
/// Is the target still considered in LOS since the last check.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool TargetInLOS = false;
|
||||
|
||||
/// <summary>
|
||||
/// Delay after target is in LOS before we start shooting.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float ShootDelay = 0.2f;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float ShootAccumulator;
|
||||
|
||||
/// <summary>
|
||||
/// Sound to play if the target enters line of sight.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public SoundSpecifier? SoundTargetInLOS;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Content.Server.NPC.Components
|
||||
{
|
||||
/// Added when a medibot injects someone
|
||||
/// So they don't get injected again for at least a minute.
|
||||
[RegisterComponent]
|
||||
public sealed class NPCRecentlyInjectedComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("accumulator")]
|
||||
public float Accumulator = 0f;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("removeTime")]
|
||||
public TimeSpan RemoveTime = TimeSpan.FromMinutes(1);
|
||||
}
|
||||
}
|
||||
60
Content.Server/NPC/Components/NPCSteeringComponent.cs
Normal file
60
Content.Server/NPC/Components/NPCSteeringComponent.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System.Threading;
|
||||
using Content.Server.CPUJob.JobQueues;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Server.NPC.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Added to NPCs that are moving.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class NPCSteeringComponent : Component
|
||||
{
|
||||
[ViewVariables] public Job<Queue<TileRef>>? Pathfind = null;
|
||||
[ViewVariables] public CancellationTokenSource? PathfindToken = null;
|
||||
|
||||
/// <summary>
|
||||
/// Current path we're following to our coordinates.
|
||||
/// </summary>
|
||||
[ViewVariables] public Queue<TileRef> CurrentPath = new();
|
||||
|
||||
/// <summary>
|
||||
/// End target that we're trying to move to.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)] public EntityCoordinates Coordinates;
|
||||
|
||||
/// <summary>
|
||||
/// Target that we're trying to move to. If we have a path then this will be the first node on the path.
|
||||
/// </summary>
|
||||
[ViewVariables] public EntityCoordinates CurrentTarget;
|
||||
|
||||
/// <summary>
|
||||
/// How close are we trying to get to the coordinates before being considered in range.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)] public float Range = 0.2f;
|
||||
|
||||
/// <summary>
|
||||
/// How far does the last node in the path need to be before considering re-pathfinding.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)] public float RepathRange = 1.5f;
|
||||
|
||||
[ViewVariables] public SteeringStatus Status = SteeringStatus.Moving;
|
||||
}
|
||||
|
||||
public enum SteeringStatus : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// If we can't reach the target (e.g. different map).
|
||||
/// </summary>
|
||||
NoPath,
|
||||
|
||||
/// <summary>
|
||||
/// Are we moving towards our target
|
||||
/// </summary>
|
||||
Moving,
|
||||
|
||||
/// <summary>
|
||||
/// Are we currently in range of our target.
|
||||
/// </summary>
|
||||
InRange,
|
||||
}
|
||||
Reference in New Issue
Block a user