NPC refactor (#10122)

Co-authored-by: metalgearsloth <metalgearsloth@gmail.com>
This commit is contained in:
metalgearsloth
2022-09-06 00:28:23 +10:00
committed by GitHub
parent 138e328c04
commit 0286b88388
290 changed files with 13842 additions and 5939 deletions

View 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 {}

View 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>();
}
}

View 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;
}
}

View 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;
}

View 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();
}
}

View 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,
}

View 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;
}

View 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();
}

View 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;
}

View File

@@ -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);
}
}

View 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,
}