Shooting NPCs and more (#18042)
* Add pirate shooting * Shooting working * Basics working * Refactor time * More conversion * Update primitives * Update yml * weh * Building again * Draft * weh * b * Start shutdown * Starting to take form * Code side done * is it worky * Fix prototypes * stuff * Shitty working * Juke events working * Even more cleanup * RTX * Fix interaction combat mode and compquery * GetAmmoCount relays * Fix rotation speed * Juke fixes * fixes * weh * The collision avoidance never ends * Fixes * Pause support * framework * lazy * Fix idling * Fix drip * goobed * Fix takeover shutdown bug * Merge fixes * shitter * Fix carpos
@@ -24,9 +24,9 @@ public sealed class NPCTest
|
|||||||
{
|
{
|
||||||
var counts = new Dictionary<string, int>();
|
var counts = new Dictionary<string, int>();
|
||||||
|
|
||||||
foreach (var compound in protoManager.EnumeratePrototypes<HTNCompoundTask>())
|
foreach (var compound in protoManager.EnumeratePrototypes<HTNCompoundPrototype>())
|
||||||
{
|
{
|
||||||
Count(compound, counts, htnSystem);
|
Count(compound, counts, htnSystem, protoManager);
|
||||||
counts.Clear();
|
counts.Clear();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -34,13 +34,11 @@ public sealed class NPCTest
|
|||||||
await pool.CleanReturnAsync();
|
await pool.CleanReturnAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Count(HTNCompoundTask compound, Dictionary<string, int> counts, HTNSystem htnSystem)
|
private static void Count(HTNCompoundPrototype compound, Dictionary<string, int> counts, HTNSystem htnSystem, IPrototypeManager protoManager)
|
||||||
{
|
{
|
||||||
var compoundBranches = htnSystem.CompoundBranches[compound];
|
foreach (var branch in compound.Branches)
|
||||||
|
|
||||||
for (var i = 0; i < compound.Branches.Count; i++)
|
|
||||||
{
|
{
|
||||||
foreach (var task in compoundBranches[i])
|
foreach (var task in branch.Tasks)
|
||||||
{
|
{
|
||||||
if (task is HTNCompoundTask compoundTask)
|
if (task is HTNCompoundTask compoundTask)
|
||||||
{
|
{
|
||||||
@@ -49,7 +47,7 @@ public sealed class NPCTest
|
|||||||
|
|
||||||
Assert.That(count, Is.LessThan(50));
|
Assert.That(count, Is.LessThan(50));
|
||||||
counts[compound.ID] = count;
|
counts[compound.ID] = count;
|
||||||
Count(compoundTask, counts, htnSystem);
|
Count(protoManager.Index<HTNCompoundPrototype>(compoundTask.Task), counts, htnSystem, protoManager);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ using Robust.Shared.GameStates;
|
|||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using Content.Shared.Sprite;
|
||||||
|
using Robust.Shared.Serialization.Manager;
|
||||||
|
|
||||||
namespace Content.Server.Dragon;
|
namespace Content.Server.Dragon;
|
||||||
|
|
||||||
@@ -29,6 +31,7 @@ public sealed partial class DragonSystem : EntitySystem
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
[Dependency] private readonly ISerializationManager _serManager = default!;
|
||||||
[Dependency] private readonly ITileDefinitionManager _tileDef = default!;
|
[Dependency] private readonly ITileDefinitionManager _tileDef = default!;
|
||||||
[Dependency] private readonly ChatSystem _chat = default!;
|
[Dependency] private readonly ChatSystem _chat = default!;
|
||||||
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
|
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
|
||||||
@@ -149,8 +152,18 @@ public sealed partial class DragonSystem : EntitySystem
|
|||||||
if (comp.SpawnAccumulator > comp.SpawnCooldown)
|
if (comp.SpawnAccumulator > comp.SpawnCooldown)
|
||||||
{
|
{
|
||||||
comp.SpawnAccumulator -= comp.SpawnCooldown;
|
comp.SpawnAccumulator -= comp.SpawnCooldown;
|
||||||
var ent = Spawn(comp.SpawnPrototype, Transform(comp.Owner).MapPosition);
|
var ent = Spawn(comp.SpawnPrototype, Transform(comp.Owner).Coordinates);
|
||||||
_npc.SetBlackboard(ent, NPCBlackboard.FollowTarget, new EntityCoordinates(comp.Owner, Vector2.Zero));
|
|
||||||
|
// Update their look to match the leader.
|
||||||
|
if (TryComp<RandomSpriteComponent>(comp.Dragon, out var randomSprite))
|
||||||
|
{
|
||||||
|
var spawnedSprite = EnsureComp<RandomSpriteComponent>(ent);
|
||||||
|
_serManager.CopyTo(randomSprite, ref spawnedSprite, notNullableOverride: true);
|
||||||
|
Dirty(ent, spawnedSprite);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comp.Dragon != null)
|
||||||
|
_npc.SetBlackboard(ent, NPCBlackboard.FollowTarget, new EntityCoordinates(comp.Dragon.Value, Vector2.Zero));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,10 @@ namespace Content.Server.NPC.Commands
|
|||||||
}
|
}
|
||||||
|
|
||||||
var comp = _entities.AddComponent<HTNComponent>(entId);
|
var comp = _entities.AddComponent<HTNComponent>(entId);
|
||||||
comp.RootTask = args[1];
|
comp.RootTask = new HTNCompoundTask()
|
||||||
|
{
|
||||||
|
Task = args[1]
|
||||||
|
};
|
||||||
shell.WriteLine("AI component added.");
|
shell.WriteLine("AI component added.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System.Linq;
|
|
||||||
using Content.Server.Administration;
|
using Content.Server.Administration;
|
||||||
using Content.Server.NPC.HTN;
|
using Content.Server.NPC.HTN;
|
||||||
using Content.Shared.Administration;
|
using Content.Shared.Administration;
|
||||||
@@ -13,9 +12,13 @@ namespace Content.Server.NPC.Commands;
|
|||||||
[AdminCommand(AdminFlags.Debug)]
|
[AdminCommand(AdminFlags.Debug)]
|
||||||
public sealed class NPCDomainCommand : IConsoleCommand
|
public sealed class NPCDomainCommand : IConsoleCommand
|
||||||
{
|
{
|
||||||
|
[Dependency] private readonly IEntitySystemManager _sysManager = default!;
|
||||||
|
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||||
|
|
||||||
public string Command => "npcdomain";
|
public string Command => "npcdomain";
|
||||||
public string Description => "Lists the domain of a particular HTN compound task";
|
public string Description => "Lists the domain of a particular HTN compound task";
|
||||||
public string Help => $"{Command} <htncompoundtask>";
|
public string Help => $"{Command} <htncompoundtask>";
|
||||||
|
|
||||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||||
{
|
{
|
||||||
if (args.Length != 1)
|
if (args.Length != 1)
|
||||||
@@ -24,17 +27,15 @@ public sealed class NPCDomainCommand : IConsoleCommand
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var protoManager = IoCManager.Resolve<IPrototypeManager>();
|
if (!_protoManager.HasIndex<HTNCompoundPrototype>(args[0]))
|
||||||
|
|
||||||
if (!protoManager.TryIndex<HTNCompoundTask>(args[0], out var compound))
|
|
||||||
{
|
{
|
||||||
shell.WriteError($"Unable to find HTN compound task for '{args[0]}'");
|
shell.WriteError($"Unable to find HTN compound task for '{args[0]}'");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var htnSystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<HTNSystem>();
|
var htnSystem = _sysManager.GetEntitySystem<HTNSystem>();
|
||||||
|
|
||||||
foreach (var line in htnSystem.GetDomain(compound).Split("\n"))
|
foreach (var line in htnSystem.GetDomain(new HTNCompoundTask {Task = args[0]}).Split("\n"))
|
||||||
{
|
{
|
||||||
shell.WriteLine(line);
|
shell.WriteLine(line);
|
||||||
}
|
}
|
||||||
@@ -42,11 +43,9 @@ public sealed class NPCDomainCommand : IConsoleCommand
|
|||||||
|
|
||||||
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||||
{
|
{
|
||||||
var protoManager = IoCManager.Resolve<IPrototypeManager>();
|
|
||||||
|
|
||||||
if (args.Length > 1)
|
if (args.Length > 1)
|
||||||
return CompletionResult.Empty;
|
return CompletionResult.Empty;
|
||||||
|
|
||||||
return CompletionResult.FromHintOptions(protoManager.EnumeratePrototypes<HTNCompoundTask>().Select(o => o.ID), "compound task");
|
return CompletionResult.FromHintOptions(CompletionHelper.PrototypeIDs<HTNCompoundPrototype>(proto: _protoManager), "compound task");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
33
Content.Server/NPC/Components/NPCJukeComponent.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
using Content.Server.NPC.HTN.PrimitiveTasks.Operators.Combat;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||||
|
|
||||||
|
namespace Content.Server.NPC.Components;
|
||||||
|
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class NPCJukeComponent : Component
|
||||||
|
{
|
||||||
|
[DataField("jukeType")]
|
||||||
|
public JukeType JukeType = JukeType.Away;
|
||||||
|
|
||||||
|
[DataField("jukeDuration")]
|
||||||
|
public float JukeDuration = 0.5f;
|
||||||
|
|
||||||
|
[DataField("nextJuke", customTypeSerializer:typeof(TimeOffsetSerializer))]
|
||||||
|
public TimeSpan NextJuke;
|
||||||
|
|
||||||
|
[DataField("targetTile")]
|
||||||
|
public Vector2i? TargetTile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum JukeType : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Will move directly away from target if applicable.
|
||||||
|
/// </summary>
|
||||||
|
Away,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Move to the adjacent tile for the specified duration.
|
||||||
|
/// </summary>
|
||||||
|
AdjacentTile
|
||||||
|
}
|
||||||
@@ -6,11 +6,6 @@ namespace Content.Server.NPC.Components;
|
|||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public sealed class NPCMeleeCombatComponent : Component
|
public sealed class NPCMeleeCombatComponent : Component
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Weapon we're using to attack the target. Can also be ourselves.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables] public EntityUid Weapon;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If the target is moving what is the chance for this NPC to miss.
|
/// If the target is moving what is the chance for this NPC to miss.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -69,6 +69,22 @@ public sealed class NPCSteeringComponent : Component
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public bool Pathfind => PathfindToken != null;
|
public bool Pathfind => PathfindToken != null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Are we considered arrived if we have line of sight of the target.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("arriveOnLineOfSight")]
|
||||||
|
public bool ArriveOnLineOfSight = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How long the target has been in line of sight if applicable.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("lineOfSightTimer")]
|
||||||
|
public float LineOfSightTimer = 0f;
|
||||||
|
|
||||||
|
[DataField("lineOfSightTimeRequired")]
|
||||||
|
public float LineOfSightTimeRequired = 0.5f;
|
||||||
|
|
||||||
[ViewVariables] public CancellationTokenSource? PathfindToken = null;
|
[ViewVariables] public CancellationTokenSource? PathfindToken = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -9,17 +9,6 @@ namespace Content.Server.NPC.Events;
|
|||||||
[ByRefEvent]
|
[ByRefEvent]
|
||||||
public readonly record struct NPCSteeringEvent(
|
public readonly record struct NPCSteeringEvent(
|
||||||
NPCSteeringComponent Steering,
|
NPCSteeringComponent Steering,
|
||||||
float[] Interest,
|
TransformComponent Transform,
|
||||||
float[] Danger,
|
Vector2 WorldPosition,
|
||||||
float AgentRadius,
|
Angle OffsetRotation);
|
||||||
Angle OffsetRotation,
|
|
||||||
Vector2 WorldPosition)
|
|
||||||
{
|
|
||||||
public readonly NPCSteeringComponent Steering = Steering;
|
|
||||||
public readonly float[] Interest = Interest;
|
|
||||||
public readonly float[] Danger = Danger;
|
|
||||||
|
|
||||||
public readonly float AgentRadius = AgentRadius;
|
|
||||||
public readonly Angle OffsetRotation = OffsetRotation;
|
|
||||||
public readonly Vector2 WorldPosition = WorldPosition;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -15,6 +15,6 @@ public sealed class HTNBranch
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Due to how serv3 works we need to defer getting the actual tasks until after they have all been serialized.
|
/// Due to how serv3 works we need to defer getting the actual tasks until after they have all been serialized.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("tasks", required: true, customTypeSerializer:typeof(HTNTaskListSerializer))]
|
[DataField("tasks", required: true)]
|
||||||
public List<string> TaskPrototypes = default!;
|
public List<HTNTask> Tasks = new();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Content.Server.NPC.Components;
|
using Content.Server.NPC.Components;
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
|
||||||
|
|
||||||
namespace Content.Server.NPC.HTN;
|
namespace Content.Server.NPC.HTN;
|
||||||
|
|
||||||
@@ -11,8 +10,8 @@ public sealed class HTNComponent : NPCComponent
|
|||||||
/// The base task to use for planning
|
/// The base task to use for planning
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables(VVAccess.ReadWrite),
|
[ViewVariables(VVAccess.ReadWrite),
|
||||||
DataField("rootTask", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<HTNCompoundTask>))]
|
DataField("rootTask", required: true)]
|
||||||
public string RootTask = default!;
|
public HTNCompoundTask RootTask = default!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check any active services for our current plan. This is used to find new targets for example without changing our plan.
|
/// Check any active services for our current plan. This is used to find new targets for example without changing our plan.
|
||||||
|
|||||||
15
Content.Server/NPC/HTN/HTNCompoundPrototype.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Server.NPC.HTN;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a network of multiple tasks. This gets expanded out to its relevant nodes.
|
||||||
|
/// </summary>
|
||||||
|
[Prototype("htnCompound")]
|
||||||
|
public sealed class HTNCompoundPrototype : IPrototype
|
||||||
|
{
|
||||||
|
[IdDataField] public string ID { get; } = string.Empty;
|
||||||
|
|
||||||
|
[DataField("branches", required: true)]
|
||||||
|
public List<HTNBranch> Branches = new();
|
||||||
|
}
|
||||||
@@ -1,16 +1,15 @@
|
|||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||||
|
|
||||||
namespace Content.Server.NPC.HTN;
|
namespace Content.Server.NPC.HTN;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a network of multiple tasks. This gets expanded out to its relevant nodes.
|
/// Represents a network of multiple tasks. This gets expanded out to its relevant nodes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Prototype("htnCompound")]
|
/// <remarks>
|
||||||
public sealed class HTNCompoundTask : HTNTask
|
/// This just points to a specific htnCompound prototype
|
||||||
|
/// </remarks>
|
||||||
|
public sealed class HTNCompoundTask : HTNTask, IHTNCompound
|
||||||
{
|
{
|
||||||
/// <summary>
|
[DataField("task", required: true, customTypeSerializer:typeof(PrototypeIdSerializer<HTNCompoundPrototype>))]
|
||||||
/// The available branches for this compound task.
|
public string Task = string.Empty;
|
||||||
/// </summary>
|
|
||||||
[DataField("branches", required: true)]
|
|
||||||
public List<HTNBranch> Branches = default!;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,16 +12,19 @@ public sealed class HTNPlan
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly List<Dictionary<string, object>?> Effects;
|
public readonly List<Dictionary<string, object>?> Effects;
|
||||||
|
|
||||||
public List<int> BranchTraversalRecord;
|
public readonly List<int> BranchTraversalRecord;
|
||||||
|
|
||||||
public List<HTNPrimitiveTask> Tasks;
|
public readonly List<HTNPrimitiveTask> Tasks;
|
||||||
|
|
||||||
public int Index = 0;
|
|
||||||
|
|
||||||
public HTNPrimitiveTask CurrentTask => Tasks[Index];
|
public HTNPrimitiveTask CurrentTask => Tasks[Index];
|
||||||
|
|
||||||
public HTNOperator CurrentOperator => CurrentTask.Operator;
|
public HTNOperator CurrentOperator => CurrentTask.Operator;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Where we are up to in the <see cref="Tasks"/>
|
||||||
|
/// </summary>
|
||||||
|
public int Index = 0;
|
||||||
|
|
||||||
public HTNPlan(List<HTNPrimitiveTask> tasks, List<int> branchTraversalRecord, List<Dictionary<string, object>?> effects)
|
public HTNPlan(List<HTNPrimitiveTask> tasks, List<int> branchTraversalRecord, List<Dictionary<string, object>?> effects)
|
||||||
{
|
{
|
||||||
Tasks = tasks;
|
Tasks = tasks;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Robust.Shared.CPUJob.JobQueues;
|
using Robust.Shared.CPUJob.JobQueues;
|
||||||
using Content.Server.NPC.HTN.PrimitiveTasks;
|
using Content.Server.NPC.HTN.PrimitiveTasks;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
namespace Content.Server.NPC.HTN;
|
namespace Content.Server.NPC.HTN;
|
||||||
|
|
||||||
@@ -11,10 +12,11 @@ namespace Content.Server.NPC.HTN;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class HTNPlanJob : Job<HTNPlan>
|
public sealed class HTNPlanJob : Job<HTNPlan>
|
||||||
{
|
{
|
||||||
private readonly HTNSystem _htn;
|
private readonly HTNTask _rootTask;
|
||||||
private readonly HTNCompoundTask _rootTask;
|
|
||||||
private NPCBlackboard _blackboard;
|
private NPCBlackboard _blackboard;
|
||||||
|
|
||||||
|
private IPrototypeManager _protoManager;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Branch traversal of an existing plan (if applicable).
|
/// Branch traversal of an existing plan (if applicable).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -22,13 +24,13 @@ public sealed class HTNPlanJob : Job<HTNPlan>
|
|||||||
|
|
||||||
public HTNPlanJob(
|
public HTNPlanJob(
|
||||||
double maxTime,
|
double maxTime,
|
||||||
HTNSystem htn,
|
IPrototypeManager protoManager,
|
||||||
HTNCompoundTask rootTask,
|
HTNTask rootTask,
|
||||||
NPCBlackboard blackboard,
|
NPCBlackboard blackboard,
|
||||||
List<int>? branchTraversal,
|
List<int>? branchTraversal,
|
||||||
CancellationToken cancellationToken = default) : base(maxTime, cancellationToken)
|
CancellationToken cancellationToken = default) : base(maxTime, cancellationToken)
|
||||||
{
|
{
|
||||||
_htn = htn;
|
_protoManager = protoManager;
|
||||||
_rootTask = rootTask;
|
_rootTask = rootTask;
|
||||||
_blackboard = blackboard;
|
_blackboard = blackboard;
|
||||||
_branchTraversal = branchTraversal;
|
_branchTraversal = branchTraversal;
|
||||||
@@ -47,7 +49,6 @@ public sealed class HTNPlanJob : Job<HTNPlan>
|
|||||||
|
|
||||||
// branch traversal record. Whenever we find a new compound task this updates.
|
// branch traversal record. Whenever we find a new compound task this updates.
|
||||||
var btrIndex = 0;
|
var btrIndex = 0;
|
||||||
var btr = new List<int>();
|
|
||||||
|
|
||||||
// For some tasks we may do something expensive or want to re-use the planning result.
|
// For some tasks we may do something expensive or want to re-use the planning result.
|
||||||
// e.g. pathfind to a target before deciding to attack it.
|
// e.g. pathfind to a target before deciding to attack it.
|
||||||
@@ -83,8 +84,6 @@ public sealed class HTNPlanJob : Job<HTNPlan>
|
|||||||
PrimitiveCount = primitiveCount,
|
PrimitiveCount = primitiveCount,
|
||||||
});
|
});
|
||||||
|
|
||||||
btr.Add(btrIndex);
|
|
||||||
|
|
||||||
// TODO: Early out if existing plan is better and save lots of time.
|
// TODO: Early out if existing plan is better and save lots of time.
|
||||||
// my brain is not working rn AAA
|
// my brain is not working rn AAA
|
||||||
|
|
||||||
@@ -94,7 +93,7 @@ public sealed class HTNPlanJob : Job<HTNPlan>
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
RestoreTolastDecomposedTask(decompHistory, tasksToProcess, appliedStates, finalPlan, ref primitiveCount, ref _blackboard, ref btrIndex, ref btr);
|
RestoreTolastDecomposedTask(decompHistory, tasksToProcess, appliedStates, finalPlan, ref primitiveCount, ref _blackboard, ref btrIndex);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case HTNPrimitiveTask primitive:
|
case HTNPrimitiveTask primitive:
|
||||||
@@ -105,7 +104,7 @@ public sealed class HTNPlanJob : Job<HTNPlan>
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
RestoreTolastDecomposedTask(decompHistory, tasksToProcess, appliedStates, finalPlan, ref primitiveCount, ref _blackboard, ref btrIndex, ref btr);
|
RestoreTolastDecomposedTask(decompHistory, tasksToProcess, appliedStates, finalPlan, ref primitiveCount, ref _blackboard, ref btrIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -157,9 +156,9 @@ public sealed class HTNPlanJob : Job<HTNPlan>
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Goes through each compound task branch and tries to find an appropriate one.
|
/// Goes through each compound task branch and tries to find an appropriate one.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool TryFindSatisfiedMethod(HTNCompoundTask compound, Queue<HTNTask> tasksToProcess, NPCBlackboard blackboard, ref int mtrIndex)
|
private bool TryFindSatisfiedMethod(HTNCompoundTask compoundId, Queue<HTNTask> tasksToProcess, NPCBlackboard blackboard, ref int mtrIndex)
|
||||||
{
|
{
|
||||||
var compBranches = _htn.CompoundBranches[compound];
|
var compound = _protoManager.Index<HTNCompoundPrototype>(compoundId.Task);
|
||||||
|
|
||||||
for (var i = mtrIndex; i < compound.Branches.Count; i++)
|
for (var i = mtrIndex; i < compound.Branches.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -178,9 +177,7 @@ public sealed class HTNPlanJob : Job<HTNPlan>
|
|||||||
if (!isValid)
|
if (!isValid)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var branchTasks = compBranches[i];
|
foreach (var task in branch.Tasks)
|
||||||
|
|
||||||
foreach (var task in branchTasks)
|
|
||||||
{
|
{
|
||||||
tasksToProcess.Enqueue(task);
|
tasksToProcess.Enqueue(task);
|
||||||
}
|
}
|
||||||
@@ -201,8 +198,7 @@ public sealed class HTNPlanJob : Job<HTNPlan>
|
|||||||
List<HTNPrimitiveTask> finalPlan,
|
List<HTNPrimitiveTask> finalPlan,
|
||||||
ref int primitiveCount,
|
ref int primitiveCount,
|
||||||
ref NPCBlackboard blackboard,
|
ref NPCBlackboard blackboard,
|
||||||
ref int mtrIndex,
|
ref int mtrIndex)
|
||||||
ref List<int> btr)
|
|
||||||
{
|
{
|
||||||
tasksToProcess.Clear();
|
tasksToProcess.Clear();
|
||||||
|
|
||||||
@@ -214,11 +210,11 @@ public sealed class HTNPlanJob : Job<HTNPlan>
|
|||||||
mtrIndex = lastDecomp.BranchTraversal + 1;
|
mtrIndex = lastDecomp.BranchTraversal + 1;
|
||||||
|
|
||||||
var count = finalPlan.Count;
|
var count = finalPlan.Count;
|
||||||
|
var reduction = count - primitiveCount;
|
||||||
|
|
||||||
// Final plan only has primitive tasks added to it so we can just remove the count we've tracked since the last decomp.
|
// Final plan only has primitive tasks added to it so we can just remove the count we've tracked since the last decomp.
|
||||||
finalPlan.RemoveRange(count - primitiveCount, primitiveCount);
|
finalPlan.RemoveRange(reduction, primitiveCount);
|
||||||
appliedStates.RemoveRange(count - primitiveCount, primitiveCount);
|
appliedStates.RemoveRange(reduction, primitiveCount);
|
||||||
btr.RemoveRange(count - primitiveCount, primitiveCount);
|
|
||||||
|
|
||||||
primitiveCount = lastDecomp.PrimitiveCount;
|
primitiveCount = lastDecomp.PrimitiveCount;
|
||||||
blackboard = lastDecomp.Blackboard;
|
blackboard = lastDecomp.Blackboard;
|
||||||
@@ -241,7 +237,7 @@ public sealed class HTNPlanJob : Job<HTNPlan>
|
|||||||
public int PrimitiveCount;
|
public int PrimitiveCount;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The compound task that owns this decomposition.
|
/// The task that owns this decomposition.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public HTNCompoundTask CompoundTask = default!;
|
public HTNCompoundTask CompoundTask = default!;
|
||||||
|
|
||||||
|
|||||||
9
Content.Server/NPC/HTN/HTNPlanState.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Content.Server.NPC.HTN;
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum HTNPlanState : byte
|
||||||
|
{
|
||||||
|
TaskFinished = 1 << 0,
|
||||||
|
|
||||||
|
PlanFinished = 1 << 1,
|
||||||
|
}
|
||||||
@@ -13,8 +13,7 @@ using JetBrains.Annotations;
|
|||||||
using Robust.Server.Player;
|
using Robust.Server.Player;
|
||||||
using Robust.Shared.Players;
|
using Robust.Shared.Players;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Utility;
|
||||||
using Robust.Shared.Timing;
|
|
||||||
|
|
||||||
namespace Content.Server.NPC.HTN;
|
namespace Content.Server.NPC.HTN;
|
||||||
|
|
||||||
@@ -25,20 +24,14 @@ public sealed class HTNSystem : EntitySystem
|
|||||||
[Dependency] private readonly NPCSystem _npc = default!;
|
[Dependency] private readonly NPCSystem _npc = default!;
|
||||||
[Dependency] private readonly NPCUtilitySystem _utility = default!;
|
[Dependency] private readonly NPCUtilitySystem _utility = default!;
|
||||||
|
|
||||||
private ISawmill _sawmill = default!;
|
|
||||||
private readonly JobQueue _planQueue = new(0.004);
|
private readonly JobQueue _planQueue = new(0.004);
|
||||||
|
|
||||||
private readonly HashSet<ICommonSession> _subscribers = new();
|
private readonly HashSet<ICommonSession> _subscribers = new();
|
||||||
|
|
||||||
// hngngghghgh
|
|
||||||
public IReadOnlyDictionary<HTNCompoundTask, List<HTNTask>[]> CompoundBranches => _compoundBranches;
|
|
||||||
private Dictionary<HTNCompoundTask, List<HTNTask>[]> _compoundBranches = new();
|
|
||||||
|
|
||||||
// Hierarchical Task Network
|
// Hierarchical Task Network
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
_sawmill = Logger.GetSawmill("npc.htn");
|
|
||||||
SubscribeLocalEvent<HTNComponent, ComponentShutdown>(OnHTNShutdown);
|
SubscribeLocalEvent<HTNComponent, ComponentShutdown>(OnHTNShutdown);
|
||||||
SubscribeNetworkEvent<RequestHTNMessage>(OnHTNMessage);
|
SubscribeNetworkEvent<RequestHTNMessage>(OnHTNMessage);
|
||||||
|
|
||||||
@@ -69,7 +62,9 @@ public sealed class HTNSystem : EntitySystem
|
|||||||
private void OnLoad()
|
private void OnLoad()
|
||||||
{
|
{
|
||||||
// Clear all NPCs in case they're hanging onto stale tasks
|
// Clear all NPCs in case they're hanging onto stale tasks
|
||||||
foreach (var comp in EntityQuery<HTNComponent>(true))
|
var query = AllEntityQuery<HTNComponent>();
|
||||||
|
|
||||||
|
while (query.MoveNext(out var comp))
|
||||||
{
|
{
|
||||||
comp.PlanningToken?.Cancel();
|
comp.PlanningToken?.Cancel();
|
||||||
comp.PlanningToken = null;
|
comp.PlanningToken = null;
|
||||||
@@ -77,73 +72,64 @@ public sealed class HTNSystem : EntitySystem
|
|||||||
if (comp.Plan != null)
|
if (comp.Plan != null)
|
||||||
{
|
{
|
||||||
var currentOperator = comp.Plan.CurrentOperator;
|
var currentOperator = comp.Plan.CurrentOperator;
|
||||||
currentOperator.Shutdown(comp.Blackboard, HTNOperatorStatus.Failed);
|
ShutdownTask(currentOperator, comp.Blackboard, HTNOperatorStatus.Failed);
|
||||||
|
ShutdownPlan(comp);
|
||||||
comp.Plan = null;
|
comp.Plan = null;
|
||||||
|
RequestPlan(comp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_compoundBranches.Clear();
|
|
||||||
|
|
||||||
// Add dependencies for all operators.
|
// Add dependencies for all operators.
|
||||||
// We put code on operators as I couldn't think of a clean way to put it on systems.
|
// We put code on operators as I couldn't think of a clean way to put it on systems.
|
||||||
foreach (var compound in _prototypeManager.EnumeratePrototypes<HTNCompoundTask>())
|
foreach (var compound in _prototypeManager.EnumeratePrototypes<HTNCompoundPrototype>())
|
||||||
{
|
{
|
||||||
UpdateCompound(compound);
|
UpdateCompound(compound);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var primitive in _prototypeManager.EnumeratePrototypes<HTNPrimitiveTask>())
|
|
||||||
{
|
|
||||||
UpdatePrimitive(primitive);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPrototypeLoad(PrototypesReloadedEventArgs obj)
|
private void OnPrototypeLoad(PrototypesReloadedEventArgs obj)
|
||||||
{
|
{
|
||||||
|
if (!obj.ByType.ContainsKey(typeof(HTNCompoundPrototype)))
|
||||||
|
return;
|
||||||
|
|
||||||
OnLoad();
|
OnLoad();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdatePrimitive(HTNPrimitiveTask primitive)
|
private void UpdateCompound(HTNCompoundPrototype compound)
|
||||||
{
|
{
|
||||||
foreach (var precon in primitive.Preconditions)
|
|
||||||
{
|
|
||||||
precon.Initialize(EntityManager.EntitySysManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
primitive.Operator.Initialize(EntityManager.EntitySysManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateCompound(HTNCompoundTask compound)
|
|
||||||
{
|
|
||||||
var branchies = new List<HTNTask>[compound.Branches.Count];
|
|
||||||
_compoundBranches.Add(compound, branchies);
|
|
||||||
|
|
||||||
for (var i = 0; i < compound.Branches.Count; i++)
|
for (var i = 0; i < compound.Branches.Count; i++)
|
||||||
{
|
{
|
||||||
var branch = compound.Branches[i];
|
var branch = compound.Branches[i];
|
||||||
var brancho = new List<HTNTask>(branch.TaskPrototypes.Count);
|
|
||||||
branchies[i] = brancho;
|
|
||||||
|
|
||||||
// Didn't do this in a typeserializer because we can't recursively grab our own prototype during it, woohoo!
|
|
||||||
foreach (var proto in branch.TaskPrototypes)
|
|
||||||
{
|
|
||||||
if (_prototypeManager.TryIndex<HTNCompoundTask>(proto, out var compTask))
|
|
||||||
{
|
|
||||||
brancho.Add(compTask);
|
|
||||||
}
|
|
||||||
else if (_prototypeManager.TryIndex<HTNPrimitiveTask>(proto, out var primTask))
|
|
||||||
{
|
|
||||||
brancho.Add(primTask);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_sawmill.Error($"Unable to find HTNTask for {proto} on {compound.ID}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var precon in branch.Preconditions)
|
foreach (var precon in branch.Preconditions)
|
||||||
{
|
{
|
||||||
precon.Initialize(EntityManager.EntitySysManager);
|
precon.Initialize(EntityManager.EntitySysManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var task in branch.Tasks)
|
||||||
|
{
|
||||||
|
UpdateTask(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateTask(HTNTask task)
|
||||||
|
{
|
||||||
|
switch (task)
|
||||||
|
{
|
||||||
|
case HTNCompoundTask:
|
||||||
|
// NOOP, handled elsewhere
|
||||||
|
break;
|
||||||
|
case HTNPrimitiveTask primitive:
|
||||||
|
foreach (var precon in primitive.Preconditions)
|
||||||
|
{
|
||||||
|
precon.Initialize(EntityManager.EntitySysManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
primitive.Operator.Initialize(EntityManager.EntitySysManager);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,7 +163,7 @@ public sealed class HTNSystem : EntitySystem
|
|||||||
{
|
{
|
||||||
if (comp.PlanningJob.Exception != null)
|
if (comp.PlanningJob.Exception != null)
|
||||||
{
|
{
|
||||||
_sawmill.Fatal($"Received exception on planning job for {uid}!");
|
Log.Fatal($"Received exception on planning job for {uid}!");
|
||||||
_npc.SleepNPC(uid);
|
_npc.SleepNPC(uid);
|
||||||
var exc = comp.PlanningJob.Exception;
|
var exc = comp.PlanningJob.Exception;
|
||||||
RemComp<HTNComponent>(uid);
|
RemComp<HTNComponent>(uid);
|
||||||
@@ -209,7 +195,13 @@ public sealed class HTNSystem : EntitySystem
|
|||||||
if (comp.Plan == null || newPlanBetter)
|
if (comp.Plan == null || newPlanBetter)
|
||||||
{
|
{
|
||||||
comp.CheckServices = false;
|
comp.CheckServices = false;
|
||||||
comp.Plan?.CurrentTask.Operator.Shutdown(comp.Blackboard, HTNOperatorStatus.BetterPlan);
|
|
||||||
|
if (comp.Plan != null)
|
||||||
|
{
|
||||||
|
ShutdownTask(comp.Plan.CurrentOperator, comp.Blackboard, HTNOperatorStatus.BetterPlan);
|
||||||
|
ShutdownPlan(comp);
|
||||||
|
}
|
||||||
|
|
||||||
comp.Plan = comp.PlanningJob.Result;
|
comp.Plan = comp.PlanningJob.Result;
|
||||||
|
|
||||||
// Startup the first task and anything else we need to do.
|
// Startup the first task and anything else we need to do.
|
||||||
@@ -227,7 +219,7 @@ public sealed class HTNSystem : EntitySystem
|
|||||||
{
|
{
|
||||||
text.AppendLine($"BTR: {string.Join(", ", comp.Plan.BranchTraversalRecord)}");
|
text.AppendLine($"BTR: {string.Join(", ", comp.Plan.BranchTraversalRecord)}");
|
||||||
text.AppendLine($"tasks:");
|
text.AppendLine($"tasks:");
|
||||||
var root = _prototypeManager.Index<HTNCompoundTask>(comp.RootTask);
|
var root = comp.RootTask;
|
||||||
var btr = new List<int>();
|
var btr = new List<int>();
|
||||||
var level = -1;
|
var level = -1;
|
||||||
AppendDebugText(root, text, comp.Plan.BranchTraversalRecord, btr, ref level);
|
AppendDebugText(root, text, comp.Plan.BranchTraversalRecord, btr, ref level);
|
||||||
@@ -267,23 +259,24 @@ public sealed class HTNSystem : EntitySystem
|
|||||||
|
|
||||||
if (task is HTNPrimitiveTask primitive)
|
if (task is HTNPrimitiveTask primitive)
|
||||||
{
|
{
|
||||||
text.AppendLine(primitive.ID);
|
text.AppendLine(primitive.ToString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (task is HTNCompoundTask compound)
|
if (task is HTNCompoundTask compTask)
|
||||||
{
|
{
|
||||||
|
var compound = _prototypeManager.Index<HTNCompoundPrototype>(compTask.Task);
|
||||||
level++;
|
level++;
|
||||||
text.AppendLine(compound.ID);
|
text.AppendLine(compound.ID);
|
||||||
var branches = _compoundBranches[compound];
|
var branches = compound.Branches;
|
||||||
|
|
||||||
for (var i = 0; i < branches.Length; i++)
|
for (var i = 0; i < branches.Count; i++)
|
||||||
{
|
{
|
||||||
var branch = branches[i];
|
var branch = branches[i];
|
||||||
btr.Add(i);
|
btr.Add(i);
|
||||||
text.AppendLine($" branch {string.Join(", ", btr)}:");
|
text.AppendLine($" branch {string.Join(", ", btr)}:");
|
||||||
|
|
||||||
foreach (var sub in branch)
|
foreach (var sub in branch.Tasks)
|
||||||
{
|
{
|
||||||
AppendDebugText(sub, text, planBtr, btr, ref level);
|
AppendDebugText(sub, text, planBtr, btr, ref level);
|
||||||
}
|
}
|
||||||
@@ -344,21 +337,22 @@ public sealed class HTNSystem : EntitySystem
|
|||||||
case HTNOperatorStatus.Continuing:
|
case HTNOperatorStatus.Continuing:
|
||||||
break;
|
break;
|
||||||
case HTNOperatorStatus.Failed:
|
case HTNOperatorStatus.Failed:
|
||||||
currentOperator.Shutdown(blackboard, status);
|
ShutdownTask(currentOperator, blackboard, status);
|
||||||
component.Plan = null;
|
ShutdownPlan(component);
|
||||||
break;
|
break;
|
||||||
// Operator completed so go to the next one.
|
// Operator completed so go to the next one.
|
||||||
case HTNOperatorStatus.Finished:
|
case HTNOperatorStatus.Finished:
|
||||||
currentOperator.Shutdown(blackboard, status);
|
ShutdownTask(currentOperator, blackboard, status);
|
||||||
component.Plan.Index++;
|
component.Plan.Index++;
|
||||||
|
|
||||||
// Plan finished!
|
// Plan finished!
|
||||||
if (component.Plan.Tasks.Count <= component.Plan.Index)
|
if (component.Plan.Tasks.Count <= component.Plan.Index)
|
||||||
{
|
{
|
||||||
component.Plan = null;
|
ShutdownPlan(component);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ConditionalShutdown(component.Plan, currentOperator, blackboard, HTNPlanState.TaskFinished);
|
||||||
StartupTask(component.Plan.Tasks[component.Plan.Index], component.Blackboard, component.Plan.Effects[component.Plan.Index]);
|
StartupTask(component.Plan.Tasks[component.Plan.Index], component.Blackboard, component.Plan.Effects[component.Plan.Index]);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -367,6 +361,50 @@ public sealed class HTNSystem : EntitySystem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ShutdownTask(HTNOperator currentOperator, NPCBlackboard blackboard, HTNOperatorStatus status)
|
||||||
|
{
|
||||||
|
if (currentOperator is IHtnConditionalShutdown conditional &&
|
||||||
|
(conditional.ShutdownState & HTNPlanState.TaskFinished) != 0x0)
|
||||||
|
{
|
||||||
|
conditional.ConditionalShutdown(blackboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentOperator.TaskShutdown(blackboard, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShutdownPlan(HTNComponent component)
|
||||||
|
{
|
||||||
|
DebugTools.Assert(component.Plan != null);
|
||||||
|
var blackboard = component.Blackboard;
|
||||||
|
|
||||||
|
foreach (var task in component.Plan.Tasks)
|
||||||
|
{
|
||||||
|
if (task.Operator is IHtnConditionalShutdown conditional &&
|
||||||
|
(conditional.ShutdownState & HTNPlanState.PlanFinished) != 0x0)
|
||||||
|
{
|
||||||
|
conditional.ConditionalShutdown(blackboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
task.Operator.PlanShutdown(component.Blackboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
component.Plan = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shuts down the current operator conditionally.
|
||||||
|
/// </summary>
|
||||||
|
private void ConditionalShutdown(HTNPlan plan, HTNOperator currentOperator, NPCBlackboard blackboard, HTNPlanState state)
|
||||||
|
{
|
||||||
|
if (currentOperator is not IHtnConditionalShutdown conditional)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ((conditional.ShutdownState & state) == 0x0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
conditional.ConditionalShutdown(blackboard);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Starts a new primitive task. Will apply effects from planning if applicable.
|
/// Starts a new primitive task. Will apply effects from planning if applicable.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -400,8 +438,8 @@ public sealed class HTNSystem : EntitySystem
|
|||||||
|
|
||||||
var job = new HTNPlanJob(
|
var job = new HTNPlanJob(
|
||||||
0.02,
|
0.02,
|
||||||
this,
|
_prototypeManager,
|
||||||
_prototypeManager.Index<HTNCompoundTask>(component.RootTask),
|
component.RootTask,
|
||||||
component.Blackboard.ShallowClone(), branchTraversal, cancelToken.Token);
|
component.Blackboard.ShallowClone(), branchTraversal, cancelToken.Token);
|
||||||
|
|
||||||
_planQueue.EnqueueJob(job);
|
_planQueue.EnqueueJob(job);
|
||||||
@@ -425,13 +463,13 @@ public sealed class HTNSystem : EntitySystem
|
|||||||
|
|
||||||
if (task is HTNPrimitiveTask primitive)
|
if (task is HTNPrimitiveTask primitive)
|
||||||
{
|
{
|
||||||
builder.AppendLine(buffer + $"Primitive: {task.ID}");
|
builder.AppendLine(buffer + $"Primitive: {task}");
|
||||||
builder.AppendLine(buffer + $" operator: {primitive.Operator.GetType().Name}");
|
builder.AppendLine(buffer + $" operator: {primitive.Operator.GetType().Name}");
|
||||||
}
|
}
|
||||||
else if (task is HTNCompoundTask compound)
|
else if (task is HTNCompoundTask compTask)
|
||||||
{
|
{
|
||||||
builder.AppendLine(buffer + $"Compound: {task.ID}");
|
var compound = _prototypeManager.Index<HTNCompoundPrototype>(compTask.Task);
|
||||||
var compoundBranches = CompoundBranches[compound];
|
builder.AppendLine(buffer + $"Compound: {task}");
|
||||||
|
|
||||||
for (var i = 0; i < compound.Branches.Count; i++)
|
for (var i = 0; i < compound.Branches.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -439,9 +477,8 @@ public sealed class HTNSystem : EntitySystem
|
|||||||
|
|
||||||
builder.AppendLine(buffer + " branch:");
|
builder.AppendLine(buffer + " branch:");
|
||||||
indent++;
|
indent++;
|
||||||
var branchTasks = compoundBranches[i];
|
|
||||||
|
|
||||||
foreach (var branchTask in branchTasks)
|
foreach (var branchTask in branch.Tasks)
|
||||||
{
|
{
|
||||||
AppendDomain(builder, branchTask, ref indent);
|
AppendDomain(builder, branchTask, ref indent);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
using Robust.Shared.Prototypes;
|
|
||||||
|
|
||||||
namespace Content.Server.NPC.HTN;
|
namespace Content.Server.NPC.HTN;
|
||||||
|
|
||||||
public abstract class HTNTask : IPrototype
|
[ImplicitDataDefinitionForInheritors]
|
||||||
|
public abstract class HTNTask
|
||||||
{
|
{
|
||||||
[IdDataField] public string ID { get; } = default!;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
using Content.Server.NPC.HTN.PrimitiveTasks;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
using Robust.Shared.Serialization;
|
|
||||||
using Robust.Shared.Serialization.Manager;
|
|
||||||
using Robust.Shared.Serialization.Markdown;
|
|
||||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
|
||||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
|
||||||
using Robust.Shared.Serialization.Markdown.Validation;
|
|
||||||
using Robust.Shared.Serialization.Markdown.Value;
|
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
|
||||||
|
|
||||||
namespace Content.Server.NPC.HTN;
|
|
||||||
|
|
||||||
public sealed class HTNTaskListSerializer : ITypeSerializer<List<string>, SequenceDataNode>
|
|
||||||
{
|
|
||||||
public ValidationNode Validate(ISerializationManager serializationManager, SequenceDataNode node,
|
|
||||||
IDependencyCollection dependencies, ISerializationContext? context = null)
|
|
||||||
{
|
|
||||||
var list = new List<ValidationNode>();
|
|
||||||
var protoManager = dependencies.Resolve<IPrototypeManager>();
|
|
||||||
|
|
||||||
foreach (var data in node.Sequence)
|
|
||||||
{
|
|
||||||
if (data is not MappingDataNode mapping)
|
|
||||||
{
|
|
||||||
list.Add(new ErrorNode(data, $"Found invalid mapping node on {data}"));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var id = ((ValueDataNode) mapping["id"]).Value;
|
|
||||||
|
|
||||||
var isCompound = protoManager.HasIndex<HTNCompoundTask>(id);
|
|
||||||
var isPrimitive = protoManager.HasIndex<HTNPrimitiveTask>(id);
|
|
||||||
|
|
||||||
list.Add(isCompound ^ isPrimitive
|
|
||||||
? new ValidatedValueNode(node)
|
|
||||||
: new ErrorNode(node, $"Found duplicated HTN compound and primitive tasks for {id}"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ValidatedSequenceNode(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<string> Read(ISerializationManager serializationManager, SequenceDataNode node,
|
|
||||||
IDependencyCollection dependencies,
|
|
||||||
SerializationHookContext hookCtx, ISerializationContext? context = null,
|
|
||||||
ISerializationManager.InstantiationDelegate<List<string>>? instanceProvider = null)
|
|
||||||
{
|
|
||||||
var value = instanceProvider != null ? instanceProvider() : new List<string>();
|
|
||||||
foreach (var data in node.Sequence)
|
|
||||||
{
|
|
||||||
var mapping = (MappingDataNode) data;
|
|
||||||
var id = ((ValueDataNode) mapping["id"]).Value;
|
|
||||||
// Can't check prototypes here because we're still loading them so yay!
|
|
||||||
value.Add(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DataNode Write(ISerializationManager serializationManager, List<string> value,
|
|
||||||
IDependencyCollection dependencies, bool alwaysWrite = false,
|
|
||||||
ISerializationContext? context = null)
|
|
||||||
{
|
|
||||||
var sequence = new SequenceDataNode();
|
|
||||||
|
|
||||||
foreach (var task in value)
|
|
||||||
{
|
|
||||||
var mapping = new MappingDataNode
|
|
||||||
{
|
|
||||||
["id"] = new ValueDataNode(task)
|
|
||||||
};
|
|
||||||
|
|
||||||
sequence.Add(mapping);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sequence;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
8
Content.Server/NPC/HTN/IHTNCompound.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Content.Server.NPC.HTN;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a HTN task that can be decomposed into primitive tasks.
|
||||||
|
/// </summary>
|
||||||
|
public interface IHTNCompound
|
||||||
|
{
|
||||||
|
}
|
||||||
17
Content.Server/NPC/HTN/IHtnConditionalShutdown.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
namespace Content.Server.NPC.HTN;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper interface to run the appropriate shutdown for a particular task.
|
||||||
|
/// </summary>
|
||||||
|
public interface IHtnConditionalShutdown
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// When to shut the task down.
|
||||||
|
/// </summary>
|
||||||
|
HTNPlanState ShutdownState { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Run whenever the <see cref="ShutdownState"/> specifies.
|
||||||
|
/// </summary>
|
||||||
|
void ConditionalShutdown(NPCBlackboard blackboard);
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using Content.Shared.Hands.Components;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Server.NPC.HTN.Preconditions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if the active hand entity has the specified components.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ActiveHandComponentPrecondition : HTNPrecondition
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||||
|
|
||||||
|
[DataField("invert")]
|
||||||
|
public bool Invert;
|
||||||
|
|
||||||
|
[DataField("components", required: true)]
|
||||||
|
public ComponentRegistry Components = new();
|
||||||
|
|
||||||
|
public override bool IsMet(NPCBlackboard blackboard)
|
||||||
|
{
|
||||||
|
if (!blackboard.TryGetValue<Hand>(NPCBlackboard.ActiveHand, out var hand, _entManager) || hand.HeldEntity == null)
|
||||||
|
{
|
||||||
|
return Invert;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var comp in Components)
|
||||||
|
{
|
||||||
|
var hasComp = _entManager.HasComponent(hand.HeldEntity, comp.Value.Component.GetType());
|
||||||
|
|
||||||
|
if (!hasComp ||
|
||||||
|
Invert && hasComp)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using Content.Shared.Hands.Components;
|
||||||
|
|
||||||
|
namespace Content.Server.NPC.HTN.Preconditions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if an entity is held in the active hand.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ActiveHandEntityPrecondition : HTNPrecondition
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||||
|
|
||||||
|
public override bool IsMet(NPCBlackboard blackboard)
|
||||||
|
{
|
||||||
|
if (!blackboard.TryGetValue(NPCBlackboard.ActiveHand, out Hand? activeHand, _entManager))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return activeHand.HeldEntity != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using Content.Shared.Hands.Components;
|
||||||
|
|
||||||
|
namespace Content.Server.NPC.HTN.Preconditions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if the active hand is unoccupied.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ActiveHandFreePrecondition : HTNPrecondition
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||||
|
|
||||||
|
public override bool IsMet(NPCBlackboard blackboard)
|
||||||
|
{
|
||||||
|
return blackboard.TryGetValue<bool>(NPCBlackboard.ActiveHandFree, out var handFree, _entManager) && handFree;
|
||||||
|
}
|
||||||
|
}
|
||||||
48
Content.Server/NPC/HTN/Preconditions/GunAmmoPrecondition.cs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
using Content.Server.Weapons.Ranged.Systems;
|
||||||
|
using Content.Shared.Weapons.Ranged.Events;
|
||||||
|
|
||||||
|
namespace Content.Server.NPC.HTN.Preconditions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets ammo for this NPC's selected gun; either active hand or itself.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GunAmmoPrecondition : HTNPrecondition
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||||
|
|
||||||
|
[DataField("minPercent")]
|
||||||
|
public float MinPercent = 0f;
|
||||||
|
|
||||||
|
[DataField("maxPercent")]
|
||||||
|
public float MaxPercent = 1f;
|
||||||
|
|
||||||
|
public override bool IsMet(NPCBlackboard blackboard)
|
||||||
|
{
|
||||||
|
var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
|
||||||
|
var gunSystem = _entManager.System<GunSystem>();
|
||||||
|
|
||||||
|
if (!gunSystem.TryGetGun(owner, out var gunUid, out _))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ammoEv = new GetAmmoCountEvent();
|
||||||
|
_entManager.EventBus.RaiseLocalEvent(gunUid, ref ammoEv);
|
||||||
|
float percent;
|
||||||
|
|
||||||
|
if (ammoEv.Capacity == 0)
|
||||||
|
percent = 0f;
|
||||||
|
else
|
||||||
|
percent = ammoEv.Count / (float) ammoEv.Capacity;
|
||||||
|
|
||||||
|
percent = Math.Clamp(percent, 0f, 1f);
|
||||||
|
|
||||||
|
if (MaxPercent < percent)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (MinPercent > percent)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ public sealed class TargetInLOSPrecondition : HTNPrecondition
|
|||||||
private InteractionSystem _interaction = default!;
|
private InteractionSystem _interaction = default!;
|
||||||
|
|
||||||
[DataField("targetKey")]
|
[DataField("targetKey")]
|
||||||
public string TargetKey = "CombatTarget";
|
public string TargetKey = "Target";
|
||||||
|
|
||||||
[DataField("rangeKey")]
|
[DataField("rangeKey")]
|
||||||
public string RangeKey = "RangeKey";
|
public string RangeKey = "RangeKey";
|
||||||
|
|||||||
@@ -40,6 +40,14 @@ public abstract class HTNOperator
|
|||||||
return HTNOperatorStatus.Finished;
|
return HTNOperatorStatus.Finished;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the plan has finished running.
|
||||||
|
/// </summary>
|
||||||
|
public virtual void PlanShutdown(NPCBlackboard blackboard)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called the first time an operator runs.
|
/// Called the first time an operator runs.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -48,5 +56,5 @@ public abstract class HTNOperator
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called whenever the operator stops running.
|
/// Called whenever the operator stops running.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual void Shutdown(NPCBlackboard blackboard, HTNOperatorStatus status) {}
|
public virtual void TaskShutdown(NPCBlackboard blackboard, HTNOperatorStatus status) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ using Robust.Shared.Prototypes;
|
|||||||
|
|
||||||
namespace Content.Server.NPC.HTN.PrimitiveTasks;
|
namespace Content.Server.NPC.HTN.PrimitiveTasks;
|
||||||
|
|
||||||
[Prototype("htnPrimitive")]
|
|
||||||
public sealed class HTNPrimitiveTask : HTNTask
|
public sealed class HTNPrimitiveTask : HTNTask
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using Content.Server.NPC.Components;
|
||||||
|
|
||||||
|
namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Combat;
|
||||||
|
|
||||||
|
public sealed class JukeOperator : HTNOperator, IHtnConditionalShutdown
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||||
|
|
||||||
|
[DataField("jukeType")]
|
||||||
|
public JukeType JukeType = JukeType.AdjacentTile;
|
||||||
|
|
||||||
|
[DataField("shutdownState")]
|
||||||
|
public HTNPlanState ShutdownState { get; } = HTNPlanState.PlanFinished;
|
||||||
|
|
||||||
|
public override void Startup(NPCBlackboard blackboard)
|
||||||
|
{
|
||||||
|
base.Startup(blackboard);
|
||||||
|
var juke = _entManager.EnsureComponent<NPCJukeComponent>(blackboard.GetValue<EntityUid>(NPCBlackboard.Owner));
|
||||||
|
juke.JukeType = JukeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime)
|
||||||
|
{
|
||||||
|
return HTNOperatorStatus.Finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ConditionalShutdown(NPCBlackboard blackboard)
|
||||||
|
{
|
||||||
|
_entManager.RemoveComponent<NPCJukeComponent>(blackboard.GetValue<EntityUid>(NPCBlackboard.Owner));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,25 @@
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Content.Server.NPC.Components;
|
using Content.Server.NPC.Components;
|
||||||
|
using Content.Shared.CombatMode;
|
||||||
using Content.Shared.Mobs;
|
using Content.Shared.Mobs;
|
||||||
using Content.Shared.Mobs.Components;
|
using Content.Shared.Mobs.Components;
|
||||||
|
|
||||||
namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Melee;
|
namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Combat.Melee;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attacks the specified key in melee combat.
|
/// Attacks the specified key in melee combat.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class MeleeOperator : HTNOperator
|
public sealed class MeleeOperator : HTNOperator, IHtnConditionalShutdown
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When to shut the task down.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("shutdownState")]
|
||||||
|
public HTNPlanState ShutdownState { get; } = HTNPlanState.TaskFinished;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Key that contains the target entity.
|
/// Key that contains the target entity.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -53,10 +60,11 @@ public sealed class MeleeOperator : HTNOperator
|
|||||||
return (true, null);
|
return (true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Shutdown(NPCBlackboard blackboard, HTNOperatorStatus status)
|
public void ConditionalShutdown(NPCBlackboard blackboard)
|
||||||
{
|
{
|
||||||
base.Shutdown(blackboard, status);
|
var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
|
||||||
_entManager.RemoveComponent<NPCMeleeCombatComponent>(blackboard.GetValue<EntityUid>(NPCBlackboard.Owner));
|
_entManager.System<SharedCombatModeSystem>().SetInCombatMode(owner, false);
|
||||||
|
_entManager.RemoveComponent<NPCMeleeCombatComponent>(owner);
|
||||||
blackboard.Remove<EntityUid>(TargetKey);
|
blackboard.Remove<EntityUid>(TargetKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,9 +104,10 @@ public sealed class MeleeOperator : HTNOperator
|
|||||||
status = HTNOperatorStatus.Failed;
|
status = HTNOperatorStatus.Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status != HTNOperatorStatus.Continuing)
|
// Mark it as finished to continue the plan.
|
||||||
|
if (status == HTNOperatorStatus.Continuing && ShutdownState == HTNPlanState.PlanFinished)
|
||||||
{
|
{
|
||||||
_entManager.RemoveComponent<NPCMeleeCombatComponent>(owner);
|
status = HTNOperatorStatus.Finished;
|
||||||
}
|
}
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
@@ -1,16 +1,20 @@
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Content.Server.NPC.Components;
|
using Content.Server.NPC.Components;
|
||||||
|
using Content.Shared.CombatMode;
|
||||||
using Content.Shared.Mobs;
|
using Content.Shared.Mobs;
|
||||||
using Content.Shared.Mobs.Components;
|
using Content.Shared.Mobs.Components;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
|
|
||||||
namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Ranged;
|
namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Combat.Ranged;
|
||||||
|
|
||||||
public sealed class RangedOperator : HTNOperator
|
public sealed class GunOperator : HTNOperator, IHtnConditionalShutdown
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||||
|
|
||||||
|
[DataField("shutdownState")]
|
||||||
|
public HTNPlanState ShutdownState { get; } = HTNPlanState.TaskFinished;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Key that contains the target entity.
|
/// Key that contains the target entity.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -23,6 +27,12 @@ public sealed class RangedOperator : HTNOperator
|
|||||||
[DataField("targetState")]
|
[DataField("targetState")]
|
||||||
public MobState TargetState = MobState.Alive;
|
public MobState TargetState = MobState.Alive;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Do we require line of sight of the target before failing.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("requireLOS")]
|
||||||
|
public bool RequireLOS = false;
|
||||||
|
|
||||||
// Like movement we add a component and pass it off to the dedicated system.
|
// Like movement we add a component and pass it off to the dedicated system.
|
||||||
|
|
||||||
public override async Task<(bool Valid, Dictionary<string, object>? Effects)> Plan(NPCBlackboard blackboard,
|
public override async Task<(bool Valid, Dictionary<string, object>? Effects)> Plan(NPCBlackboard blackboard,
|
||||||
@@ -60,10 +70,11 @@ public sealed class RangedOperator : HTNOperator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Shutdown(NPCBlackboard blackboard, HTNOperatorStatus status)
|
public void ConditionalShutdown(NPCBlackboard blackboard)
|
||||||
{
|
{
|
||||||
base.Shutdown(blackboard, status);
|
var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
|
||||||
_entManager.RemoveComponent<NPCRangedCombatComponent>(blackboard.GetValue<EntityUid>(NPCBlackboard.Owner));
|
_entManager.System<SharedCombatModeSystem>().SetInCombatMode(owner, false);
|
||||||
|
_entManager.RemoveComponent<NPCRangedCombatComponent>(owner);
|
||||||
blackboard.Remove<EntityUid>(TargetKey);
|
blackboard.Remove<EntityUid>(TargetKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,9 +100,14 @@ public sealed class RangedOperator : HTNOperator
|
|||||||
switch (combat.Status)
|
switch (combat.Status)
|
||||||
{
|
{
|
||||||
case CombatStatus.TargetUnreachable:
|
case CombatStatus.TargetUnreachable:
|
||||||
case CombatStatus.NotInSight:
|
|
||||||
status = HTNOperatorStatus.Failed;
|
status = HTNOperatorStatus.Failed;
|
||||||
break;
|
break;
|
||||||
|
case CombatStatus.NotInSight:
|
||||||
|
if (RequireLOS)
|
||||||
|
status = HTNOperatorStatus.Failed;
|
||||||
|
else
|
||||||
|
status = HTNOperatorStatus.Continuing;
|
||||||
|
break;
|
||||||
case CombatStatus.Normal:
|
case CombatStatus.Normal:
|
||||||
status = HTNOperatorStatus.Continuing;
|
status = HTNOperatorStatus.Continuing;
|
||||||
break;
|
break;
|
||||||
@@ -106,9 +122,10 @@ public sealed class RangedOperator : HTNOperator
|
|||||||
status = HTNOperatorStatus.Failed;
|
status = HTNOperatorStatus.Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status != HTNOperatorStatus.Continuing)
|
// Mark it as finished to continue the plan.
|
||||||
|
if (status == HTNOperatorStatus.Continuing && ShutdownState == HTNPlanState.PlanFinished)
|
||||||
{
|
{
|
||||||
_entManager.RemoveComponent<NPCRangedCombatComponent>(owner);
|
status = HTNOperatorStatus.Finished;
|
||||||
}
|
}
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
@@ -4,14 +4,14 @@ using System.Threading.Tasks;
|
|||||||
using Content.Shared.DoAfter;
|
using Content.Shared.DoAfter;
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
|
|
||||||
namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators;
|
namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Interactions;
|
||||||
|
|
||||||
public sealed class AltInteractOperator : HTNOperator
|
public sealed class AltInteractOperator : HTNOperator
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||||
|
|
||||||
[DataField("targetKey")]
|
[DataField("targetKey")]
|
||||||
public string Key = "CombatTarget";
|
public string Key = "Target";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If this alt-interaction started a do_after where does the key get stored.
|
/// If this alt-interaction started a do_after where does the key get stored.
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using Content.Server.Hands.Systems;
|
||||||
|
using Content.Shared.Hands.Components;
|
||||||
|
|
||||||
|
namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Interactions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Drops the active hand entity underneath us.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class DropOperator : HTNOperator
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||||
|
|
||||||
|
public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime)
|
||||||
|
{
|
||||||
|
if (!blackboard.TryGetValue(NPCBlackboard.ActiveHand, out Hand? activeHand, _entManager))
|
||||||
|
{
|
||||||
|
return HTNOperatorStatus.Finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
var owner = blackboard.GetValueOrDefault<EntityUid>(NPCBlackboard.Owner, _entManager);
|
||||||
|
// TODO: Need some sort of interaction cooldown probably.
|
||||||
|
var handsSystem = _entManager.System<HandsSystem>();
|
||||||
|
|
||||||
|
if (handsSystem.TryDrop(owner))
|
||||||
|
{
|
||||||
|
return HTNOperatorStatus.Finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HTNOperatorStatus.Failed;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using Content.Server.Hands.Systems;
|
||||||
|
|
||||||
|
namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Interactions;
|
||||||
|
|
||||||
|
public sealed class EquipOperator : HTNOperator
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||||
|
|
||||||
|
[DataField("target")]
|
||||||
|
public string Target = "Target";
|
||||||
|
|
||||||
|
public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime)
|
||||||
|
{
|
||||||
|
if (!blackboard.TryGetValue<EntityUid>(Target, out var target, _entManager))
|
||||||
|
{
|
||||||
|
return HTNOperatorStatus.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
|
||||||
|
var handsSystem = _entManager.System<HandsSystem>();
|
||||||
|
|
||||||
|
// TODO: As elsewhere need some generic interaction cooldown system
|
||||||
|
if (handsSystem.TryPickup(owner, target))
|
||||||
|
{
|
||||||
|
return HTNOperatorStatus.Finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HTNOperatorStatus.Failed;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
using Content.Server.Interaction;
|
using Content.Server.Interaction;
|
||||||
|
using Content.Shared.CombatMode;
|
||||||
using Content.Shared.Timing;
|
using Content.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators;
|
namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Interactions;
|
||||||
|
|
||||||
public sealed class InteractWithOperator : HTNOperator
|
public sealed class InteractWithOperator : HTNOperator
|
||||||
{
|
{
|
||||||
@@ -24,6 +25,7 @@ public sealed class InteractWithOperator : HTNOperator
|
|||||||
return HTNOperatorStatus.Continuing;
|
return HTNOperatorStatus.Continuing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_entManager.System<SharedCombatModeSystem>().SetInCombatMode(owner, false);
|
||||||
_entManager.System<InteractionSystem>().UserInteraction(owner, targetXform.Coordinates, moveTarget);
|
_entManager.System<InteractionSystem>().UserInteraction(owner, targetXform.Coordinates, moveTarget);
|
||||||
|
|
||||||
return HTNOperatorStatus.Finished;
|
return HTNOperatorStatus.Finished;
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Server.Hands.Systems;
|
||||||
|
using Content.Shared.Hands.Components;
|
||||||
|
|
||||||
|
namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Interactions;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Swaps to any free hand.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class SwapToFreeHandOperator : HTNOperator
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||||
|
|
||||||
|
public override async Task<(bool Valid, Dictionary<string, object>? Effects)> Plan(NPCBlackboard blackboard, CancellationToken cancelToken)
|
||||||
|
{
|
||||||
|
if (!blackboard.TryGetValue<List<string>>(NPCBlackboard.FreeHands, out var hands, _entManager) ||
|
||||||
|
!_entManager.TryGetComponent<HandsComponent>(blackboard.GetValue<EntityUid>(NPCBlackboard.Owner), out var handsComp))
|
||||||
|
{
|
||||||
|
return (false, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var hand in hands)
|
||||||
|
{
|
||||||
|
return (true, new Dictionary<string, object>()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
NPCBlackboard.ActiveHand, handsComp.Hands[hand]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
NPCBlackboard.ActiveHandFree, true
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (false, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime)
|
||||||
|
{
|
||||||
|
// TODO: Need interaction cooldown
|
||||||
|
var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
|
||||||
|
var handSystem = _entManager.System<HandsSystem>();
|
||||||
|
|
||||||
|
if (!handSystem.TrySelectEmptyHand(owner))
|
||||||
|
{
|
||||||
|
return HTNOperatorStatus.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HTNOperatorStatus.Finished;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Moves an NPC to the specified target key. Hands the actual steering off to NPCSystem.Steering
|
/// Moves an NPC to the specified target key. Hands the actual steering off to NPCSystem.Steering
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class MoveToOperator : HTNOperator
|
public sealed class MoveToOperator : HTNOperator, IHtnConditionalShutdown
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||||
@@ -19,6 +19,12 @@ public sealed class MoveToOperator : HTNOperator
|
|||||||
private PathfindingSystem _pathfind = default!;
|
private PathfindingSystem _pathfind = default!;
|
||||||
private SharedTransformSystem _transform = default!;
|
private SharedTransformSystem _transform = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When to shut the task down.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("shutdownState")]
|
||||||
|
public HTNPlanState ShutdownState { get; } = HTNPlanState.TaskFinished;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Should we assume the MovementTarget is reachable during planning or should we pathfind to it?
|
/// Should we assume the MovementTarget is reachable during planning or should we pathfind to it?
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -35,7 +41,7 @@ public sealed class MoveToOperator : HTNOperator
|
|||||||
/// Target Coordinates to move to. This gets removed after execution.
|
/// Target Coordinates to move to. This gets removed after execution.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("targetKey")]
|
[DataField("targetKey")]
|
||||||
public string TargetKey = "MovementTarget";
|
public string TargetKey = "TargetCoordinates";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Where the pathfinding result will be stored (if applicable). This gets removed after execution.
|
/// Where the pathfinding result will be stored (if applicable). This gets removed after execution.
|
||||||
@@ -49,6 +55,12 @@ public sealed class MoveToOperator : HTNOperator
|
|||||||
[DataField("rangeKey")]
|
[DataField("rangeKey")]
|
||||||
public string RangeKey = "MovementRange";
|
public string RangeKey = "MovementRange";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Do we only need to move into line of sight.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("stopOnLineOfSight")]
|
||||||
|
public bool StopOnLineOfSight;
|
||||||
|
|
||||||
private const string MovementCancelToken = "MovementCancelToken";
|
private const string MovementCancelToken = "MovementCancelToken";
|
||||||
|
|
||||||
public override void Initialize(IEntitySystemManager sysManager)
|
public override void Initialize(IEntitySystemManager sysManager)
|
||||||
@@ -132,6 +144,7 @@ public sealed class MoveToOperator : HTNOperator
|
|||||||
|
|
||||||
// Re-use the path we may have if applicable.
|
// Re-use the path we may have if applicable.
|
||||||
var comp = _steering.Register(uid, targetCoordinates);
|
var comp = _steering.Register(uid, targetCoordinates);
|
||||||
|
comp.ArriveOnLineOfSight = StopOnLineOfSight;
|
||||||
|
|
||||||
if (blackboard.TryGetValue<float>(RangeKey, out var range, _entManager))
|
if (blackboard.TryGetValue<float>(RangeKey, out var range, _entManager))
|
||||||
{
|
{
|
||||||
@@ -150,10 +163,30 @@ public sealed class MoveToOperator : HTNOperator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Shutdown(NPCBlackboard blackboard, HTNOperatorStatus status)
|
public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime)
|
||||||
{
|
{
|
||||||
base.Shutdown(blackboard, status);
|
var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
|
||||||
|
|
||||||
|
if (!_entManager.TryGetComponent<NPCSteeringComponent>(owner, out var steering))
|
||||||
|
return HTNOperatorStatus.Failed;
|
||||||
|
|
||||||
|
// Just keep moving in the background and let the other tasks handle it.
|
||||||
|
if (ShutdownState == HTNPlanState.PlanFinished && steering.Status == SteeringStatus.Moving)
|
||||||
|
{
|
||||||
|
return HTNOperatorStatus.Finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
return steering.Status switch
|
||||||
|
{
|
||||||
|
SteeringStatus.InRange => HTNOperatorStatus.Finished,
|
||||||
|
SteeringStatus.NoPath => HTNOperatorStatus.Failed,
|
||||||
|
SteeringStatus.Moving => HTNOperatorStatus.Continuing,
|
||||||
|
_ => throw new ArgumentOutOfRangeException()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ConditionalShutdown(NPCBlackboard blackboard)
|
||||||
|
{
|
||||||
// Cleanup the blackboard and remove steering.
|
// Cleanup the blackboard and remove steering.
|
||||||
if (blackboard.TryGetValue<CancellationTokenSource>(MovementCancelToken, out var cancelToken, _entManager))
|
if (blackboard.TryGetValue<CancellationTokenSource>(MovementCancelToken, out var cancelToken, _entManager))
|
||||||
{
|
{
|
||||||
@@ -171,20 +204,4 @@ public sealed class MoveToOperator : HTNOperator
|
|||||||
|
|
||||||
_steering.Unregister(blackboard.GetValue<EntityUid>(NPCBlackboard.Owner));
|
_steering.Unregister(blackboard.GetValue<EntityUid>(NPCBlackboard.Owner));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime)
|
|
||||||
{
|
|
||||||
var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
|
|
||||||
|
|
||||||
if (!_entManager.TryGetComponent<NPCSteeringComponent>(owner, out var steering))
|
|
||||||
return HTNOperatorStatus.Failed;
|
|
||||||
|
|
||||||
return steering.Status switch
|
|
||||||
{
|
|
||||||
SteeringStatus.InRange => HTNOperatorStatus.Finished,
|
|
||||||
SteeringStatus.NoPath => HTNOperatorStatus.Failed,
|
|
||||||
SteeringStatus.Moving => HTNOperatorStatus.Continuing,
|
|
||||||
_ => throw new ArgumentOutOfRangeException()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// What it sounds like.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class NoOperator : HTNOperator
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -16,8 +16,8 @@ public sealed class PickAccessibleOperator : HTNOperator
|
|||||||
[DataField("rangeKey", required: true)]
|
[DataField("rangeKey", required: true)]
|
||||||
public string RangeKey = string.Empty;
|
public string RangeKey = string.Empty;
|
||||||
|
|
||||||
[DataField("targetKey", required: true)]
|
[DataField("targetCoordinates")]
|
||||||
public string TargetKey = string.Empty;
|
public string TargetCoordinates = "TargetCoordinates";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Where the pathfinding result will be stored (if applicable). This gets removed after execution.
|
/// Where the pathfinding result will be stored (if applicable). This gets removed after execution.
|
||||||
@@ -58,7 +58,7 @@ public sealed class PickAccessibleOperator : HTNOperator
|
|||||||
|
|
||||||
return (true, new Dictionary<string, object>()
|
return (true, new Dictionary<string, object>()
|
||||||
{
|
{
|
||||||
{ TargetKey, target },
|
{ TargetCoordinates, target },
|
||||||
{ PathfindKey, path}
|
{ PathfindKey, path}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ public sealed class RotateToTargetOperator : HTNOperator
|
|||||||
_rotate = sysManager.GetEntitySystem<RotateToFaceSystem>();
|
_rotate = sysManager.GetEntitySystem<RotateToFaceSystem>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Shutdown(NPCBlackboard blackboard, HTNOperatorStatus status)
|
public override void TaskShutdown(NPCBlackboard blackboard, HTNOperatorStatus status)
|
||||||
{
|
{
|
||||||
base.Shutdown(blackboard, status);
|
base.TaskShutdown(blackboard, status);
|
||||||
blackboard.Remove<Angle>(TargetKey);
|
blackboard.Remove<Angle>(TargetKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,9 +36,9 @@ public sealed class MedibotInjectOperator : HTNOperator
|
|||||||
_solution = sysManager.GetEntitySystem<SolutionContainerSystem>();
|
_solution = sysManager.GetEntitySystem<SolutionContainerSystem>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Shutdown(NPCBlackboard blackboard, HTNOperatorStatus status)
|
public override void TaskShutdown(NPCBlackboard blackboard, HTNOperatorStatus status)
|
||||||
{
|
{
|
||||||
base.Shutdown(blackboard, status);
|
base.TaskShutdown(blackboard, status);
|
||||||
blackboard.Remove<EntityUid>(TargetKey);
|
blackboard.Remove<EntityUid>(TargetKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ public sealed class UtilityOperator : HTNOperator
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||||
|
|
||||||
[DataField("key")] public string Key = "CombatTarget";
|
[DataField("key")] public string Key = "Target";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The EntityCoordinates of the specified target.
|
/// The EntityCoordinates of the specified target.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("keyCoordinates")]
|
[DataField("keyCoordinates")]
|
||||||
public string KeyCoordinates = "CombatTargetCoordinates";
|
public string KeyCoordinates = "TargetCoordinates";
|
||||||
|
|
||||||
[DataField("proto", required: true, customTypeSerializer:typeof(PrototypeIdSerializer<UtilityQueryPrototype>))]
|
[DataField("proto", required: true, customTypeSerializer:typeof(PrototypeIdSerializer<UtilityQueryPrototype>))]
|
||||||
public string Prototype = string.Empty;
|
public string Prototype = string.Empty;
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ public sealed class WaitOperator : HTNOperator
|
|||||||
return timer <= 0f ? HTNOperatorStatus.Finished : HTNOperatorStatus.Continuing;
|
return timer <= 0f ? HTNOperatorStatus.Finished : HTNOperatorStatus.Continuing;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Shutdown(NPCBlackboard blackboard, HTNOperatorStatus status)
|
public override void TaskShutdown(NPCBlackboard blackboard, HTNOperatorStatus status)
|
||||||
{
|
{
|
||||||
base.Shutdown(blackboard, status);
|
base.TaskShutdown(blackboard, status);
|
||||||
|
|
||||||
// The replacement plan may want this value so only dump it if we're successful.
|
// The replacement plan may want this value so only dump it if we're successful.
|
||||||
if (status != HTNOperatorStatus.BetterPlan)
|
if (status != HTNOperatorStatus.BetterPlan)
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ using System.Diagnostics.CodeAnalysis;
|
|||||||
using Content.Server.Interaction;
|
using Content.Server.Interaction;
|
||||||
using Content.Shared.Access.Systems;
|
using Content.Shared.Access.Systems;
|
||||||
using Content.Shared.ActionBlocker;
|
using Content.Shared.ActionBlocker;
|
||||||
|
using Content.Shared.Hands.Components;
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
|
using Content.Shared.Inventory;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
@@ -27,9 +29,10 @@ public sealed class NPCBlackboard : IEnumerable<KeyValuePair<string, object>>
|
|||||||
{MeleeMissChance, 0.3f},
|
{MeleeMissChance, 0.3f},
|
||||||
{"MeleeRange", 1f},
|
{"MeleeRange", 1f},
|
||||||
{"MinimumIdleTime", 2f},
|
{"MinimumIdleTime", 2f},
|
||||||
|
{"MovementRangeClose", 0.2f},
|
||||||
{"MovementRange", 1.5f},
|
{"MovementRange", 1.5f},
|
||||||
{"RangedRange", 10f},
|
{"RangedRange", 10f},
|
||||||
{"RotateSpeed", MathF.PI},
|
{"RotateSpeed", float.MaxValue},
|
||||||
{"VisionRadius", 10f},
|
{"VisionRadius", 10f},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -151,6 +154,7 @@ public sealed class NPCBlackboard : IEnumerable<KeyValuePair<string, object>>
|
|||||||
switch (key)
|
switch (key)
|
||||||
{
|
{
|
||||||
case Access:
|
case Access:
|
||||||
|
{
|
||||||
if (!TryGetValue(Owner, out owner, entManager))
|
if (!TryGetValue(Owner, out owner, entManager))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -159,7 +163,33 @@ public sealed class NPCBlackboard : IEnumerable<KeyValuePair<string, object>>
|
|||||||
var access = entManager.EntitySysManager.GetEntitySystem<AccessReaderSystem>();
|
var access = entManager.EntitySysManager.GetEntitySystem<AccessReaderSystem>();
|
||||||
value = access.FindAccessTags(owner);
|
value = access.FindAccessTags(owner);
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
case ActiveHand:
|
||||||
|
{
|
||||||
|
if (!TryGetValue(Owner, out owner, entManager) ||
|
||||||
|
!entManager.TryGetComponent<HandsComponent>(owner, out var hands) ||
|
||||||
|
hands.ActiveHand == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = hands.ActiveHand;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case ActiveHandFree:
|
||||||
|
{
|
||||||
|
if (!TryGetValue(Owner, out owner, entManager) ||
|
||||||
|
!entManager.TryGetComponent<HandsComponent>(owner, out var hands) ||
|
||||||
|
hands.ActiveHand == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = hands.ActiveHand.IsEmpty;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
case CanMove:
|
case CanMove:
|
||||||
|
{
|
||||||
if (!TryGetValue(Owner, out owner, entManager))
|
if (!TryGetValue(Owner, out owner, entManager))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -168,7 +198,53 @@ public sealed class NPCBlackboard : IEnumerable<KeyValuePair<string, object>>
|
|||||||
var blocker = entManager.EntitySysManager.GetEntitySystem<ActionBlockerSystem>();
|
var blocker = entManager.EntitySysManager.GetEntitySystem<ActionBlockerSystem>();
|
||||||
value = blocker.CanMove(owner);
|
value = blocker.CanMove(owner);
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
case FreeHands:
|
||||||
|
{
|
||||||
|
if (!TryGetValue(Owner, out owner, entManager) ||
|
||||||
|
!entManager.TryGetComponent<HandsComponent>(owner, out var hands) ||
|
||||||
|
hands.ActiveHand == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var handos = new List<string>();
|
||||||
|
|
||||||
|
foreach (var (id, hand) in hands.Hands)
|
||||||
|
{
|
||||||
|
if (!hand.IsEmpty)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
handos.Add(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
value = handos;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case Inventory:
|
||||||
|
{
|
||||||
|
if (!TryGetValue(Owner, out owner, entManager) ||
|
||||||
|
!entManager.TryGetComponent<HandsComponent>(owner, out var hands) ||
|
||||||
|
hands.ActiveHand == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var handos = new List<string>();
|
||||||
|
|
||||||
|
foreach (var (id, hand) in hands.Hands)
|
||||||
|
{
|
||||||
|
if (!hand.IsEmpty)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
handos.Add(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
value = handos;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
case OwnerCoordinates:
|
case OwnerCoordinates:
|
||||||
|
{
|
||||||
if (!TryGetValue(Owner, out owner, entManager))
|
if (!TryGetValue(Owner, out owner, entManager))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -181,6 +257,7 @@ public sealed class NPCBlackboard : IEnumerable<KeyValuePair<string, object>>
|
|||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -200,8 +277,12 @@ public sealed class NPCBlackboard : IEnumerable<KeyValuePair<string, object>>
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
public const string Access = "Access";
|
public const string Access = "Access";
|
||||||
|
public const string ActiveHand = "ActiveHand";
|
||||||
|
public const string ActiveHandFree = "ActiveHandFree";
|
||||||
public const string CanMove = "CanMove";
|
public const string CanMove = "CanMove";
|
||||||
|
public const string FreeHands = "FreeHands";
|
||||||
public const string FollowTarget = "FollowTarget";
|
public const string FollowTarget = "FollowTarget";
|
||||||
|
public const string Inventory = "Inventory";
|
||||||
public const string MedibotInjectRange = "MedibotInjectRange";
|
public const string MedibotInjectRange = "MedibotInjectRange";
|
||||||
|
|
||||||
public const string MeleeMissChance = "MeleeMissChance";
|
public const string MeleeMissChance = "MeleeMissChance";
|
||||||
@@ -237,7 +318,7 @@ public sealed class NPCBlackboard : IEnumerable<KeyValuePair<string, object>>
|
|||||||
|
|
||||||
public const string RotateSpeed = "RotateSpeed";
|
public const string RotateSpeed = "RotateSpeed";
|
||||||
public const string VisionRadius = "VisionRadius";
|
public const string VisionRadius = "VisionRadius";
|
||||||
public const string UtilityTarget = "Target";
|
public const string UtilityTarget = "UtilityTarget";
|
||||||
|
|
||||||
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
|
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,31 +16,31 @@ public sealed class NPCBlackboardSerializer : ITypeReader<NPCBlackboard, Mapping
|
|||||||
{
|
{
|
||||||
var validated = new List<ValidationNode>();
|
var validated = new List<ValidationNode>();
|
||||||
|
|
||||||
if (node.Count > 0)
|
if (node.Count <= 0)
|
||||||
|
return new ValidatedSequenceNode(validated);
|
||||||
|
|
||||||
|
var reflection = dependencies.Resolve<IReflectionManager>();
|
||||||
|
|
||||||
|
foreach (var data in node)
|
||||||
{
|
{
|
||||||
var reflection = dependencies.Resolve<IReflectionManager>();
|
var key = data.Key.ToYamlNode().AsString();
|
||||||
|
|
||||||
foreach (var data in node)
|
if (data.Value.Tag == null)
|
||||||
{
|
{
|
||||||
var key = data.Key.ToYamlNode().AsString();
|
validated.Add(new ErrorNode(data.Key, $"Unable to validate {key}'s type"));
|
||||||
|
continue;
|
||||||
if (data.Value.Tag == null)
|
|
||||||
{
|
|
||||||
validated.Add(new ErrorNode(data.Key, $"Unable to validate {key}'s type"));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var typeString = data.Value.Tag[6..];
|
|
||||||
|
|
||||||
if (!reflection.TryLooseGetType(typeString, out var type))
|
|
||||||
{
|
|
||||||
validated.Add(new ErrorNode(data.Key, $"Unable to find type for {typeString}"));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var validatedNode = serializationManager.ValidateNode(type, data.Value, context);
|
|
||||||
validated.Add(validatedNode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var typeString = data.Value.Tag[6..];
|
||||||
|
|
||||||
|
if (!reflection.TryLooseGetType(typeString, out var type))
|
||||||
|
{
|
||||||
|
validated.Add(new ErrorNode(data.Key, $"Unable to find type for {typeString}"));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var validatedNode = serializationManager.ValidateNode(type, data.Value, context);
|
||||||
|
validated.Add(validatedNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ValidatedSequenceNode(validated);
|
return new ValidatedSequenceNode(validated);
|
||||||
@@ -53,29 +53,29 @@ public sealed class NPCBlackboardSerializer : ITypeReader<NPCBlackboard, Mapping
|
|||||||
{
|
{
|
||||||
var value = instanceProvider != null ? instanceProvider() : new NPCBlackboard();
|
var value = instanceProvider != null ? instanceProvider() : new NPCBlackboard();
|
||||||
|
|
||||||
if (node.Count > 0)
|
if (node.Count <= 0)
|
||||||
|
return value;
|
||||||
|
|
||||||
|
var reflection = dependencies.Resolve<IReflectionManager>();
|
||||||
|
|
||||||
|
foreach (var data in node)
|
||||||
{
|
{
|
||||||
var reflection = dependencies.Resolve<IReflectionManager>();
|
var key = data.Key.ToYamlNode().AsString();
|
||||||
|
|
||||||
foreach (var data in node)
|
if (data.Value.Tag == null)
|
||||||
{
|
throw new NullReferenceException($"Found null tag for {key}");
|
||||||
var key = data.Key.ToYamlNode().AsString();
|
|
||||||
|
|
||||||
if (data.Value.Tag == null)
|
var typeString = data.Value.Tag[6..];
|
||||||
throw new NullReferenceException($"Found null tag for {key}");
|
|
||||||
|
|
||||||
var typeString = data.Value.Tag[6..];
|
if (!reflection.TryLooseGetType(typeString, out var type))
|
||||||
|
throw new NullReferenceException($"Found null type for {key}");
|
||||||
|
|
||||||
if (!reflection.TryLooseGetType(typeString, out var type))
|
var bbData = serializationManager.Read(type, data.Value, hookCtx, context);
|
||||||
throw new NullReferenceException($"Found null type for {key}");
|
|
||||||
|
|
||||||
var bbData = serializationManager.Read(type, data.Value, hookCtx, context);
|
if (bbData == null)
|
||||||
|
throw new NullReferenceException($"Found null data for {key}, expected {type}");
|
||||||
|
|
||||||
if (bbData == null)
|
value.SetValue(key, bbData);
|
||||||
throw new NullReferenceException($"Found null data for {key}, expected {type}");
|
|
||||||
|
|
||||||
value.SetValue(key, bbData);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Content.Server.NPC.Queries.Considerations;
|
||||||
|
|
||||||
|
public sealed class TargetAmmoCon : UtilityConsideration
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Content.Server.NPC.Queries.Considerations;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns 1f where the specified target is valid for the active hand's whitelist.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class TargetAmmoMatchesCon : UtilityConsideration
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Content.Server.NPC.Queries.Considerations;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the DPS out of 100.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class TargetMeleeCon : UtilityConsideration
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,9 +2,9 @@ namespace Content.Server.NPC.Queries.Curves;
|
|||||||
|
|
||||||
public sealed class QuadraticCurve : IUtilityCurve
|
public sealed class QuadraticCurve : IUtilityCurve
|
||||||
{
|
{
|
||||||
[DataField("slope")] public readonly float Slope;
|
[DataField("slope")] public readonly float Slope = 1f;
|
||||||
|
|
||||||
[DataField("exponent")] public readonly float Exponent;
|
[DataField("exponent")] public readonly float Exponent = 1f;
|
||||||
|
|
||||||
[DataField("yOffset")] public readonly float YOffset;
|
[DataField("yOffset")] public readonly float YOffset;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
|
using Content.Shared.Inventory;
|
||||||
|
|
||||||
namespace Content.Server.NPC.Queries.Queries;
|
namespace Content.Server.NPC.Queries.Queries;
|
||||||
|
|
||||||
public sealed class ClothingSlotFilter : UtilityQueryFilter
|
public sealed class ClothingSlotFilter : UtilityQueryFilter
|
||||||
{
|
{
|
||||||
|
[DataField("slotFlags", required: true)]
|
||||||
|
public SlotFlags SlotFlags = SlotFlags.NONE;
|
||||||
}
|
}
|
||||||
|
|||||||
12
Content.Server/NPC/Queries/Queries/ComponentFilter.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Server.NPC.Queries.Queries;
|
||||||
|
|
||||||
|
public sealed class ComponentFilter : UtilityQueryFilter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Components to filter for.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("components", required: true)]
|
||||||
|
public ComponentRegistry Components = new();
|
||||||
|
}
|
||||||
9
Content.Server/NPC/Queries/Queries/InventoryQuery.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Content.Server.NPC.Queries.Queries;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns inventory entities recursively.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class InventoryQuery : UtilityQuery
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -18,66 +18,6 @@ public sealed partial class NPCCombatSystem
|
|||||||
{
|
{
|
||||||
SubscribeLocalEvent<NPCMeleeCombatComponent, ComponentStartup>(OnMeleeStartup);
|
SubscribeLocalEvent<NPCMeleeCombatComponent, ComponentStartup>(OnMeleeStartup);
|
||||||
SubscribeLocalEvent<NPCMeleeCombatComponent, ComponentShutdown>(OnMeleeShutdown);
|
SubscribeLocalEvent<NPCMeleeCombatComponent, ComponentShutdown>(OnMeleeShutdown);
|
||||||
SubscribeLocalEvent<NPCMeleeCombatComponent, NPCSteeringEvent>(OnMeleeSteering);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnMeleeSteering(EntityUid uid, NPCMeleeCombatComponent component, ref NPCSteeringEvent args)
|
|
||||||
{
|
|
||||||
args.Steering.CanSeek = true;
|
|
||||||
|
|
||||||
if (TryComp<MeleeWeaponComponent>(component.Weapon, out var weapon))
|
|
||||||
{
|
|
||||||
var cdRemaining = weapon.NextAttack - _timing.CurTime;
|
|
||||||
var attackCooldown = TimeSpan.FromSeconds(1f / _melee.GetAttackRate(component.Weapon, uid, weapon));
|
|
||||||
|
|
||||||
// Might as well get in range.
|
|
||||||
if (cdRemaining < attackCooldown * 0.45f)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!_physics.TryGetNearestPoints(uid, component.Target, out var pointA, out var pointB))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var obstacleDirection = pointB - args.WorldPosition;
|
|
||||||
|
|
||||||
// If they're moving away then pursue anyway.
|
|
||||||
// If just hit then always back up a bit.
|
|
||||||
if (cdRemaining < attackCooldown * 0.90f &&
|
|
||||||
TryComp<PhysicsComponent>(component.Target, out var targetPhysics) &&
|
|
||||||
Vector2.Dot(targetPhysics.LinearVelocity, obstacleDirection) > 0f)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cdRemaining < TimeSpan.FromSeconds(1f / _melee.GetAttackRate(component.Weapon, uid, weapon)) * 0.45f)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var idealDistance = weapon.Range * 4f;
|
|
||||||
var obstacleDistance = obstacleDirection.Length();
|
|
||||||
|
|
||||||
if (obstacleDistance > idealDistance || obstacleDistance == 0f)
|
|
||||||
{
|
|
||||||
// Don't want to get too far.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
args.Steering.CanSeek = false;
|
|
||||||
obstacleDirection = args.OffsetRotation.RotateVec(obstacleDirection);
|
|
||||||
var norm = obstacleDirection.Normalized();
|
|
||||||
|
|
||||||
var weight = (obstacleDistance <= args.AgentRadius
|
|
||||||
? 1f
|
|
||||||
: (idealDistance - obstacleDistance) / idealDistance);
|
|
||||||
|
|
||||||
for (var i = 0; i < SharedNPCSteeringSystem.InterestDirections; i++)
|
|
||||||
{
|
|
||||||
var result = -Vector2.Dot(norm, NPCSteeringSystem.Directions[i]) * weight;
|
|
||||||
|
|
||||||
if (result < 0f)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
args.Interest[i] = MathF.Max(args.Interest[i], result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnMeleeShutdown(EntityUid uid, NPCMeleeCombatComponent component, ComponentShutdown args)
|
private void OnMeleeShutdown(EntityUid uid, NPCMeleeCombatComponent component, ComponentShutdown args)
|
||||||
@@ -87,7 +27,7 @@ public sealed partial class NPCCombatSystem
|
|||||||
_combat.SetInCombatMode(uid, false, combatMode);
|
_combat.SetInCombatMode(uid, false, combatMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
_steering.Unregister(component.Owner);
|
_steering.Unregister(uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnMeleeStartup(EntityUid uid, NPCMeleeCombatComponent component, ComponentStartup args)
|
private void OnMeleeStartup(EntityUid uid, NPCMeleeCombatComponent component, ComponentStartup args)
|
||||||
@@ -96,9 +36,6 @@ public sealed partial class NPCCombatSystem
|
|||||||
{
|
{
|
||||||
_combat.SetInCombatMode(uid, true, combatMode);
|
_combat.SetInCombatMode(uid, true, combatMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Cleanup later, just looking for parity for now.
|
|
||||||
component.Weapon = uid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateMelee(float frameTime)
|
private void UpdateMelee(float frameTime)
|
||||||
@@ -107,11 +44,10 @@ public sealed partial class NPCCombatSystem
|
|||||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||||
var physicsQuery = GetEntityQuery<PhysicsComponent>();
|
var physicsQuery = GetEntityQuery<PhysicsComponent>();
|
||||||
var curTime = _timing.CurTime;
|
var curTime = _timing.CurTime;
|
||||||
|
var query = EntityQueryEnumerator<NPCMeleeCombatComponent, ActiveNPCComponent>();
|
||||||
|
|
||||||
foreach (var (comp, _) in EntityQuery<NPCMeleeCombatComponent, ActiveNPCComponent>())
|
while (query.MoveNext(out var uid, out var comp, out _))
|
||||||
{
|
{
|
||||||
var uid = comp.Owner;
|
|
||||||
|
|
||||||
if (!combatQuery.TryGetComponent(uid, out var combat) || !combat.IsInCombatMode)
|
if (!combatQuery.TryGetComponent(uid, out var combat) || !combat.IsInCombatMode)
|
||||||
{
|
{
|
||||||
RemComp<NPCMeleeCombatComponent>(uid);
|
RemComp<NPCMeleeCombatComponent>(uid);
|
||||||
@@ -126,7 +62,7 @@ public sealed partial class NPCCombatSystem
|
|||||||
{
|
{
|
||||||
component.Status = CombatStatus.Normal;
|
component.Status = CombatStatus.Normal;
|
||||||
|
|
||||||
if (!TryComp<MeleeWeaponComponent>(component.Weapon, out var weapon))
|
if (!_melee.TryGetWeapon(uid, out var weaponUid, out var weapon))
|
||||||
{
|
{
|
||||||
component.Status = CombatStatus.NoWeapon;
|
component.Status = CombatStatus.NoWeapon;
|
||||||
return;
|
return;
|
||||||
@@ -167,12 +103,6 @@ public sealed partial class NPCCombatSystem
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
steering = EnsureComp<NPCSteeringComponent>(uid);
|
|
||||||
steering.Range = MathF.Max(0.2f, weapon.Range - 0.4f);
|
|
||||||
|
|
||||||
// Gets unregistered on component shutdown.
|
|
||||||
_steering.TryRegister(uid, new EntityCoordinates(component.Target, Vector2.Zero), steering);
|
|
||||||
|
|
||||||
if (weapon.NextAttack > curTime || !Enabled)
|
if (weapon.NextAttack > curTime || !Enabled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -180,11 +110,11 @@ public sealed partial class NPCCombatSystem
|
|||||||
physicsQuery.TryGetComponent(component.Target, out var targetPhysics) &&
|
physicsQuery.TryGetComponent(component.Target, out var targetPhysics) &&
|
||||||
targetPhysics.LinearVelocity.LengthSquared() != 0f)
|
targetPhysics.LinearVelocity.LengthSquared() != 0f)
|
||||||
{
|
{
|
||||||
_melee.AttemptLightAttackMiss(uid, component.Weapon, weapon, targetXform.Coordinates.Offset(_random.NextVector2(0.5f)));
|
_melee.AttemptLightAttackMiss(uid, weaponUid, weapon, targetXform.Coordinates.Offset(_random.NextVector2(0.5f)));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_melee.AttemptLightAttack(uid, component.Weapon, weapon, component.Target);
|
_melee.AttemptLightAttack(uid, weaponUid, weapon, component.Target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
using System.Numerics;
|
|
||||||
using Content.Server.NPC.Components;
|
using Content.Server.NPC.Components;
|
||||||
using Content.Shared.CombatMode;
|
using Content.Shared.CombatMode;
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
|
using Content.Shared.Weapons.Ranged.Components;
|
||||||
|
using Content.Shared.Weapons.Ranged.Events;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Physics.Components;
|
using Robust.Shared.Physics.Components;
|
||||||
|
|
||||||
@@ -12,6 +13,12 @@ public sealed partial class NPCCombatSystem
|
|||||||
[Dependency] private readonly SharedCombatModeSystem _combat = default!;
|
[Dependency] private readonly SharedCombatModeSystem _combat = default!;
|
||||||
[Dependency] private readonly RotateToFaceSystem _rotate = default!;
|
[Dependency] private readonly RotateToFaceSystem _rotate = default!;
|
||||||
|
|
||||||
|
private EntityQuery<CombatModeComponent> _combatQuery;
|
||||||
|
private EntityQuery<NPCSteeringComponent> _steeringQuery;
|
||||||
|
private EntityQuery<RechargeBasicEntityAmmoComponent> _rechargeQuery;
|
||||||
|
private EntityQuery<PhysicsComponent> _physicsQuery;
|
||||||
|
private EntityQuery<TransformComponent> _xformQuery;
|
||||||
|
|
||||||
// TODO: Don't predict for hitscan
|
// TODO: Don't predict for hitscan
|
||||||
private const float ShootSpeed = 20f;
|
private const float ShootSpeed = 20f;
|
||||||
|
|
||||||
@@ -22,6 +29,12 @@ public sealed partial class NPCCombatSystem
|
|||||||
|
|
||||||
private void InitializeRanged()
|
private void InitializeRanged()
|
||||||
{
|
{
|
||||||
|
_combatQuery = GetEntityQuery<CombatModeComponent>();
|
||||||
|
_physicsQuery = GetEntityQuery<PhysicsComponent>();
|
||||||
|
_rechargeQuery = GetEntityQuery<RechargeBasicEntityAmmoComponent>();
|
||||||
|
_steeringQuery = GetEntityQuery<NPCSteeringComponent>();
|
||||||
|
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||||
|
|
||||||
SubscribeLocalEvent<NPCRangedCombatComponent, ComponentStartup>(OnRangedStartup);
|
SubscribeLocalEvent<NPCRangedCombatComponent, ComponentStartup>(OnRangedStartup);
|
||||||
SubscribeLocalEvent<NPCRangedCombatComponent, ComponentShutdown>(OnRangedShutdown);
|
SubscribeLocalEvent<NPCRangedCombatComponent, ComponentShutdown>(OnRangedShutdown);
|
||||||
}
|
}
|
||||||
@@ -48,9 +61,6 @@ public sealed partial class NPCCombatSystem
|
|||||||
|
|
||||||
private void UpdateRanged(float frameTime)
|
private void UpdateRanged(float frameTime)
|
||||||
{
|
{
|
||||||
var bodyQuery = GetEntityQuery<PhysicsComponent>();
|
|
||||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
|
||||||
var combatQuery = GetEntityQuery<CombatModeComponent>();
|
|
||||||
var query = EntityQueryEnumerator<NPCRangedCombatComponent, TransformComponent>();
|
var query = EntityQueryEnumerator<NPCRangedCombatComponent, TransformComponent>();
|
||||||
|
|
||||||
while (query.MoveNext(out var uid, out var comp, out var xform))
|
while (query.MoveNext(out var uid, out var comp, out var xform))
|
||||||
@@ -58,8 +68,15 @@ public sealed partial class NPCCombatSystem
|
|||||||
if (comp.Status == CombatStatus.Unspecified)
|
if (comp.Status == CombatStatus.Unspecified)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!xformQuery.TryGetComponent(comp.Target, out var targetXform) ||
|
if (_steeringQuery.TryGetComponent(uid, out var steering) && steering.Status == SteeringStatus.NoPath)
|
||||||
!bodyQuery.TryGetComponent(comp.Target, out var targetBody))
|
{
|
||||||
|
comp.Status = CombatStatus.TargetUnreachable;
|
||||||
|
comp.ShootAccumulator = 0f;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_xformQuery.TryGetComponent(comp.Target, out var targetXform) ||
|
||||||
|
!_physicsQuery.TryGetComponent(comp.Target, out var targetBody))
|
||||||
{
|
{
|
||||||
comp.Status = CombatStatus.TargetUnreachable;
|
comp.Status = CombatStatus.TargetUnreachable;
|
||||||
comp.ShootAccumulator = 0f;
|
comp.ShootAccumulator = 0f;
|
||||||
@@ -73,7 +90,7 @@ public sealed partial class NPCCombatSystem
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (combatQuery.TryGetComponent(uid, out var combatMode))
|
if (_combatQuery.TryGetComponent(uid, out var combatMode))
|
||||||
{
|
{
|
||||||
_combat.SetInCombatMode(uid, true, combatMode);
|
_combat.SetInCombatMode(uid, true, combatMode);
|
||||||
}
|
}
|
||||||
@@ -85,10 +102,26 @@ public sealed partial class NPCCombatSystem
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ammoEv = new GetAmmoCountEvent();
|
||||||
|
RaiseLocalEvent(gunUid, ref ammoEv);
|
||||||
|
|
||||||
|
if (ammoEv.Count == 0)
|
||||||
|
{
|
||||||
|
// Recharging then?
|
||||||
|
if (_rechargeQuery.HasComponent(gunUid))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
comp.Status = CombatStatus.Unspecified;
|
||||||
|
comp.ShootAccumulator = 0f;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
comp.LOSAccumulator -= frameTime;
|
comp.LOSAccumulator -= frameTime;
|
||||||
|
|
||||||
var (worldPos, worldRot) = _transform.GetWorldPositionRotation(xform, xformQuery);
|
var worldPos = _transform.GetWorldPosition(xform);
|
||||||
var (targetPos, targetRot) = _transform.GetWorldPositionRotation(targetXform, xformQuery);
|
var targetPos = _transform.GetWorldPosition(targetXform);
|
||||||
|
|
||||||
// We'll work out the projected spot of the target and shoot there instead of where they are.
|
// We'll work out the projected spot of the target and shoot there instead of where they are.
|
||||||
var distance = (targetPos - worldPos).Length();
|
var distance = (targetPos - worldPos).Length();
|
||||||
@@ -105,7 +138,7 @@ public sealed partial class NPCCombatSystem
|
|||||||
if (!comp.TargetInLOS)
|
if (!comp.TargetInLOS)
|
||||||
{
|
{
|
||||||
comp.ShootAccumulator = 0f;
|
comp.ShootAccumulator = 0f;
|
||||||
comp.Status = CombatStatus.TargetUnreachable;
|
comp.Status = CombatStatus.NotInSight;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,6 +189,7 @@ public sealed partial class NPCCombatSystem
|
|||||||
}
|
}
|
||||||
|
|
||||||
_gun.AttemptShoot(uid, gunUid, gun, targetCordinates);
|
_gun.AttemptShoot(uid, gunUid, gun, targetCordinates);
|
||||||
|
comp.Status = CombatStatus.Normal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
206
Content.Server/NPC/Systems/NPCJukeSystem.cs
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Content.Server.NPC.Components;
|
||||||
|
using Content.Server.NPC.Events;
|
||||||
|
using Content.Server.NPC.HTN.PrimitiveTasks.Operators.Combat;
|
||||||
|
using Content.Server.Weapons.Melee;
|
||||||
|
using Content.Shared.NPC;
|
||||||
|
using Content.Shared.Weapons.Melee;
|
||||||
|
using Robust.Shared.Collections;
|
||||||
|
using Robust.Shared.Map.Components;
|
||||||
|
using Robust.Shared.Physics.Components;
|
||||||
|
using Robust.Shared.Physics.Systems;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
|
namespace Content.Server.NPC.Systems;
|
||||||
|
|
||||||
|
public sealed class NPCJukeSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IGameTiming _timing = default!;
|
||||||
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||||
|
[Dependency] private readonly MeleeWeaponSystem _melee = default!;
|
||||||
|
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||||
|
|
||||||
|
private EntityQuery<NPCMeleeCombatComponent> _npcMeleeQuery;
|
||||||
|
private EntityQuery<NPCRangedCombatComponent> _npcRangedQuery;
|
||||||
|
private EntityQuery<PhysicsComponent> _physicsQuery;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
_npcMeleeQuery = GetEntityQuery<NPCMeleeCombatComponent>();
|
||||||
|
_npcRangedQuery = GetEntityQuery<NPCRangedCombatComponent>();
|
||||||
|
_physicsQuery = GetEntityQuery<PhysicsComponent>();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<NPCJukeComponent, EntityUnpausedEvent>(OnJukeUnpaused);
|
||||||
|
SubscribeLocalEvent<NPCJukeComponent, NPCSteeringEvent>(OnJukeSteering);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnJukeUnpaused(EntityUid uid, NPCJukeComponent component, ref EntityUnpausedEvent args)
|
||||||
|
{
|
||||||
|
component.NextJuke += args.PausedTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnJukeSteering(EntityUid uid, NPCJukeComponent component, ref NPCSteeringEvent args)
|
||||||
|
{
|
||||||
|
if (component.JukeType == JukeType.AdjacentTile)
|
||||||
|
{
|
||||||
|
if (_npcRangedQuery.TryGetComponent(uid, out var ranged) &&
|
||||||
|
ranged.Status == CombatStatus.NotInSight)
|
||||||
|
{
|
||||||
|
component.TargetTile = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_timing.CurTime < component.NextJuke)
|
||||||
|
{
|
||||||
|
component.TargetTile = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryComp<MapGridComponent>(args.Transform.GridUid, out var grid))
|
||||||
|
{
|
||||||
|
component.TargetTile = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentTile = grid.CoordinatesToTile(args.Transform.Coordinates);
|
||||||
|
|
||||||
|
if (component.TargetTile == null)
|
||||||
|
{
|
||||||
|
var targetTile = currentTile;
|
||||||
|
var startIndex = _random.Next(8);
|
||||||
|
_physicsQuery.TryGetComponent(uid, out var ownerPhysics);
|
||||||
|
var collisionLayer = ownerPhysics?.CollisionLayer ?? 0;
|
||||||
|
var collisionMask = ownerPhysics?.CollisionMask ?? 0;
|
||||||
|
|
||||||
|
for (var i = 0; i < 8; i++)
|
||||||
|
{
|
||||||
|
var index = (startIndex + i) % 8;
|
||||||
|
var neighbor = ((Direction) index).ToIntVec() + currentTile;
|
||||||
|
var valid = true;
|
||||||
|
|
||||||
|
// TODO: Probably make this a helper on engine maybe
|
||||||
|
var tileBounds = new Box2(neighbor, neighbor + grid.TileSize);
|
||||||
|
tileBounds = tileBounds.Enlarged(-0.1f);
|
||||||
|
|
||||||
|
foreach (var ent in _lookup.GetEntitiesIntersecting(args.Transform.GridUid.Value, tileBounds))
|
||||||
|
{
|
||||||
|
if (ent == uid ||
|
||||||
|
!_physicsQuery.TryGetComponent(ent, out var physics) ||
|
||||||
|
!physics.CanCollide ||
|
||||||
|
!physics.Hard ||
|
||||||
|
((physics.CollisionMask & collisionLayer) == 0x0 &&
|
||||||
|
(physics.CollisionLayer & collisionMask) == 0x0))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
valid = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!valid)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
targetTile = neighbor;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
component.TargetTile ??= targetTile;
|
||||||
|
}
|
||||||
|
|
||||||
|
var elapsed = _timing.CurTime - component.NextJuke;
|
||||||
|
|
||||||
|
// Finished juke, reset timer.
|
||||||
|
if (elapsed.TotalSeconds > component.JukeDuration ||
|
||||||
|
currentTile == component.TargetTile)
|
||||||
|
{
|
||||||
|
component.TargetTile = null;
|
||||||
|
component.NextJuke = _timing.CurTime + TimeSpan.FromSeconds(component.JukeDuration);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetCoords = grid.GridTileToWorld(component.TargetTile.Value);
|
||||||
|
var targetDir = (targetCoords.Position - args.WorldPosition);
|
||||||
|
targetDir = args.OffsetRotation.RotateVec(targetDir);
|
||||||
|
const float weight = 1f;
|
||||||
|
var norm = targetDir.Normalized();
|
||||||
|
|
||||||
|
for (var i = 0; i < SharedNPCSteeringSystem.InterestDirections; i++)
|
||||||
|
{
|
||||||
|
var result = -Vector2.Dot(norm, NPCSteeringSystem.Directions[i]) * weight;
|
||||||
|
|
||||||
|
if (result < 0f)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
args.Steering.Interest[i] = MathF.Max(args.Steering.Interest[i], result);
|
||||||
|
}
|
||||||
|
|
||||||
|
args.Steering.CanSeek = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (component.JukeType == JukeType.Away)
|
||||||
|
{
|
||||||
|
// TODO: Ranged away juking
|
||||||
|
if (_npcMeleeQuery.TryGetComponent(uid, out var melee))
|
||||||
|
{
|
||||||
|
if (!_melee.TryGetWeapon(uid, out var weaponUid, out var weapon))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var cdRemaining = weapon.NextAttack - _timing.CurTime;
|
||||||
|
var attackCooldown = TimeSpan.FromSeconds(1f / _melee.GetAttackRate(weaponUid, uid, weapon));
|
||||||
|
|
||||||
|
// Might as well get in range.
|
||||||
|
if (cdRemaining < attackCooldown * 0.45f)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!_physics.TryGetNearestPoints(uid, melee.Target, out var pointA, out var pointB))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var obstacleDirection = pointB - args.WorldPosition;
|
||||||
|
|
||||||
|
// If they're moving away then pursue anyway.
|
||||||
|
// If just hit then always back up a bit.
|
||||||
|
if (cdRemaining < attackCooldown * 0.90f &&
|
||||||
|
TryComp<PhysicsComponent>(melee.Target, out var targetPhysics) &&
|
||||||
|
Vector2.Dot(targetPhysics.LinearVelocity, obstacleDirection) > 0f)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cdRemaining < TimeSpan.FromSeconds(1f / _melee.GetAttackRate(weaponUid, uid, weapon)) * 0.45f)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var idealDistance = weapon.Range * 4f;
|
||||||
|
var obstacleDistance = obstacleDirection.Length();
|
||||||
|
|
||||||
|
if (obstacleDistance > idealDistance || obstacleDistance == 0f)
|
||||||
|
{
|
||||||
|
// Don't want to get too far.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
obstacleDirection = args.OffsetRotation.RotateVec(obstacleDirection);
|
||||||
|
var norm = obstacleDirection.Normalized();
|
||||||
|
|
||||||
|
var weight = obstacleDistance <= args.Steering.Radius
|
||||||
|
? 1f
|
||||||
|
: (idealDistance - obstacleDistance) / idealDistance;
|
||||||
|
|
||||||
|
for (var i = 0; i < SharedNPCSteeringSystem.InterestDirections; i++)
|
||||||
|
{
|
||||||
|
var result = -Vector2.Dot(norm, NPCSteeringSystem.Directions[i]) * weight;
|
||||||
|
|
||||||
|
if (result < 0f)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
args.Steering.Interest[i] = MathF.Max(args.Steering.Interest[i], result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
args.Steering.CanSeek = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -77,12 +77,43 @@ public sealed partial class NPCSteeringSystem
|
|||||||
{
|
{
|
||||||
var ourCoordinates = xform.Coordinates;
|
var ourCoordinates = xform.Coordinates;
|
||||||
var destinationCoordinates = steering.Coordinates;
|
var destinationCoordinates = steering.Coordinates;
|
||||||
|
var inLos = true;
|
||||||
|
|
||||||
|
// Check if we're in LOS if that's required.
|
||||||
|
// TODO: Need something uhh better not sure on the interaction between these.
|
||||||
|
if (steering.ArriveOnLineOfSight)
|
||||||
|
{
|
||||||
|
// TODO: use vision range
|
||||||
|
inLos = _interaction.InRangeUnobstructed(uid, steering.Coordinates, 10f);
|
||||||
|
|
||||||
|
if (inLos)
|
||||||
|
{
|
||||||
|
steering.LineOfSightTimer += frameTime;
|
||||||
|
|
||||||
|
if (steering.LineOfSightTimer >= steering.LineOfSightTimeRequired)
|
||||||
|
{
|
||||||
|
steering.Status = SteeringStatus.InRange;
|
||||||
|
ResetStuck(steering, ourCoordinates);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
steering.LineOfSightTimer = 0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
steering.LineOfSightTimer = 0f;
|
||||||
|
}
|
||||||
|
|
||||||
// We've arrived, nothing else matters.
|
// We've arrived, nothing else matters.
|
||||||
if (xform.Coordinates.TryDistance(EntityManager, destinationCoordinates, out var distance) &&
|
if (xform.Coordinates.TryDistance(EntityManager, destinationCoordinates, out var targetDistance) &&
|
||||||
distance <= steering.Range)
|
inLos &&
|
||||||
|
targetDistance <= steering.Range)
|
||||||
{
|
{
|
||||||
steering.Status = SteeringStatus.InRange;
|
steering.Status = SteeringStatus.InRange;
|
||||||
|
ResetStuck(steering, ourCoordinates);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +148,7 @@ public sealed partial class NPCSteeringSystem
|
|||||||
// This is to avoid popping it too early
|
// This is to avoid popping it too early
|
||||||
else if (steering.CurrentPath.TryPeek(out var node) && IsFreeSpace(uid, steering, node))
|
else if (steering.CurrentPath.TryPeek(out var node) && IsFreeSpace(uid, steering, node))
|
||||||
{
|
{
|
||||||
arrivalDistance = MathF.Min(node.Box.Width / 2f, node.Box.Height / 2f) - 0.01f;
|
arrivalDistance = MathF.Max(0.05f, MathF.Min(node.Box.Width / 2f, node.Box.Height / 2f) - 0.05f);
|
||||||
}
|
}
|
||||||
// Try getting into blocked range I guess?
|
// Try getting into blocked range I guess?
|
||||||
// TODO: Consider melee range or the likes.
|
// TODO: Consider melee range or the likes.
|
||||||
@@ -172,7 +203,7 @@ public sealed partial class NPCSteeringSystem
|
|||||||
steering.Status = SteeringStatus.NoPath;
|
steering.Status = SteeringStatus.NoPath;
|
||||||
return false;
|
return false;
|
||||||
case SteeringObstacleStatus.Continuing:
|
case SteeringObstacleStatus.Continuing:
|
||||||
CheckPath(uid, steering, xform, needsPath, distance);
|
CheckPath(uid, steering, xform, needsPath, targetDistance);
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
throw new ArgumentOutOfRangeException();
|
throw new ArgumentOutOfRangeException();
|
||||||
@@ -205,9 +236,7 @@ public sealed partial class NPCSteeringSystem
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// This probably shouldn't happen as we check above but eh.
|
needsPath = true;
|
||||||
steering.Status = SteeringStatus.NoPath;
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Stuck detection
|
// Stuck detection
|
||||||
@@ -228,8 +257,13 @@ public sealed partial class NPCSteeringSystem
|
|||||||
// B) NPCs still try to move in locked containers (e.g. cow, hamster)
|
// B) NPCs still try to move in locked containers (e.g. cow, hamster)
|
||||||
// and I don't want to spam grafana even harder than it gets spammed rn.
|
// and I don't want to spam grafana even harder than it gets spammed rn.
|
||||||
Log.Debug($"NPC {ToPrettyString(uid)} found stuck at {ourCoordinates}");
|
Log.Debug($"NPC {ToPrettyString(uid)} found stuck at {ourCoordinates}");
|
||||||
steering.Status = SteeringStatus.NoPath;
|
needsPath = true;
|
||||||
return false;
|
|
||||||
|
if (stuckTime.TotalSeconds > maxStuckTime * 3)
|
||||||
|
{
|
||||||
|
steering.Status = SteeringStatus.NoPath;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -237,14 +271,14 @@ public sealed partial class NPCSteeringSystem
|
|||||||
ResetStuck(steering, ourCoordinates);
|
ResetStuck(steering, ourCoordinates);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do we have no more nodes to follow OR has the target moved sufficiently? If so then re-path.
|
// If not in LOS and no path then get a new one fam.
|
||||||
if (!needsPath)
|
if (!inLos && steering.CurrentPath.Count == 0)
|
||||||
{
|
{
|
||||||
needsPath = steering.CurrentPath.Count == 0 || (steering.CurrentPath.Peek().Data.Flags & PathfindingBreadcrumbFlag.Invalid) != 0x0;
|
needsPath = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Probably need partial planning support i.e. patch from the last node to where the target moved to.
|
// TODO: Probably need partial planning support i.e. patch from the last node to where the target moved to.
|
||||||
CheckPath(uid, steering, xform, needsPath, distance);
|
CheckPath(uid, steering, xform, needsPath, targetDistance);
|
||||||
|
|
||||||
// If we don't have a path yet then do nothing; this is to avoid stutter-stepping if it turns out there's no path
|
// If we don't have a path yet then do nothing; this is to avoid stutter-stepping if it turns out there's no path
|
||||||
// available but we assume there was.
|
// available but we assume there was.
|
||||||
@@ -295,8 +329,10 @@ public sealed partial class NPCSteeringSystem
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!needsPath)
|
if (!needsPath && steering.CurrentPath.Count > 0)
|
||||||
{
|
{
|
||||||
|
needsPath = steering.CurrentPath.Count > 0 && (steering.CurrentPath.Peek().Data.Flags & PathfindingBreadcrumbFlag.Invalid) != 0x0;
|
||||||
|
|
||||||
// If the target has sufficiently moved.
|
// If the target has sufficiently moved.
|
||||||
var lastNode = GetCoordinates(steering.CurrentPath.Last());
|
var lastNode = GetCoordinates(steering.CurrentPath.Last());
|
||||||
|
|
||||||
@@ -357,10 +393,6 @@ public sealed partial class NPCSteeringSystem
|
|||||||
mask = (CollisionGroup) physics.CollisionMask;
|
mask = (CollisionGroup) physics.CollisionMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have to backtrack (for example, we're behind a table and the target is on the other side)
|
|
||||||
// Then don't consider pruning.
|
|
||||||
var goal = nodes.Last().Coordinates.ToMap(EntityManager, _transform);
|
|
||||||
|
|
||||||
for (var i = 0; i < nodes.Count; i++)
|
for (var i = 0; i < nodes.Count; i++)
|
||||||
{
|
{
|
||||||
var node = nodes[i];
|
var node = nodes[i];
|
||||||
@@ -451,7 +483,9 @@ public sealed partial class NPCSteeringSystem
|
|||||||
|
|
||||||
var xformB = _xformQuery.GetComponent(ent);
|
var xformB = _xformQuery.GetComponent(ent);
|
||||||
|
|
||||||
if (!_physics.TryGetNearest(uid, ent, out var pointA, out var pointB, out var distance, xform, xformB))
|
if (!_physics.TryGetNearest(uid, ent,
|
||||||
|
out var pointA, out var pointB, out var distance,
|
||||||
|
xform, xformB))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -508,8 +542,7 @@ public sealed partial class NPCSteeringSystem
|
|||||||
var objectRadius = 0.25f;
|
var objectRadius = 0.25f;
|
||||||
var detectionRadius = MathF.Max(0.35f, agentRadius + objectRadius);
|
var detectionRadius = MathF.Max(0.35f, agentRadius + objectRadius);
|
||||||
var ourVelocity = body.LinearVelocity;
|
var ourVelocity = body.LinearVelocity;
|
||||||
var factionQuery = GetEntityQuery<NpcFactionMemberComponent>();
|
_factionQuery.TryGetComponent(uid, out var ourFaction);
|
||||||
factionQuery.TryGetComponent(uid, out var ourFaction);
|
|
||||||
|
|
||||||
foreach (var ent in _lookup.GetEntitiesInRange(uid, detectionRadius, LookupFlags.Dynamic))
|
foreach (var ent in _lookup.GetEntitiesInRange(uid, detectionRadius, LookupFlags.Dynamic))
|
||||||
{
|
{
|
||||||
@@ -520,7 +553,7 @@ public sealed partial class NPCSteeringSystem
|
|||||||
!otherBody.CanCollide ||
|
!otherBody.CanCollide ||
|
||||||
(mask & otherBody.CollisionLayer) == 0x0 &&
|
(mask & otherBody.CollisionLayer) == 0x0 &&
|
||||||
(layer & otherBody.CollisionMask) == 0x0 ||
|
(layer & otherBody.CollisionMask) == 0x0 ||
|
||||||
!factionQuery.TryGetComponent(ent, out var otherFaction) ||
|
!_factionQuery.TryGetComponent(ent, out var otherFaction) ||
|
||||||
!_npcFaction.IsEntityFriendly(uid, ent, ourFaction, otherFaction) ||
|
!_npcFaction.IsEntityFriendly(uid, ent, ourFaction, otherFaction) ||
|
||||||
// Use <= 0 so we ignore stationary friends in case.
|
// Use <= 0 so we ignore stationary friends in case.
|
||||||
Vector2.Dot(otherBody.LinearVelocity, ourVelocity) <= 0f)
|
Vector2.Dot(otherBody.LinearVelocity, ourVelocity) <= 0f)
|
||||||
|
|||||||
@@ -65,8 +65,9 @@ public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem
|
|||||||
[Dependency] private readonly SharedCombatModeSystem _combat = default!;
|
[Dependency] private readonly SharedCombatModeSystem _combat = default!;
|
||||||
|
|
||||||
private EntityQuery<FixturesComponent> _fixturesQuery;
|
private EntityQuery<FixturesComponent> _fixturesQuery;
|
||||||
private EntityQuery<PhysicsComponent> _physicsQuery;
|
|
||||||
private EntityQuery<MovementSpeedModifierComponent> _modifierQuery;
|
private EntityQuery<MovementSpeedModifierComponent> _modifierQuery;
|
||||||
|
private EntityQuery<NpcFactionMemberComponent> _factionQuery;
|
||||||
|
private EntityQuery<PhysicsComponent> _physicsQuery;
|
||||||
private EntityQuery<TransformComponent> _xformQuery;
|
private EntityQuery<TransformComponent> _xformQuery;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -89,8 +90,9 @@ public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem
|
|||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
_fixturesQuery = GetEntityQuery<FixturesComponent>();
|
_fixturesQuery = GetEntityQuery<FixturesComponent>();
|
||||||
_physicsQuery = GetEntityQuery<PhysicsComponent>();
|
|
||||||
_modifierQuery = GetEntityQuery<MovementSpeedModifierComponent>();
|
_modifierQuery = GetEntityQuery<MovementSpeedModifierComponent>();
|
||||||
|
_factionQuery = GetEntityQuery<NpcFactionMemberComponent>();
|
||||||
|
_physicsQuery = GetEntityQuery<PhysicsComponent>();
|
||||||
_xformQuery = GetEntityQuery<TransformComponent>();
|
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
@@ -238,8 +240,16 @@ public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// Not every mob has the modifier component so do it as a separate query.
|
// Not every mob has the modifier component so do it as a separate query.
|
||||||
var npcs = EntityQuery<ActiveNPCComponent, NPCSteeringComponent, InputMoverComponent, TransformComponent>()
|
var npcs = new (EntityUid, NPCSteeringComponent, InputMoverComponent, TransformComponent)[Count<ActiveNPCComponent>()];
|
||||||
.Select(o => (o.Item1.Owner, o.Item2, o.Item3, o.Item4)).ToArray();
|
|
||||||
|
var query = EntityQueryEnumerator<ActiveNPCComponent, NPCSteeringComponent, InputMoverComponent, TransformComponent>();
|
||||||
|
var index = 0;
|
||||||
|
|
||||||
|
while (query.MoveNext(out var uid, out _, out var steering, out var mover, out var xform))
|
||||||
|
{
|
||||||
|
npcs[index] = (uid, steering, mover, xform);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
// Dependency issues across threads.
|
// Dependency issues across threads.
|
||||||
var options = new ParallelOptions
|
var options = new ParallelOptions
|
||||||
@@ -248,7 +258,7 @@ public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem
|
|||||||
};
|
};
|
||||||
var curTime = _timing.CurTime;
|
var curTime = _timing.CurTime;
|
||||||
|
|
||||||
Parallel.For(0, npcs.Length, options, i =>
|
Parallel.For(0, index, options, i =>
|
||||||
{
|
{
|
||||||
var (uid, steering, mover, xform) = npcs[i];
|
var (uid, steering, mover, xform) = npcs[i];
|
||||||
Steer(uid, steering, mover, xform, frameTime, curTime);
|
Steer(uid, steering, mover, xform, frameTime, curTime);
|
||||||
@@ -257,10 +267,12 @@ public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem
|
|||||||
|
|
||||||
if (_subscribedSessions.Count > 0)
|
if (_subscribedSessions.Count > 0)
|
||||||
{
|
{
|
||||||
var data = new List<NPCSteeringDebugData>(npcs.Length);
|
var data = new List<NPCSteeringDebugData>(index);
|
||||||
|
|
||||||
foreach (var (uid, steering, mover, _) in npcs)
|
for (var i = 0; i < index; i++)
|
||||||
{
|
{
|
||||||
|
var (uid, steering, mover, _) = npcs[i];
|
||||||
|
|
||||||
data.Add(new NPCSteeringDebugData(
|
data.Add(new NPCSteeringDebugData(
|
||||||
uid,
|
uid,
|
||||||
mover.CurTickSprintMovement,
|
mover.CurTickSprintMovement,
|
||||||
@@ -341,7 +353,9 @@ public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem
|
|||||||
steering.Danger[i] = 0f;
|
steering.Danger[i] = 0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
var ev = new NPCSteeringEvent(steering, interest, danger, agentRadius, offsetRot, worldPos);
|
steering.CanSeek = true;
|
||||||
|
|
||||||
|
var ev = new NPCSteeringEvent(steering, xform, worldPos, offsetRot);
|
||||||
RaiseLocalEvent(uid, ref ev);
|
RaiseLocalEvent(uid, ref ev);
|
||||||
// If seek has arrived at the target node for example then immediately re-steer.
|
// If seek has arrived at the target node for example then immediately re-steer.
|
||||||
var forceSteer = true;
|
var forceSteer = true;
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ namespace Content.Server.NPC.Systems
|
|||||||
[Dependency] private readonly HTNSystem _htn = default!;
|
[Dependency] private readonly HTNSystem _htn = default!;
|
||||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||||
|
|
||||||
private ISawmill _sawmill = default!;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether any NPCs are allowed to run at all.
|
/// Whether any NPCs are allowed to run at all.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -35,8 +33,6 @@ namespace Content.Server.NPC.Systems
|
|||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
_sawmill = Logger.GetSawmill("npc");
|
|
||||||
_sawmill.Level = LogLevel.Info;
|
|
||||||
SubscribeLocalEvent<NPCComponent, MobStateChangedEvent>(OnMobStateChange);
|
SubscribeLocalEvent<NPCComponent, MobStateChangedEvent>(OnMobStateChange);
|
||||||
SubscribeLocalEvent<NPCComponent, MapInitEvent>(OnNPCMapInit);
|
SubscribeLocalEvent<NPCComponent, MapInitEvent>(OnNPCMapInit);
|
||||||
SubscribeLocalEvent<NPCComponent, ComponentShutdown>(OnNPCShutdown);
|
SubscribeLocalEvent<NPCComponent, ComponentShutdown>(OnNPCShutdown);
|
||||||
@@ -98,7 +94,7 @@ namespace Content.Server.NPC.Systems
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_sawmill.Debug($"Waking {ToPrettyString(uid)}");
|
Log.Debug($"Waking {ToPrettyString(uid)}");
|
||||||
EnsureComp<ActiveNPCComponent>(uid);
|
EnsureComp<ActiveNPCComponent>(uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +105,19 @@ namespace Content.Server.NPC.Systems
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_sawmill.Debug($"Sleeping {ToPrettyString(uid)}");
|
// Don't bother with an event
|
||||||
|
if (TryComp<HTNComponent>(uid, out var htn))
|
||||||
|
{
|
||||||
|
if (htn.Plan != null)
|
||||||
|
{
|
||||||
|
var currentOperator = htn.Plan.CurrentOperator;
|
||||||
|
_htn.ShutdownTask(currentOperator, htn.Blackboard, HTNOperatorStatus.Failed);
|
||||||
|
_htn.ShutdownPlan(htn);
|
||||||
|
htn.Plan = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Debug($"Sleeping {ToPrettyString(uid)}");
|
||||||
RemComp<ActiveNPCComponent>(uid);
|
RemComp<ActiveNPCComponent>(uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,11 +11,18 @@ using Content.Server.Nutrition.EntitySystems;
|
|||||||
using Content.Server.Storage.Components;
|
using Content.Server.Storage.Components;
|
||||||
using Content.Shared.Examine;
|
using Content.Shared.Examine;
|
||||||
using Content.Shared.Fluids.Components;
|
using Content.Shared.Fluids.Components;
|
||||||
|
using Content.Shared.Hands.Components;
|
||||||
|
using Content.Shared.Inventory;
|
||||||
using Content.Shared.Mobs.Systems;
|
using Content.Shared.Mobs.Systems;
|
||||||
using Content.Shared.Nutrition.Components;
|
using Content.Shared.Nutrition.Components;
|
||||||
|
using Content.Shared.Weapons.Melee;
|
||||||
|
using Content.Shared.Weapons.Ranged.Components;
|
||||||
|
using Content.Shared.Weapons.Ranged.Events;
|
||||||
|
using Microsoft.Extensions.ObjectPool;
|
||||||
using Robust.Server.Containers;
|
using Robust.Server.Containers;
|
||||||
using Robust.Shared.Collections;
|
using Robust.Shared.Collections;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Server.NPC.Systems;
|
namespace Content.Server.NPC.Systems;
|
||||||
|
|
||||||
@@ -24,17 +31,29 @@ namespace Content.Server.NPC.Systems;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class NPCUtilitySystem : EntitySystem
|
public sealed class NPCUtilitySystem : EntitySystem
|
||||||
{
|
{
|
||||||
|
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||||
[Dependency] private readonly ContainerSystem _container = default!;
|
[Dependency] private readonly ContainerSystem _container = default!;
|
||||||
[Dependency] private readonly DrinkSystem _drink = default!;
|
[Dependency] private readonly DrinkSystem _drink = default!;
|
||||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||||
[Dependency] private readonly FoodSystem _food = default!;
|
[Dependency] private readonly FoodSystem _food = default!;
|
||||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||||
[Dependency] private readonly NpcFactionSystem _npcFaction = default!;
|
[Dependency] private readonly NpcFactionSystem _npcFaction = default!;
|
||||||
[Dependency] private readonly PuddleSystem _puddle = default!;
|
[Dependency] private readonly PuddleSystem _puddle = default!;
|
||||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||||
[Dependency] private readonly SolutionContainerSystem _solutions = default!;
|
[Dependency] private readonly SolutionContainerSystem _solutions = default!;
|
||||||
|
|
||||||
|
private EntityQuery<TransformComponent> _xformQuery;
|
||||||
|
|
||||||
|
private ObjectPool<HashSet<EntityUid>> _entPool =
|
||||||
|
new DefaultObjectPool<HashSet<EntityUid>>(new SetPolicy<EntityUid>(), 256);
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runs the UtilityQueryPrototype and returns the best-matching entities.
|
/// Runs the UtilityQueryPrototype and returns the best-matching entities.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -47,7 +66,7 @@ public sealed class NPCUtilitySystem : EntitySystem
|
|||||||
// TODO: PickHostilesop or whatever needs to juse be UtilityQueryOperator
|
// TODO: PickHostilesop or whatever needs to juse be UtilityQueryOperator
|
||||||
|
|
||||||
var weh = _proto.Index<UtilityQueryPrototype>(proto);
|
var weh = _proto.Index<UtilityQueryPrototype>(proto);
|
||||||
var ents = new HashSet<EntityUid>();
|
var ents = _entPool.Get();
|
||||||
|
|
||||||
foreach (var query in weh.Query)
|
foreach (var query in weh.Query)
|
||||||
{
|
{
|
||||||
@@ -63,7 +82,10 @@ public sealed class NPCUtilitySystem : EntitySystem
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ents.Count == 0)
|
if (ents.Count == 0)
|
||||||
|
{
|
||||||
|
_entPool.Return(ents);
|
||||||
return UtilityResult.Empty;
|
return UtilityResult.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
var results = new Dictionary<EntityUid, float>();
|
var results = new Dictionary<EntityUid, float>();
|
||||||
var highestScore = 0f;
|
var highestScore = 0f;
|
||||||
@@ -101,6 +123,7 @@ public sealed class NPCUtilitySystem : EntitySystem
|
|||||||
|
|
||||||
var result = new UtilityResult(results);
|
var result = new UtilityResult(results);
|
||||||
blackboard.Remove<EntityUid>(NPCBlackboard.UtilityTarget);
|
blackboard.Remove<EntityUid>(NPCBlackboard.UtilityTarget);
|
||||||
|
_entPool.Return(ents);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +138,7 @@ public sealed class NPCUtilitySystem : EntitySystem
|
|||||||
case PresetCurve presetCurve:
|
case PresetCurve presetCurve:
|
||||||
return GetScore(_proto.Index<UtilityCurvePresetPrototype>(presetCurve.Preset).Curve, conScore);
|
return GetScore(_proto.Index<UtilityCurvePresetPrototype>(presetCurve.Preset).Curve, conScore);
|
||||||
case QuadraticCurve quadraticCurve:
|
case QuadraticCurve quadraticCurve:
|
||||||
return Math.Clamp(quadraticCurve.Slope * (float) Math.Pow(conScore - quadraticCurve.XOffset, quadraticCurve.Exponent) + quadraticCurve.YOffset, 0f, 1f);
|
return Math.Clamp(quadraticCurve.Slope * MathF.Pow(conScore - quadraticCurve.XOffset, quadraticCurve.Exponent) + quadraticCurve.YOffset, 0f, 1f);
|
||||||
default:
|
default:
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
@@ -189,6 +212,21 @@ public sealed class NPCUtilitySystem : EntitySystem
|
|||||||
// TODO: Pathfind there, though probably do it in a separate con.
|
// TODO: Pathfind there, though probably do it in a separate con.
|
||||||
return 1f;
|
return 1f;
|
||||||
}
|
}
|
||||||
|
case TargetAmmoMatchesCon:
|
||||||
|
{
|
||||||
|
if (!blackboard.TryGetValue(NPCBlackboard.ActiveHand, out Hand? activeHand, EntityManager) ||
|
||||||
|
!TryComp<BallisticAmmoProviderComponent>(activeHand.HeldEntity, out var heldGun))
|
||||||
|
{
|
||||||
|
return 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (heldGun.Whitelist?.IsValid(targetUid, EntityManager) != true)
|
||||||
|
{
|
||||||
|
return 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1f;
|
||||||
|
}
|
||||||
case TargetDistanceCon:
|
case TargetDistanceCon:
|
||||||
{
|
{
|
||||||
var radius = blackboard.GetValueOrDefault<float>(NPCBlackboard.VisionRadius, EntityManager);
|
var radius = blackboard.GetValueOrDefault<float>(NPCBlackboard.VisionRadius, EntityManager);
|
||||||
@@ -207,6 +245,23 @@ public sealed class NPCUtilitySystem : EntitySystem
|
|||||||
|
|
||||||
return Math.Clamp(distance / radius, 0f, 1f);
|
return Math.Clamp(distance / radius, 0f, 1f);
|
||||||
}
|
}
|
||||||
|
case TargetAmmoCon:
|
||||||
|
{
|
||||||
|
if (!HasComp<GunComponent>(targetUid))
|
||||||
|
return 0f;
|
||||||
|
|
||||||
|
var ev = new GetAmmoCountEvent();
|
||||||
|
RaiseLocalEvent(targetUid, ref ev);
|
||||||
|
|
||||||
|
if (ev.Count == 0)
|
||||||
|
return 0f;
|
||||||
|
|
||||||
|
// Wat
|
||||||
|
if (ev.Capacity == 0)
|
||||||
|
return 1f;
|
||||||
|
|
||||||
|
return (float) ev.Count / ev.Capacity;
|
||||||
|
}
|
||||||
case TargetHealthCon:
|
case TargetHealthCon:
|
||||||
{
|
{
|
||||||
return 0f;
|
return 0f;
|
||||||
@@ -222,7 +277,7 @@ public sealed class NPCUtilitySystem : EntitySystem
|
|||||||
var radius = blackboard.GetValueOrDefault<float>(NPCBlackboard.VisionRadius, EntityManager);
|
var radius = blackboard.GetValueOrDefault<float>(NPCBlackboard.VisionRadius, EntityManager);
|
||||||
const float bufferRange = 0.5f;
|
const float bufferRange = 0.5f;
|
||||||
|
|
||||||
if (blackboard.TryGetValue<EntityUid>("CombatTarget", out var currentTarget, EntityManager) &&
|
if (blackboard.TryGetValue<EntityUid>("Target", out var currentTarget, EntityManager) &&
|
||||||
currentTarget == targetUid &&
|
currentTarget == targetUid &&
|
||||||
TryComp<TransformComponent>(owner, out var xform) &&
|
TryComp<TransformComponent>(owner, out var xform) &&
|
||||||
TryComp<TransformComponent>(targetUid, out var targetXform) &&
|
TryComp<TransformComponent>(targetUid, out var targetXform) &&
|
||||||
@@ -246,6 +301,15 @@ public sealed class NPCUtilitySystem : EntitySystem
|
|||||||
{
|
{
|
||||||
return _mobState.IsDead(targetUid) ? 1f : 0f;
|
return _mobState.IsDead(targetUid) ? 1f : 0f;
|
||||||
}
|
}
|
||||||
|
case TargetMeleeCon:
|
||||||
|
{
|
||||||
|
if (TryComp<MeleeWeaponComponent>(targetUid, out var melee))
|
||||||
|
{
|
||||||
|
return melee.Damage.Total.Float() * melee.AttackRate / 100f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0f;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
@@ -275,40 +339,109 @@ public sealed class NPCUtilitySystem : EntitySystem
|
|||||||
switch (query)
|
switch (query)
|
||||||
{
|
{
|
||||||
case ComponentQuery compQuery:
|
case ComponentQuery compQuery:
|
||||||
|
{
|
||||||
var mapPos = Transform(owner).MapPosition;
|
var mapPos = Transform(owner).MapPosition;
|
||||||
foreach (var compReg in compQuery.Components.Values)
|
var comps = compQuery.Components.Values.ToList();
|
||||||
|
var compZero = comps[0];
|
||||||
|
comps.RemoveAt(0);
|
||||||
|
|
||||||
|
foreach (var comp in _lookup.GetComponentsInRange(compZero.Component.GetType(), mapPos, vision))
|
||||||
{
|
{
|
||||||
foreach (var comp in _lookup.GetComponentsInRange(compReg.Component.GetType(), mapPos, vision))
|
var ent = comp.Owner;
|
||||||
|
|
||||||
|
if (ent == owner)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var othersFound = true;
|
||||||
|
|
||||||
|
foreach (var compOther in comps)
|
||||||
{
|
{
|
||||||
var ent = comp.Owner;
|
if (!HasComp(ent, compOther.Component.GetType()))
|
||||||
|
{
|
||||||
|
othersFound = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (ent == owner)
|
if (!othersFound)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
entities.Add(ent);
|
entities.Add(ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case InventoryQuery:
|
||||||
|
{
|
||||||
|
if (!_inventory.TryGetContainerSlotEnumerator(owner, out var enumerator))
|
||||||
|
break;
|
||||||
|
|
||||||
|
while (enumerator.MoveNext(out var slot))
|
||||||
|
{
|
||||||
|
foreach (var child in slot.ContainedEntities)
|
||||||
|
{
|
||||||
|
RecursiveAdd(child, entities);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case NearbyHostilesQuery:
|
case NearbyHostilesQuery:
|
||||||
|
{
|
||||||
foreach (var ent in _npcFaction.GetNearbyHostiles(owner, vision))
|
foreach (var ent in _npcFaction.GetNearbyHostiles(owner, vision))
|
||||||
{
|
{
|
||||||
entities.Add(ent);
|
entities.Add(ent);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RecursiveAdd(EntityUid uid, HashSet<EntityUid> entities)
|
||||||
|
{
|
||||||
|
// TODO: Probably need a recursive struct enumerator on engine.
|
||||||
|
var xform = _xformQuery.GetComponent(uid);
|
||||||
|
var enumerator = xform.ChildEnumerator;
|
||||||
|
entities.Add(uid);
|
||||||
|
|
||||||
|
while (enumerator.MoveNext(out var child))
|
||||||
|
{
|
||||||
|
RecursiveAdd(child.Value, entities);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void Filter(NPCBlackboard blackboard, HashSet<EntityUid> entities, UtilityQueryFilter filter)
|
private void Filter(NPCBlackboard blackboard, HashSet<EntityUid> entities, UtilityQueryFilter filter)
|
||||||
{
|
{
|
||||||
switch (filter)
|
switch (filter)
|
||||||
{
|
{
|
||||||
|
case ComponentFilter compFilter:
|
||||||
|
{
|
||||||
|
var toRemove = new ValueList<EntityUid>();
|
||||||
|
|
||||||
|
foreach (var ent in entities)
|
||||||
|
{
|
||||||
|
foreach (var comp in compFilter.Components)
|
||||||
|
{
|
||||||
|
if (HasComp(ent, comp.Value.Component.GetType()))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
toRemove.Add(ent);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var ent in toRemove)
|
||||||
|
{
|
||||||
|
entities.Remove(ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
case PuddleFilter:
|
case PuddleFilter:
|
||||||
{
|
{
|
||||||
var puddleQuery = GetEntityQuery<PuddleComponent>();
|
var puddleQuery = GetEntityQuery<PuddleComponent>();
|
||||||
|
|
||||||
var toRemove = new ValueList<EntityUid>();
|
var toRemove = new ValueList<EntityUid>();
|
||||||
|
|
||||||
foreach (var ent in entities)
|
foreach (var ent in entities)
|
||||||
|
|||||||
@@ -232,7 +232,7 @@ namespace Content.Server.Zombies
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var htn = EnsureComp<HTNComponent>(target);
|
var htn = EnsureComp<HTNComponent>(target);
|
||||||
htn.RootTask = "SimpleHostileCompound";
|
htn.RootTask = new HTNCompoundTask() {Task = "SimpleHostileCompound"};
|
||||||
htn.Blackboard.SetValue(NPCBlackboard.Owner, target);
|
htn.Blackboard.SetValue(NPCBlackboard.Owner, target);
|
||||||
_npc.WakeNPC(target, htn);
|
_npc.WakeNPC(target, htn);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
namespace Content.Shared.Inventory;
|
namespace Content.Shared.Inventory;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ public abstract partial class SharedGunSystem
|
|||||||
protected virtual void InitializeChamberMagazine()
|
protected virtual void InitializeChamberMagazine()
|
||||||
{
|
{
|
||||||
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, TakeAmmoEvent>(OnChamberMagazineTakeAmmo);
|
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, TakeAmmoEvent>(OnChamberMagazineTakeAmmo);
|
||||||
|
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, GetAmmoCountEvent>(OnChamberAmmoCount);
|
||||||
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, GetVerbsEvent<AlternativeVerb>>(OnMagazineVerb);
|
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, GetVerbsEvent<AlternativeVerb>>(OnMagazineVerb);
|
||||||
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, EntInsertedIntoContainerMessage>(OnMagazineSlotChange);
|
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, EntInsertedIntoContainerMessage>(OnMagazineSlotChange);
|
||||||
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, EntRemovedFromContainerMessage>(OnMagazineSlotChange);
|
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, EntRemovedFromContainerMessage>(OnMagazineSlotChange);
|
||||||
@@ -73,6 +74,18 @@ public abstract partial class SharedGunSystem
|
|||||||
slot.Insert(ammo);
|
slot.Insert(ammo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnChamberAmmoCount(EntityUid uid, ChamberMagazineAmmoProviderComponent component, ref GetAmmoCountEvent args)
|
||||||
|
{
|
||||||
|
OnMagazineAmmoCount(uid, component, ref args);
|
||||||
|
args.Capacity += 1;
|
||||||
|
var chambered = GetChamberEntity(uid);
|
||||||
|
|
||||||
|
if (chambered != null)
|
||||||
|
{
|
||||||
|
args.Count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void OnChamberMagazineTakeAmmo(EntityUid uid, ChamberMagazineAmmoProviderComponent component, TakeAmmoEvent args)
|
private void OnChamberMagazineTakeAmmo(EntityUid uid, ChamberMagazineAmmoProviderComponent component, TakeAmmoEvent args)
|
||||||
{
|
{
|
||||||
// So chamber logic is kinda sussier than the others
|
// So chamber logic is kinda sussier than the others
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ public abstract partial class SharedGunSystem
|
|||||||
protected virtual void InitializeMagazine()
|
protected virtual void InitializeMagazine()
|
||||||
{
|
{
|
||||||
SubscribeLocalEvent<MagazineAmmoProviderComponent, TakeAmmoEvent>(OnMagazineTakeAmmo);
|
SubscribeLocalEvent<MagazineAmmoProviderComponent, TakeAmmoEvent>(OnMagazineTakeAmmo);
|
||||||
|
SubscribeLocalEvent<MagazineAmmoProviderComponent, GetAmmoCountEvent>(OnMagazineAmmoCount);
|
||||||
SubscribeLocalEvent<MagazineAmmoProviderComponent, GetVerbsEvent<AlternativeVerb>>(OnMagazineVerb);
|
SubscribeLocalEvent<MagazineAmmoProviderComponent, GetVerbsEvent<AlternativeVerb>>(OnMagazineVerb);
|
||||||
SubscribeLocalEvent<MagazineAmmoProviderComponent, EntInsertedIntoContainerMessage>(OnMagazineSlotChange);
|
SubscribeLocalEvent<MagazineAmmoProviderComponent, EntInsertedIntoContainerMessage>(OnMagazineSlotChange);
|
||||||
SubscribeLocalEvent<MagazineAmmoProviderComponent, EntRemovedFromContainerMessage>(OnMagazineSlotChange);
|
SubscribeLocalEvent<MagazineAmmoProviderComponent, EntRemovedFromContainerMessage>(OnMagazineSlotChange);
|
||||||
@@ -96,6 +97,16 @@ public abstract partial class SharedGunSystem
|
|||||||
return slot.ContainedEntity;
|
return slot.ContainedEntity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnMagazineAmmoCount(EntityUid uid, MagazineAmmoProviderComponent component, ref GetAmmoCountEvent args)
|
||||||
|
{
|
||||||
|
var magEntity = GetMagazineEntity(uid);
|
||||||
|
|
||||||
|
if (magEntity == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
RaiseLocalEvent(magEntity.Value, ref args);
|
||||||
|
}
|
||||||
|
|
||||||
private void OnMagazineTakeAmmo(EntityUid uid, MagazineAmmoProviderComponent component, TakeAmmoEvent args)
|
private void OnMagazineTakeAmmo(EntityUid uid, MagazineAmmoProviderComponent component, TakeAmmoEvent args)
|
||||||
{
|
{
|
||||||
var magEntity = GetMagazineEntity(uid);
|
var magEntity = GetMagazineEntity(uid);
|
||||||
|
|||||||
@@ -133,7 +133,8 @@
|
|||||||
- type: InputMover
|
- type: InputMover
|
||||||
- type: MobMover
|
- type: MobMover
|
||||||
- type: HTN
|
- type: HTN
|
||||||
rootTask: SimpleHostileCompound
|
rootTask:
|
||||||
|
task: SimpleHostileCompound
|
||||||
- type: NpcFactionMember
|
- type: NpcFactionMember
|
||||||
factions:
|
factions:
|
||||||
- SimpleHostile
|
- SimpleHostile
|
||||||
@@ -470,7 +471,8 @@
|
|||||||
- type: Body
|
- type: Body
|
||||||
prototype: AnimalRuminant
|
prototype: AnimalRuminant
|
||||||
- type: HTN
|
- type: HTN
|
||||||
rootTask: RuminantCompound
|
rootTask:
|
||||||
|
task: RuminantCompound
|
||||||
- type: GuideHelp
|
- type: GuideHelp
|
||||||
guides:
|
guides:
|
||||||
- Chef
|
- Chef
|
||||||
@@ -588,7 +590,8 @@
|
|||||||
- type: Body
|
- type: Body
|
||||||
prototype: AnimalRuminant
|
prototype: AnimalRuminant
|
||||||
- type: HTN
|
- type: HTN
|
||||||
rootTask: RuminantCompound
|
rootTask:
|
||||||
|
task: RuminantCompound
|
||||||
|
|
||||||
# Note that we gotta make this bitch vomit someday when you feed it anthrax or sumthin. Needs to be a small item thief too and aggressive if attacked.
|
# Note that we gotta make this bitch vomit someday when you feed it anthrax or sumthin. Needs to be a small item thief too and aggressive if attacked.
|
||||||
- type: entity
|
- type: entity
|
||||||
@@ -763,7 +766,8 @@
|
|||||||
- type: Loadout
|
- type: Loadout
|
||||||
prototypes: [ BoxingKangarooGear ]
|
prototypes: [ BoxingKangarooGear ]
|
||||||
- type: HTN
|
- type: HTN
|
||||||
rootTask: SimpleHostileCompound
|
rootTask:
|
||||||
|
task: SimpleHostileCompound
|
||||||
- type: NpcFactionMember
|
- type: NpcFactionMember
|
||||||
factions:
|
factions:
|
||||||
- SimpleHostile
|
- SimpleHostile
|
||||||
@@ -906,7 +910,8 @@
|
|||||||
factions:
|
factions:
|
||||||
- Mouse
|
- Mouse
|
||||||
- type: HTN
|
- type: HTN
|
||||||
rootTask: MouseCompound
|
rootTask:
|
||||||
|
task: MouseCompound
|
||||||
- type: Physics
|
- type: Physics
|
||||||
- type: Fixtures
|
- type: Fixtures
|
||||||
fixtures:
|
fixtures:
|
||||||
@@ -1286,7 +1291,8 @@
|
|||||||
- type: InputMover
|
- type: InputMover
|
||||||
- type: MobMover
|
- type: MobMover
|
||||||
- type: HTN
|
- type: HTN
|
||||||
rootTask: SimpleHostileCompound
|
rootTask:
|
||||||
|
task: SimpleHostileCompound
|
||||||
- type: NpcFactionMember
|
- type: NpcFactionMember
|
||||||
factions:
|
factions:
|
||||||
- Syndicate
|
- Syndicate
|
||||||
@@ -1562,7 +1568,8 @@
|
|||||||
- type: InputMover
|
- type: InputMover
|
||||||
- type: MobMover
|
- type: MobMover
|
||||||
- type: HTN
|
- type: HTN
|
||||||
rootTask: SimpleHostileCompound
|
rootTask:
|
||||||
|
task: SimpleHostileCompound
|
||||||
- type: GhostRole
|
- type: GhostRole
|
||||||
makeSentient: true
|
makeSentient: true
|
||||||
name: ghost-role-information-giant-spider-name
|
name: ghost-role-information-giant-spider-name
|
||||||
@@ -1866,7 +1873,8 @@
|
|||||||
- type: InputMover
|
- type: InputMover
|
||||||
- type: MobMover
|
- type: MobMover
|
||||||
- type: HTN
|
- type: HTN
|
||||||
rootTask: SimpleHostileCompound
|
rootTask:
|
||||||
|
task: SimpleHostileCompound
|
||||||
- type: NpcFactionMember
|
- type: NpcFactionMember
|
||||||
factions:
|
factions:
|
||||||
- SimpleHostile
|
- SimpleHostile
|
||||||
|
|||||||
@@ -12,7 +12,8 @@
|
|||||||
description: ghost-role-information-behonker-description
|
description: ghost-role-information-behonker-description
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: HTN
|
- type: HTN
|
||||||
rootTask: SimpleHostileCompound
|
rootTask:
|
||||||
|
task: SimpleHostileCompound
|
||||||
- type: NpcFactionMember
|
- type: NpcFactionMember
|
||||||
factions:
|
factions:
|
||||||
- SimpleHostile
|
- SimpleHostile
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
- type: InputMover
|
- type: InputMover
|
||||||
- type: MobMover
|
- type: MobMover
|
||||||
- type: HTN
|
- type: HTN
|
||||||
rootTask: SimpleHostileCompound
|
rootTask:
|
||||||
|
task: SimpleHostileCompound
|
||||||
blackboard:
|
blackboard:
|
||||||
NavSmash: !type:Bool
|
NavSmash: !type:Bool
|
||||||
true
|
true
|
||||||
@@ -20,7 +21,7 @@
|
|||||||
sprite: Mobs/Aliens/Carps/space.rsi
|
sprite: Mobs/Aliens/Carps/space.rsi
|
||||||
layers:
|
layers:
|
||||||
- map: [ "enum.DamageStateVisualLayers.Base" ]
|
- map: [ "enum.DamageStateVisualLayers.Base" ]
|
||||||
state: base
|
state: alive
|
||||||
- type: CombatMode
|
- type: CombatMode
|
||||||
- type: Physics
|
- type: Physics
|
||||||
- type: Fixtures
|
- type: Fixtures
|
||||||
@@ -45,10 +46,10 @@
|
|||||||
- type: DamageStateVisuals
|
- type: DamageStateVisuals
|
||||||
states:
|
states:
|
||||||
Alive:
|
Alive:
|
||||||
Base: base
|
Base: alive
|
||||||
BaseUnshaded: mouth
|
BaseUnshaded: mouth
|
||||||
Dead:
|
Dead:
|
||||||
Base: base_dead
|
Base: dead
|
||||||
BaseUnshaded: dead_mouth
|
BaseUnshaded: dead_mouth
|
||||||
- type: Butcherable
|
- type: Butcherable
|
||||||
spawned:
|
spawned:
|
||||||
@@ -82,14 +83,14 @@
|
|||||||
- type: Sprite
|
- type: Sprite
|
||||||
layers:
|
layers:
|
||||||
- map: [ "enum.DamageStateVisualLayers.Base" ]
|
- map: [ "enum.DamageStateVisualLayers.Base" ]
|
||||||
state: base
|
state: alive
|
||||||
- map: [ "enum.DamageStateVisualLayers.BaseUnshaded" ]
|
- map: [ "enum.DamageStateVisualLayers.BaseUnshaded" ]
|
||||||
state: mouth
|
state: mouth
|
||||||
shader: unshaded
|
shader: unshaded
|
||||||
- type: RandomSprite
|
- type: RandomSprite
|
||||||
available:
|
available:
|
||||||
- enum.DamageStateVisualLayers.Base:
|
- enum.DamageStateVisualLayers.Base:
|
||||||
base: Rainbow
|
alive: Rainbow
|
||||||
enum.DamageStateVisualLayers.BaseUnshaded:
|
enum.DamageStateVisualLayers.BaseUnshaded:
|
||||||
mouth: ""
|
mouth: ""
|
||||||
|
|
||||||
@@ -145,7 +146,7 @@
|
|||||||
name: space carp
|
name: space carp
|
||||||
id: MobCarpDragon
|
id: MobCarpDragon
|
||||||
suffix: DragonBrood
|
suffix: DragonBrood
|
||||||
parent: BaseMobCarp
|
parent: MobCarp
|
||||||
components:
|
components:
|
||||||
- type: GhostRole
|
- type: GhostRole
|
||||||
allowMovement: true
|
allowMovement: true
|
||||||
@@ -155,7 +156,8 @@
|
|||||||
description: ghost-role-information-sentient-carp-description
|
description: ghost-role-information-sentient-carp-description
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: HTN
|
- type: HTN
|
||||||
rootTask: DragonCarpCompound
|
rootTask:
|
||||||
|
task: DragonCarpCompound
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: MobCarpDungeon
|
id: MobCarpDungeon
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
abstract: true
|
abstract: true
|
||||||
components:
|
components:
|
||||||
- type: HTN
|
- type: HTN
|
||||||
rootTask: SimpleHostileCompound
|
rootTask:
|
||||||
|
task: SimpleHostileCompound
|
||||||
blackboard:
|
blackboard:
|
||||||
NavSmash: !type:Bool
|
NavSmash: !type:Bool
|
||||||
true
|
true
|
||||||
|
|||||||
@@ -12,6 +12,23 @@
|
|||||||
factions:
|
factions:
|
||||||
- NanoTrasen
|
- NanoTrasen
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
name: Salvager
|
||||||
|
parent: BaseMobHuman
|
||||||
|
id: MobSalvager
|
||||||
|
components:
|
||||||
|
- type: NpcFactionMember
|
||||||
|
factions:
|
||||||
|
- Syndicate
|
||||||
|
- type: Loadout
|
||||||
|
prototypes:
|
||||||
|
- SalvageSpecialistGear
|
||||||
|
- type: InputMover
|
||||||
|
- type: MobMover
|
||||||
|
- type: HTN
|
||||||
|
rootTask:
|
||||||
|
task: SimpleHumanoidHostileCompound
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: Spirate
|
name: Spirate
|
||||||
parent: BaseMobHuman
|
parent: BaseMobHuman
|
||||||
@@ -21,10 +38,14 @@
|
|||||||
- type: NpcFactionMember
|
- type: NpcFactionMember
|
||||||
factions:
|
factions:
|
||||||
- Syndicate
|
- Syndicate
|
||||||
|
- type: Loadout
|
||||||
|
prototypes:
|
||||||
|
- PirateGear
|
||||||
- type: InputMover
|
- type: InputMover
|
||||||
- type: MobMover
|
- type: MobMover
|
||||||
- type: HTN
|
- type: HTN
|
||||||
rootTask: RangedCombatCompound
|
rootTask:
|
||||||
|
task: SimpleHumanoidHostileCompound
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: BaseMobHuman
|
parent: BaseMobHuman
|
||||||
|
|||||||
@@ -90,7 +90,8 @@
|
|||||||
factions:
|
factions:
|
||||||
- PetsNT
|
- PetsNT
|
||||||
- type: HTN
|
- type: HTN
|
||||||
rootTask: SimpleHostileCompound
|
rootTask:
|
||||||
|
task: SimpleHostileCompound
|
||||||
- type: Grammar
|
- type: Grammar
|
||||||
attributes:
|
attributes:
|
||||||
gender: female
|
gender: female
|
||||||
@@ -487,7 +488,8 @@
|
|||||||
state: shiva
|
state: shiva
|
||||||
sprite: Mobs/Pets/shiva.rsi
|
sprite: Mobs/Pets/shiva.rsi
|
||||||
- type: HTN
|
- type: HTN
|
||||||
rootTask: SimpleHostileCompound
|
rootTask:
|
||||||
|
task: SimpleHostileCompound
|
||||||
- type: Physics
|
- type: Physics
|
||||||
- type: Fixtures
|
- type: Fixtures
|
||||||
fixtures:
|
fixtures:
|
||||||
|
|||||||
@@ -11,7 +11,8 @@
|
|||||||
- type: InputMover
|
- type: InputMover
|
||||||
- type: MobMover
|
- type: MobMover
|
||||||
- type: HTN
|
- type: HTN
|
||||||
rootTask: SimpleHostileCompound
|
rootTask:
|
||||||
|
task: SimpleHostileCompound
|
||||||
- type: Reactive
|
- type: Reactive
|
||||||
groups:
|
groups:
|
||||||
Flammable: [Touch]
|
Flammable: [Touch]
|
||||||
@@ -176,7 +177,8 @@
|
|||||||
- type: InputMover
|
- type: InputMover
|
||||||
- type: MobMover
|
- type: MobMover
|
||||||
- type: HTN
|
- type: HTN
|
||||||
rootTask: SimpleHostileCompound
|
rootTask:
|
||||||
|
task: SimpleHostileCompound
|
||||||
- type: Reactive
|
- type: Reactive
|
||||||
groups:
|
groups:
|
||||||
Flammable: [Touch]
|
Flammable: [Touch]
|
||||||
|
|||||||
@@ -227,7 +227,8 @@
|
|||||||
baseSprintSpeed: 3
|
baseSprintSpeed: 3
|
||||||
- type: NoSlip
|
- type: NoSlip
|
||||||
- type: HTN
|
- type: HTN
|
||||||
rootTask: CleanbotCompound
|
rootTask:
|
||||||
|
task: CleanbotCompound
|
||||||
- type: DrainableSolution
|
- type: DrainableSolution
|
||||||
solution: drainBuffer
|
solution: drainBuffer
|
||||||
- type: InteractionPopup
|
- type: InteractionPopup
|
||||||
@@ -256,7 +257,8 @@
|
|||||||
state: medibot
|
state: medibot
|
||||||
- type: Speech
|
- type: Speech
|
||||||
- type: HTN
|
- type: HTN
|
||||||
rootTask: MedibotCompound
|
rootTask:
|
||||||
|
task: MedibotCompound
|
||||||
- type: Construction
|
- type: Construction
|
||||||
graph: MediBot
|
graph: MediBot
|
||||||
node: bot
|
node: bot
|
||||||
|
|||||||
@@ -16,7 +16,8 @@
|
|||||||
- type: InputMover
|
- type: InputMover
|
||||||
- type: MobMover
|
- type: MobMover
|
||||||
- type: HTN
|
- type: HTN
|
||||||
rootTask: IdleCompound
|
rootTask:
|
||||||
|
task: IdleCompound
|
||||||
- type: Input
|
- type: Input
|
||||||
context: "human"
|
context: "human"
|
||||||
- type: NpcFactionMember
|
- type: NpcFactionMember
|
||||||
|
|||||||
@@ -9,7 +9,8 @@
|
|||||||
factions:
|
factions:
|
||||||
- SimpleNeutral
|
- SimpleNeutral
|
||||||
- type: HTN
|
- type: HTN
|
||||||
rootTask: SimpleHostileCompound
|
rootTask:
|
||||||
|
task: SimpleHostileCompound
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
drawdepth: Mobs
|
drawdepth: Mobs
|
||||||
sprite: Mobs/Aliens/slimes.rsi
|
sprite: Mobs/Aliens/slimes.rsi
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
description: It looks friendly. Why don't you give it a hug?
|
description: It looks friendly. Why don't you give it a hug?
|
||||||
components:
|
components:
|
||||||
- type: HTN
|
- type: HTN
|
||||||
rootTask: SimpleHostileCompound
|
rootTask:
|
||||||
|
task: SimpleHostileCompound
|
||||||
- type: NpcFactionMember
|
- type: NpcFactionMember
|
||||||
factions:
|
factions:
|
||||||
- SimpleHostile
|
- SimpleHostile
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
- type: InputMover
|
- type: InputMover
|
||||||
- type: MobMover
|
- type: MobMover
|
||||||
- type: HTN
|
- type: HTN
|
||||||
rootTask: SimpleHostileCompound
|
rootTask:
|
||||||
|
task: SimpleHostileCompound
|
||||||
- type: NpcFactionMember
|
- type: NpcFactionMember
|
||||||
factions:
|
factions:
|
||||||
- SimpleHostile
|
- SimpleHostile
|
||||||
|
|||||||
@@ -10,7 +10,8 @@
|
|||||||
- type: InputMover
|
- type: InputMover
|
||||||
- type: MobMover
|
- type: MobMover
|
||||||
- type: HTN
|
- type: HTN
|
||||||
rootTask: XenoCompound
|
rootTask:
|
||||||
|
task: XenoCompound
|
||||||
blackboard:
|
blackboard:
|
||||||
NavInteract: !type:Bool
|
NavInteract: !type:Bool
|
||||||
true
|
true
|
||||||
@@ -330,8 +331,24 @@
|
|||||||
thresholds:
|
thresholds:
|
||||||
0: Alive
|
0: Alive
|
||||||
75: Dead
|
75: Dead
|
||||||
|
- type: HTN
|
||||||
|
rootTask:
|
||||||
|
task: SimpleRangedHostileCompound
|
||||||
- type: Stamina
|
- type: Stamina
|
||||||
excess: 300
|
excess: 300
|
||||||
|
- type: RechargeBasicEntityAmmo
|
||||||
|
rechargeCooldown: 0.75
|
||||||
|
- type: BasicEntityAmmoProvider
|
||||||
|
proto: BulletAcid
|
||||||
|
capacity: 1
|
||||||
|
count: 1
|
||||||
|
- type: Gun
|
||||||
|
fireRate: 0.75
|
||||||
|
useKey: false
|
||||||
|
selectedMode: FullAuto
|
||||||
|
availableModes:
|
||||||
|
- FullAuto
|
||||||
|
soundGunshot: /Audio/Weapons/Xeno/alien_spitacid.ogg
|
||||||
- type: SlowOnDamage
|
- type: SlowOnDamage
|
||||||
speedModifierThresholds:
|
speedModifierThresholds:
|
||||||
50: 0.4
|
50: 0.4
|
||||||
@@ -374,7 +391,8 @@
|
|||||||
- type: InputMover
|
- type: InputMover
|
||||||
- type: MobMover
|
- type: MobMover
|
||||||
- type: HTN
|
- type: HTN
|
||||||
rootTask: SimpleHostileCompound
|
rootTask:
|
||||||
|
task: SimpleHostileCompound
|
||||||
- type: NpcFactionMember
|
- type: NpcFactionMember
|
||||||
factions:
|
factions:
|
||||||
- Xeno
|
- Xeno
|
||||||
|
|||||||
@@ -14,7 +14,8 @@
|
|||||||
description: ghost-role-information-space-dragon-description
|
description: ghost-role-information-space-dragon-description
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: HTN
|
- type: HTN
|
||||||
rootTask: XenoCompound
|
rootTask:
|
||||||
|
task: XenoCompound
|
||||||
blackboard:
|
blackboard:
|
||||||
NavInteract: !type:Bool
|
NavInteract: !type:Bool
|
||||||
true
|
true
|
||||||
|
|||||||
@@ -94,9 +94,6 @@
|
|||||||
- type: InventorySlots
|
- type: InventorySlots
|
||||||
- type: Clickable
|
- type: Clickable
|
||||||
- type: InteractionOutline
|
- type: InteractionOutline
|
||||||
- type: Icon
|
|
||||||
sprite: Mobs/Species/Human/parts.rsi
|
|
||||||
state: full
|
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
noRot: true
|
noRot: true
|
||||||
drawdepth: Mobs
|
drawdepth: Mobs
|
||||||
|
|||||||
@@ -106,7 +106,8 @@
|
|||||||
proto: CartridgeCaselessRifle
|
proto: CartridgeCaselessRifle
|
||||||
capacity: 500
|
capacity: 500
|
||||||
- type: HTN
|
- type: HTN
|
||||||
rootTask: TurretCompound
|
rootTask:
|
||||||
|
task: TurretCompound
|
||||||
blackboard:
|
blackboard:
|
||||||
SoundTargetInLOS: !type:SoundPathSpecifier
|
SoundTargetInLOS: !type:SoundPathSpecifier
|
||||||
path: /Audio/Effects/double_beep.ogg
|
path: /Audio/Effects/double_beep.ogg
|
||||||
@@ -211,7 +212,8 @@
|
|||||||
selectedMode: FullAuto
|
selectedMode: FullAuto
|
||||||
soundGunshot: /Audio/Weapons/Xeno/alien_spitacid.ogg
|
soundGunshot: /Audio/Weapons/Xeno/alien_spitacid.ogg
|
||||||
- type: HTN
|
- type: HTN
|
||||||
rootTask: TurretCompound
|
rootTask:
|
||||||
|
task: TurretCompound
|
||||||
blackboard:
|
blackboard:
|
||||||
SoundTargetInLOS: !type:SoundPathSpecifier
|
SoundTargetInLOS: !type:SoundPathSpecifier
|
||||||
path: /Audio/Animals/snake_hiss.ogg
|
path: /Audio/Animals/snake_hiss.ogg
|
||||||
|
|||||||
158
Resources/Prototypes/NPCs/Combat/gun.yml
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
# Tries to shoot a target at range.
|
||||||
|
- type: htnCompound
|
||||||
|
id: GunCombatCompound
|
||||||
|
branches:
|
||||||
|
# Pick target, then move into range and shoot them.
|
||||||
|
- tasks:
|
||||||
|
- !type:HTNPrimitiveTask
|
||||||
|
operator: !type:UtilityOperator
|
||||||
|
proto: NearbyGunTargets
|
||||||
|
|
||||||
|
- !type:HTNPrimitiveTask
|
||||||
|
operator: !type:MoveToOperator
|
||||||
|
shutdownState: PlanFinished
|
||||||
|
pathfindInPlanning: true
|
||||||
|
removeKeyOnFinish: false
|
||||||
|
targetKey: TargetCoordinates
|
||||||
|
pathfindKey: TargetPathfind
|
||||||
|
stopOnLineOfSight: true
|
||||||
|
rangeKey: MeleeRange
|
||||||
|
|
||||||
|
- !type:HTNPrimitiveTask
|
||||||
|
operator: !type:JukeOperator
|
||||||
|
jukeType: AdjacentTile
|
||||||
|
|
||||||
|
- !type:HTNPrimitiveTask
|
||||||
|
preconditions:
|
||||||
|
- !type:KeyExistsPrecondition
|
||||||
|
key: Target
|
||||||
|
operator: !type:GunOperator
|
||||||
|
targetKey: Target
|
||||||
|
services:
|
||||||
|
- !type:UtilityService
|
||||||
|
id: RangedService
|
||||||
|
proto: NearbyGunTargets
|
||||||
|
key: Target
|
||||||
|
|
||||||
|
# Selects ammo in range, then moves to it and picks it up
|
||||||
|
- type: htnCompound
|
||||||
|
id: PickupAmmoCompound
|
||||||
|
branches:
|
||||||
|
# Find ammo then pick it up.
|
||||||
|
- tasks:
|
||||||
|
- !type:HTNPrimitiveTask
|
||||||
|
operator: !type:UtilityOperator
|
||||||
|
proto: NearbyAmmo
|
||||||
|
|
||||||
|
- !type:HTNPrimitiveTask
|
||||||
|
operator: !type:MoveToOperator
|
||||||
|
|
||||||
|
- !type:HTNPrimitiveTask
|
||||||
|
preconditions:
|
||||||
|
- !type:TargetInRangePrecondition
|
||||||
|
targetKey: Target
|
||||||
|
rangeKey: InteractRange
|
||||||
|
operator: !type:InteractWithOperator
|
||||||
|
targetKey: Target
|
||||||
|
# TODO: Prioritise ammo for weapon we have equipped, otherwise grab anything if we don't have any.
|
||||||
|
# TODO: Only works on ballistic
|
||||||
|
|
||||||
|
# Selects a gun in range, then moves to it and picks it up.
|
||||||
|
- type: htnCompound
|
||||||
|
id: PickupGunCompound
|
||||||
|
branches:
|
||||||
|
- tasks:
|
||||||
|
- !type:HTNPrimitiveTask
|
||||||
|
operator: !type:UtilityOperator
|
||||||
|
proto: NearbyGuns
|
||||||
|
|
||||||
|
- !type:HTNPrimitiveTask
|
||||||
|
operator: !type:MoveToOperator
|
||||||
|
|
||||||
|
- !type:HTNPrimitiveTask
|
||||||
|
preconditions:
|
||||||
|
- !type:TargetInRangePrecondition
|
||||||
|
targetKey: Target
|
||||||
|
rangeKey: InteractRange
|
||||||
|
operator: !type:InteractWithOperator
|
||||||
|
targetKey: Target
|
||||||
|
|
||||||
|
# TODO: Need a thing to recharge a laser gun
|
||||||
|
# TODO: When selecting pickup guns also add chargers or easy container grabs.
|
||||||
|
|
||||||
|
# Shorted version of RangedCombatCompound for entities that are guns themselves.
|
||||||
|
- type: htnCompound
|
||||||
|
id: InnateRangedCombatCompound
|
||||||
|
branches:
|
||||||
|
- preconditions:
|
||||||
|
- !type:GunAmmoPrecondition
|
||||||
|
minPercent: 0.001
|
||||||
|
tasks:
|
||||||
|
- !type:HTNCompoundTask
|
||||||
|
task: GunCombatCompound
|
||||||
|
|
||||||
|
|
||||||
|
- type: htnCompound
|
||||||
|
id: RangedCombatCompound
|
||||||
|
branches:
|
||||||
|
# Move to target and shoot them if ammo
|
||||||
|
- preconditions:
|
||||||
|
- !type:GunAmmoPrecondition
|
||||||
|
minPercent: 0.001
|
||||||
|
tasks:
|
||||||
|
- !type:HTNCompoundTask
|
||||||
|
task: GunCombatCompound
|
||||||
|
|
||||||
|
# Reload gun
|
||||||
|
# TODO
|
||||||
|
|
||||||
|
# Equip a gun from inventory if one found, preferring over pickup.
|
||||||
|
# TODO: Doing inventory cleanly will be a PITA so deferring to later
|
||||||
|
# The issue is recursively checking items but also ignoring some recursive entities
|
||||||
|
# i.e. we need to recursively go into storage containers.
|
||||||
|
#- tasks:
|
||||||
|
# - !type:HTNCompoundTask
|
||||||
|
# task: ClearActiveHandCompound
|
||||||
|
#
|
||||||
|
# - !type:HTNPrimitiveTask
|
||||||
|
# operator: !type:UtilityOperator
|
||||||
|
# proto: InventoryGuns
|
||||||
|
#
|
||||||
|
# - !type:HTNPrimitiveTask
|
||||||
|
# operator: !type:EquipOperator
|
||||||
|
|
||||||
|
# Pickup ammo if any nearby
|
||||||
|
#- preconditions:
|
||||||
|
# - !type:GunAmmoPrecondition
|
||||||
|
# maxPercent: 0.0
|
||||||
|
# tasks:
|
||||||
|
# - !type:HTNCompoundTask
|
||||||
|
# task: ClearActiveHandCompound
|
||||||
|
#
|
||||||
|
# - !type:HTNCompoundTask
|
||||||
|
# task: PickupAmmoCompound
|
||||||
|
|
||||||
|
# Pickup gun with ammo if we have no ammo
|
||||||
|
- preconditions:
|
||||||
|
- !type:ActiveHandComponentPrecondition
|
||||||
|
components:
|
||||||
|
- type: Gun
|
||||||
|
invert: true
|
||||||
|
tasks:
|
||||||
|
- !type:HTNCompoundTask
|
||||||
|
task: PickupGunCompound
|
||||||
|
|
||||||
|
# Discard gun if no ammo
|
||||||
|
- preconditions:
|
||||||
|
- !type:ActiveHandComponentPrecondition
|
||||||
|
components:
|
||||||
|
- type: Gun
|
||||||
|
- !type:GunAmmoPrecondition
|
||||||
|
maxPercent: 0.001
|
||||||
|
tasks:
|
||||||
|
- !type:HTNPrimitiveTask
|
||||||
|
preconditions:
|
||||||
|
- !type:ActiveHandEntityPrecondition
|
||||||
|
operator: !type:DropOperator
|
||||||
|
|
||||||
|
# TODO: Reload a nearby gun
|
||||||
80
Resources/Prototypes/NPCs/Combat/melee.yml
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
# -- Melee --
|
||||||
|
# Selects a target in melee and tries to attack it.
|
||||||
|
- type: htnCompound
|
||||||
|
id: MeleeCombatCompound
|
||||||
|
branches:
|
||||||
|
# Pickup weapon if we don't have one.
|
||||||
|
- preconditions:
|
||||||
|
- !type:ActiveHandComponentPrecondition
|
||||||
|
components:
|
||||||
|
# Just serializer things
|
||||||
|
- type: MeleeWeapon
|
||||||
|
damage:
|
||||||
|
types:
|
||||||
|
Brute: 0
|
||||||
|
invert: true
|
||||||
|
tasks:
|
||||||
|
- !type:HTNCompoundTask
|
||||||
|
task: PickupMeleeCompound
|
||||||
|
|
||||||
|
# Melee combat (unarmed or otherwise)
|
||||||
|
- tasks:
|
||||||
|
- !type:HTNPrimitiveTask
|
||||||
|
operator: !type:UtilityOperator
|
||||||
|
proto: NearbyMeleeTargets
|
||||||
|
- !type:HTNCompoundTask
|
||||||
|
task: MeleeAttackTargetCompound
|
||||||
|
|
||||||
|
- type: htnCompound
|
||||||
|
id: PickupMeleeCompound
|
||||||
|
branches:
|
||||||
|
- tasks:
|
||||||
|
- !type:HTNPrimitiveTask
|
||||||
|
operator: !type:UtilityOperator
|
||||||
|
proto: NearbyMeleeWeapons
|
||||||
|
|
||||||
|
- !type:HTNPrimitiveTask
|
||||||
|
operator: !type:MoveToOperator
|
||||||
|
|
||||||
|
- !type:HTNPrimitiveTask
|
||||||
|
preconditions:
|
||||||
|
- !type:TargetInRangePrecondition
|
||||||
|
targetKey: Target
|
||||||
|
rangeKey: InteractRange
|
||||||
|
operator: !type:InteractWithOperator
|
||||||
|
targetKey: Target
|
||||||
|
|
||||||
|
# Tries to melee attack our target.
|
||||||
|
- type: htnCompound
|
||||||
|
id: MeleeAttackTargetCompound
|
||||||
|
preconditions:
|
||||||
|
- !type:KeyExistsPrecondition
|
||||||
|
key: Target
|
||||||
|
branches:
|
||||||
|
# Move to melee range and hit them
|
||||||
|
- tasks:
|
||||||
|
- !type:HTNPrimitiveTask
|
||||||
|
operator: !type:MoveToOperator
|
||||||
|
shutdownState: PlanFinished
|
||||||
|
pathfindInPlanning: true
|
||||||
|
removeKeyOnFinish: false
|
||||||
|
targetKey: TargetCoordinates
|
||||||
|
pathfindKey: TargetPathfind
|
||||||
|
rangeKey: MeleeRange
|
||||||
|
- !type:HTNPrimitiveTask
|
||||||
|
operator: !type:JukeOperator
|
||||||
|
jukeType: Away
|
||||||
|
- !type:HTNPrimitiveTask
|
||||||
|
operator: !type:MeleeOperator
|
||||||
|
targetKey: Target
|
||||||
|
preconditions:
|
||||||
|
- !type:KeyExistsPrecondition
|
||||||
|
key: Target
|
||||||
|
- !type:TargetInRangePrecondition
|
||||||
|
targetKey: Target
|
||||||
|
rangeKey: MeleeRange
|
||||||
|
services:
|
||||||
|
- !type:UtilityService
|
||||||
|
id: MeleeService
|
||||||
|
proto: NearbyMeleeTargets
|
||||||
|
key: Target
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
# -- Ranged --
|
|
||||||
# Tries to shoot a target in LOS in range.
|
|
||||||
- type: htnCompound
|
|
||||||
id: TurretCompound
|
|
||||||
branches:
|
|
||||||
- tasks:
|
|
||||||
- id: PickRangedTargetPrimitive
|
|
||||||
- id: RangedAttackTargetPrimitive
|
|
||||||
- tasks:
|
|
||||||
- id: IdleSpinCompound
|
|
||||||
|
|
||||||
# Tries to shoot a target at range.
|
|
||||||
- type: htnCompound
|
|
||||||
id: RangedCombatCompound
|
|
||||||
branches:
|
|
||||||
- tasks:
|
|
||||||
- id: PickRangedTargetPrimitive
|
|
||||||
- id: RangedAttackTargetCompound
|
|
||||||
|
|
||||||
# Tries to ranged attack our target.
|
|
||||||
- type: htnCompound
|
|
||||||
id: RangedAttackTargetCompound
|
|
||||||
preconditions:
|
|
||||||
- !type:KeyExistsPrecondition
|
|
||||||
key: CombatTarget
|
|
||||||
branches:
|
|
||||||
# Keep hitting them if they're in LOS
|
|
||||||
- tasks:
|
|
||||||
- id: RangedAttackTargetPrimitive
|
|
||||||
|
|
||||||
# Move to range and hit them
|
|
||||||
- tasks:
|
|
||||||
- id: MoveToCombatTargetPrimitive
|
|
||||||
- id: RangedAttackTargetPrimitive
|
|
||||||
|
|
||||||
|
|
||||||
- type: htnPrimitive
|
|
||||||
id: PickRangedTargetPrimitive
|
|
||||||
operator: !type:UtilityOperator
|
|
||||||
proto: NearbyRangedTargets
|
|
||||||
|
|
||||||
# Attacks the specified target if they're in LOS.
|
|
||||||
- type: htnPrimitive
|
|
||||||
id: RangedAttackTargetPrimitive
|
|
||||||
operator: !type:RangedOperator
|
|
||||||
targetKey: CombatTarget
|
|
||||||
preconditions:
|
|
||||||
- !type:KeyExistsPrecondition
|
|
||||||
key: CombatTarget
|
|
||||||
- !type:TargetInRangePrecondition
|
|
||||||
targetKey: CombatTarget
|
|
||||||
# TODO: Non-scuffed
|
|
||||||
rangeKey: RangedRange
|
|
||||||
- !type:TargetInLOSPrecondition
|
|
||||||
targetKey: CombatTarget
|
|
||||||
rangeKey: RangedRange
|
|
||||||
services:
|
|
||||||
- !type:UtilityService
|
|
||||||
id: RangedService
|
|
||||||
proto: NearbyRangedTargets
|
|
||||||
key: CombatTarget
|
|
||||||
|
|
||||||
|
|
||||||
# -- Melee --
|
|
||||||
# Selects a target in melee and tries to attack it.
|
|
||||||
- type: htnCompound
|
|
||||||
id: MeleeCombatCompound
|
|
||||||
branches:
|
|
||||||
# Unarmed combat
|
|
||||||
- tasks:
|
|
||||||
- id: PickMeleeTargetPrimitive
|
|
||||||
- id: MeleeAttackTargetCompound
|
|
||||||
|
|
||||||
# Tries to melee attack our target.
|
|
||||||
- type: htnCompound
|
|
||||||
id: MeleeAttackTargetCompound
|
|
||||||
preconditions:
|
|
||||||
- !type:KeyExistsPrecondition
|
|
||||||
key: CombatTarget
|
|
||||||
branches:
|
|
||||||
# Keep hitting them if they're in range
|
|
||||||
- tasks:
|
|
||||||
- id: MeleeAttackTargetPrimitive
|
|
||||||
|
|
||||||
# Move to melee range and hit them
|
|
||||||
- tasks:
|
|
||||||
- id: MoveToCombatTargetPrimitive
|
|
||||||
- id: MeleeAttackTargetPrimitive
|
|
||||||
|
|
||||||
|
|
||||||
- type: htnPrimitive
|
|
||||||
id: PickMeleeTargetPrimitive
|
|
||||||
operator: !type:UtilityOperator
|
|
||||||
proto: NearbyMeleeTargets
|
|
||||||
|
|
||||||
# Attacks the specified target if they're in range.
|
|
||||||
- type: htnPrimitive
|
|
||||||
id: MeleeAttackTargetPrimitive
|
|
||||||
operator: !type:MeleeOperator
|
|
||||||
targetKey: CombatTarget
|
|
||||||
preconditions:
|
|
||||||
- !type:KeyExistsPrecondition
|
|
||||||
key: CombatTarget
|
|
||||||
- !type:TargetInRangePrecondition
|
|
||||||
targetKey: CombatTarget
|
|
||||||
rangeKey: MeleeRange
|
|
||||||
services:
|
|
||||||
- !type:UtilityService
|
|
||||||
id: MeleeService
|
|
||||||
proto: NearbyMeleeTargets
|
|
||||||
key: CombatTarget
|
|
||||||
|
|
||||||
# Moves the owner into range of the combat target.
|
|
||||||
- type: htnPrimitive
|
|
||||||
id: MoveToCombatTargetPrimitive
|
|
||||||
operator: !type:MoveToOperator
|
|
||||||
pathfindInPlanning: true
|
|
||||||
removeKeyOnFinish: false
|
|
||||||
targetKey: CombatTargetCoordinates
|
|
||||||
pathfindKey: CombatTargetPathfind
|
|
||||||
rangeKey: MeleeRange
|
|
||||||
@@ -2,40 +2,37 @@
|
|||||||
id: CleanbotCompound
|
id: CleanbotCompound
|
||||||
branches:
|
branches:
|
||||||
- tasks:
|
- tasks:
|
||||||
- id: BufferNearbyPuddlesCompound
|
- !type:HTNCompoundTask
|
||||||
|
task: BufferNearbyPuddlesCompound
|
||||||
- tasks:
|
- tasks:
|
||||||
- id: IdleCompound
|
- !type:HTNCompoundTask
|
||||||
|
task: IdleCompound
|
||||||
|
|
||||||
# Picks a random puddle in range to move to and idle
|
# Picks a random puddle in range to move to and idle
|
||||||
- type: htnCompound
|
- type: htnCompound
|
||||||
id: BufferNearbyPuddlesCompound
|
id: BufferNearbyPuddlesCompound
|
||||||
branches:
|
branches:
|
||||||
- tasks:
|
- tasks:
|
||||||
- id: PickPuddlePrimitive
|
- !type:HTNPrimitiveTask
|
||||||
- id: MoveToCombatTargetPrimitive
|
operator: !type:UtilityOperator
|
||||||
- id: MopPrimitive
|
proto: NearbyPuddles
|
||||||
|
|
||||||
- type: htnPrimitive
|
- !type:HTNPrimitiveTask
|
||||||
id: PickPuddlePrimitive
|
operator: !type:MoveToOperator
|
||||||
operator: !type:UtilityOperator
|
pathfindInPlanning: true
|
||||||
proto: NearbyPuddles
|
removeKeyOnFinish: false
|
||||||
|
targetKey: TargetCoordinates
|
||||||
- type: htnPrimitive
|
pathfindKey: TargetPathfind
|
||||||
id: SetIdleTimePrimitive
|
rangeKey: MeleeRange
|
||||||
operator: !type:SetFloatOperator
|
- !type:HTNPrimitiveTask
|
||||||
targetKey: IdleTime
|
preconditions:
|
||||||
amount: 3
|
- !type:TargetInRangePrecondition
|
||||||
|
targetKey: Target
|
||||||
- type: htnPrimitive
|
rangeKey: InteractRange
|
||||||
id: MopPrimitive
|
operator: !type:InteractWithOperator
|
||||||
preconditions:
|
targetKey: Target
|
||||||
- !type:TargetInRangePrecondition
|
services:
|
||||||
targetKey: CombatTarget
|
- !type:UtilityService
|
||||||
rangeKey: InteractRange
|
id: PuddleService
|
||||||
operator: !type:InteractWithOperator
|
proto: NearbyPuddles
|
||||||
targetKey: CombatTarget
|
key: Target
|
||||||
services:
|
|
||||||
- !type:UtilityService
|
|
||||||
id: PuddleService
|
|
||||||
proto: NearbyPuddles
|
|
||||||
key: CombatTarget
|
|
||||||
|
|||||||
5
Resources/Prototypes/NPCs/clothing.yml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Generic pickups to not be naked.
|
||||||
|
# TODO: Armor / Pressure pickups.
|
||||||
|
# TODO: Preferred slots
|
||||||
|
#- type: htnCompound
|
||||||
|
# id:
|
||||||
@@ -7,49 +7,53 @@
|
|||||||
branches:
|
branches:
|
||||||
# Head to follow target
|
# Head to follow target
|
||||||
- tasks:
|
- tasks:
|
||||||
- id: FollowPrimitive
|
- !type:HTNPrimitiveTask
|
||||||
|
preconditions:
|
||||||
|
- !type:CoordinatesNotInRangePrecondition
|
||||||
|
targetKey: FollowTarget
|
||||||
|
rangeKey: FollowRange
|
||||||
|
operator: !type:MoveToOperator
|
||||||
|
pathfindInPlanning: true
|
||||||
|
targetKey: FollowTarget
|
||||||
|
rangeKey: FollowCloseRange
|
||||||
|
removeKeyOnFinish: false
|
||||||
|
|
||||||
# Keep idling near follow target
|
# Keep idling near follow target
|
||||||
- tasks:
|
- tasks:
|
||||||
- id: WaitFollowPrimitive
|
- !type:HTNPrimitiveTask
|
||||||
|
preconditions:
|
||||||
|
- !type:KeyExistsPrecondition
|
||||||
|
key: IdleTime
|
||||||
|
- !type:CoordinatesInRangePrecondition
|
||||||
|
targetKey: FollowTarget
|
||||||
|
rangeKey: FollowRange
|
||||||
|
operator: !type:WaitOperator
|
||||||
|
key: IdleTime
|
||||||
|
|
||||||
# Pick a new idle spot near the follow target
|
# Pick a new idle spot near the follow target
|
||||||
- tasks:
|
- tasks:
|
||||||
- id: PickAccessibleNearFollowPrimitive
|
- !type:HTNPrimitiveTask
|
||||||
- id: IdleNearFollowPrimitive
|
operator: !type:PickAccessibleOperator
|
||||||
- id: RandomIdleTimePrimitive
|
# originKey: FollowTarget
|
||||||
- id: WaitFollowPrimitive
|
rangeKey: FollowCloseRange
|
||||||
|
targetCoordinates: FollowIdleTarget
|
||||||
|
|
||||||
|
- !type:HTNPrimitiveTask
|
||||||
|
operator: !type:MoveToOperator
|
||||||
|
targetKey: FollowIdleTarget
|
||||||
|
|
||||||
- type: htnPrimitive
|
- !type:HTNPrimitiveTask
|
||||||
id: WaitFollowPrimitive
|
operator: !type:RandomOperator
|
||||||
operator: !type:WaitOperator
|
targetKey: IdleTime
|
||||||
key: IdleTime
|
minKey: MinimumIdleTime
|
||||||
preconditions:
|
maxKey: MaximumIdleTime
|
||||||
- !type:KeyExistsPrecondition
|
|
||||||
key: IdleTime
|
|
||||||
- !type:CoordinatesInRangePrecondition
|
|
||||||
targetKey: FollowTarget
|
|
||||||
rangeKey: FollowRange
|
|
||||||
|
|
||||||
- type: htnPrimitive
|
- !type:HTNPrimitiveTask
|
||||||
id: PickAccessibleNearFollowPrimitive
|
preconditions:
|
||||||
operator: !type:PickAccessibleOperator
|
- !type:KeyExistsPrecondition
|
||||||
# originKey: FollowTarget
|
key: IdleTime
|
||||||
rangeKey: FollowCloseRange
|
- !type:CoordinatesInRangePrecondition
|
||||||
targetKey: FollowIdleTarget
|
targetKey: FollowTarget
|
||||||
|
rangeKey: FollowRange
|
||||||
- type: htnPrimitive
|
operator: !type:WaitOperator
|
||||||
id: IdleNearFollowPrimitive
|
key: IdleTime
|
||||||
operator: !type:MoveToOperator
|
|
||||||
targetKey: FollowIdleTarget
|
|
||||||
|
|
||||||
- type: htnPrimitive
|
|
||||||
id: FollowPrimitive
|
|
||||||
operator: !type:MoveToOperator
|
|
||||||
pathfindInPlanning: true
|
|
||||||
targetKey: FollowTarget
|
|
||||||
rangeKey: FollowCloseRange
|
|
||||||
removeKeyOnFinish: false
|
|
||||||
preconditions:
|
|
||||||
- !type:CoordinatesNotInRangePrecondition
|
|
||||||
targetKey: FollowTarget
|
|
||||||
rangeKey: FollowRange
|
|
||||||
|
|||||||
25
Resources/Prototypes/NPCs/generic.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
- type: htnCompound
|
||||||
|
id: ClearActiveHandCompound
|
||||||
|
branches:
|
||||||
|
# Do nothing
|
||||||
|
- preconditions:
|
||||||
|
- !type:ActiveHandFreePrecondition
|
||||||
|
tasks:
|
||||||
|
- !type:HTNPrimitiveTask
|
||||||
|
operator: !type:NoOperator
|
||||||
|
|
||||||
|
# Swap to another free hand
|
||||||
|
- tasks:
|
||||||
|
- !type:HTNPrimitiveTask
|
||||||
|
operator: !type:SwapToFreeHandOperator
|
||||||
|
|
||||||
|
# TODO: Need to make sure this works with blackboard and handles storage + pockets + inventory slots
|
||||||
|
# Put active hand into storage
|
||||||
|
#- tasks:
|
||||||
|
# - !type:HTNPrimitiveTask
|
||||||
|
# operator: !type:StashActiveHandOperator
|
||||||
|
|
||||||
|
# Drop active hand
|
||||||
|
- tasks:
|
||||||
|
- !type:HTNPrimitiveTask
|
||||||
|
operator: !type:DropOperator
|
||||||
@@ -2,76 +2,65 @@
|
|||||||
- type: htnCompound
|
- type: htnCompound
|
||||||
id: IdleCompound
|
id: IdleCompound
|
||||||
branches:
|
branches:
|
||||||
- tasks:
|
|
||||||
- id: WaitIdleTimePrimitive
|
|
||||||
# Pick a new spot and wait there.
|
# Pick a new spot and wait there.
|
||||||
- tasks:
|
- preconditions:
|
||||||
- id: PickAccessiblePrimitive
|
|
||||||
- id: MoveToAccessiblePrimitive
|
|
||||||
- id: RandomIdleTimePrimitive
|
|
||||||
- id: WaitIdleTimePrimitive
|
|
||||||
preconditions:
|
|
||||||
- !type:BuckledPrecondition
|
- !type:BuckledPrecondition
|
||||||
isBuckled: false
|
isBuckled: false
|
||||||
- !type:PulledPrecondition
|
- !type:PulledPrecondition
|
||||||
isPulled: false
|
isPulled: false
|
||||||
|
tasks:
|
||||||
|
- !type:HTNPrimitiveTask
|
||||||
|
operator: !type:PickAccessibleOperator
|
||||||
|
rangeKey: IdleRange
|
||||||
|
|
||||||
|
- !type:HTNPrimitiveTask
|
||||||
|
operator: !type:MoveToOperator
|
||||||
|
pathfindInPlanning: false
|
||||||
|
|
||||||
|
- !type:HTNPrimitiveTask
|
||||||
|
operator: !type:RandomOperator
|
||||||
|
targetKey: IdleTime
|
||||||
|
minKey: MinimumIdleTime
|
||||||
|
maxKey: MaximumIdleTime
|
||||||
|
|
||||||
|
- !type:HTNPrimitiveTask
|
||||||
|
operator: !type:WaitOperator
|
||||||
|
key: IdleTime
|
||||||
|
preconditions:
|
||||||
|
- !type:KeyExistsPrecondition
|
||||||
|
key: IdleTime
|
||||||
|
|
||||||
# Spin to a random rotation and idle.
|
# Spin to a random rotation and idle.
|
||||||
- type: htnCompound
|
- type: htnCompound
|
||||||
id: IdleSpinCompound
|
id: IdleSpinCompound
|
||||||
branches:
|
branches:
|
||||||
- tasks:
|
- tasks:
|
||||||
- id: WaitIdleTimePrimitive
|
- !type:HTNPrimitiveTask
|
||||||
|
operator: !type:WaitOperator
|
||||||
|
key: IdleTime
|
||||||
|
preconditions:
|
||||||
|
- !type:KeyExistsPrecondition
|
||||||
|
key: IdleTime
|
||||||
|
|
||||||
# Pick a new angle and spin there
|
# Pick a new angle and spin there
|
||||||
- tasks:
|
- tasks:
|
||||||
- id: PickRandomRotationPrimitive
|
- !type:HTNPrimitiveTask
|
||||||
- id: RotateToTargetPrimitive
|
operator: !type:PickRandomRotationOperator
|
||||||
- id: RandomIdleTimePrimitive
|
targetKey: RotateTarget
|
||||||
- id: WaitIdleTimePrimitive
|
|
||||||
|
|
||||||
|
- !type:HTNPrimitiveTask
|
||||||
|
operator: !type:RotateToTargetOperator
|
||||||
|
targetKey: RotateTarget
|
||||||
|
|
||||||
# Primitives
|
- !type:HTNPrimitiveTask
|
||||||
- type: htnPrimitive
|
operator: !type:RandomOperator
|
||||||
id: InteractWithPrimitive
|
targetKey: IdleTime
|
||||||
preconditions:
|
minKey: MinimumIdleTime
|
||||||
- !type:TargetInRangePrecondition
|
maxKey: MaximumIdleTime
|
||||||
targetKey: Target
|
|
||||||
rangeKey: InteractRange
|
|
||||||
operator: !type:InteractWithOperator
|
|
||||||
targetKey: Target
|
|
||||||
|
|
||||||
- type: htnPrimitive
|
- !type:HTNPrimitiveTask
|
||||||
id: MoveToAccessiblePrimitive
|
operator: !type:WaitOperator
|
||||||
operator: !type:MoveToOperator
|
key: IdleTime
|
||||||
pathfindInPlanning: false
|
preconditions:
|
||||||
|
- !type:KeyExistsPrecondition
|
||||||
- type: htnPrimitive
|
key: IdleTime
|
||||||
id: PickAccessiblePrimitive
|
|
||||||
operator: !type:PickAccessibleOperator
|
|
||||||
rangeKey: IdleRange
|
|
||||||
targetKey: MovementTarget
|
|
||||||
|
|
||||||
- type: htnPrimitive
|
|
||||||
id: PickRandomRotationPrimitive
|
|
||||||
operator: !type:PickRandomRotationOperator
|
|
||||||
targetKey: RotateTarget
|
|
||||||
|
|
||||||
- type: htnPrimitive
|
|
||||||
id: RotateToTargetPrimitive
|
|
||||||
operator: !type:RotateToTargetOperator
|
|
||||||
targetKey: RotateTarget
|
|
||||||
|
|
||||||
- type: htnPrimitive
|
|
||||||
id: RandomIdleTimePrimitive
|
|
||||||
operator: !type:RandomOperator
|
|
||||||
targetKey: IdleTime
|
|
||||||
minKey: MinimumIdleTime
|
|
||||||
maxKey: MaximumIdleTime
|
|
||||||
|
|
||||||
- type: htnPrimitive
|
|
||||||
id: WaitIdleTimePrimitive
|
|
||||||
operator: !type:WaitOperator
|
|
||||||
key: IdleTime
|
|
||||||
preconditions:
|
|
||||||
- !type:KeyExistsPrecondition
|
|
||||||
key: IdleTime
|
|
||||||
|
|||||||
@@ -2,33 +2,42 @@
|
|||||||
id: MedibotCompound
|
id: MedibotCompound
|
||||||
branches:
|
branches:
|
||||||
- tasks:
|
- tasks:
|
||||||
- id: InjectNearbyCompound
|
- !type:HTNCompoundTask
|
||||||
|
task: InjectNearbyCompound
|
||||||
- tasks:
|
- tasks:
|
||||||
- id: IdleCompound
|
- !type:HTNCompoundTask
|
||||||
|
task: IdleCompound
|
||||||
|
|
||||||
- type: htnCompound
|
- type: htnCompound
|
||||||
id: InjectNearbyCompound
|
id: InjectNearbyCompound
|
||||||
branches:
|
branches:
|
||||||
- tasks:
|
- tasks:
|
||||||
- id: PickNearbyInjectablePrimitive
|
# TODO: Kill this shit
|
||||||
- id: MoveToAccessiblePrimitive
|
- !type:HTNPrimitiveTask
|
||||||
- id: MedibotSpeakPrimitive
|
operator: !type:PickNearbyInjectableOperator
|
||||||
- id: SetIdleTimePrimitive
|
targetKey: InjectTarget
|
||||||
- id: WaitIdleTimePrimitive
|
targetMoveKey: MovementTarget
|
||||||
- id: MedibotInjectPrimitive
|
- !type:HTNPrimitiveTask
|
||||||
|
operator: !type:SpeakOperator
|
||||||
|
speech: medibot-start-inject
|
||||||
|
|
||||||
- type: htnPrimitive
|
- !type:HTNPrimitiveTask
|
||||||
id: MedibotSpeakPrimitive
|
operator: !type:MoveToOperator
|
||||||
operator: !type:SpeakOperator
|
pathfindInPlanning: false
|
||||||
speech: medibot-start-inject
|
|
||||||
|
|
||||||
- type: htnPrimitive
|
- !type:HTNPrimitiveTask
|
||||||
id: PickNearbyInjectablePrimitive
|
operator: !type:SetFloatOperator
|
||||||
operator: !type:PickNearbyInjectableOperator
|
targetKey: IdleTime
|
||||||
targetKey: InjectTarget
|
amount: 3
|
||||||
targetMoveKey: MovementTarget
|
|
||||||
|
|
||||||
- type: htnPrimitive
|
- !type:HTNPrimitiveTask
|
||||||
id: MedibotInjectPrimitive
|
operator: !type:WaitOperator
|
||||||
operator: !type:MedibotInjectOperator
|
key: IdleTime
|
||||||
targetKey: InjectTarget
|
preconditions:
|
||||||
|
- !type:KeyExistsPrecondition
|
||||||
|
key: IdleTime
|
||||||
|
|
||||||
|
# TODO: Kill this
|
||||||
|
- !type:HTNPrimitiveTask
|
||||||
|
operator: !type:MedibotInjectOperator
|
||||||
|
targetKey: InjectTarget
|
||||||
|
|||||||
@@ -3,32 +3,41 @@
|
|||||||
id: SimpleHostileCompound
|
id: SimpleHostileCompound
|
||||||
branches:
|
branches:
|
||||||
- tasks:
|
- tasks:
|
||||||
- id: MeleeCombatCompound
|
- !type:HTNCompoundTask
|
||||||
|
task: MeleeCombatCompound
|
||||||
- tasks:
|
- tasks:
|
||||||
- id: IdleCompound
|
- !type:HTNCompoundTask
|
||||||
|
task: IdleCompound
|
||||||
|
|
||||||
- type: htnCompound
|
- type: htnCompound
|
||||||
id: MouseCompound
|
id: MouseCompound
|
||||||
branches:
|
branches:
|
||||||
- tasks:
|
- tasks:
|
||||||
- id: FoodCompound
|
- !type:HTNCompoundTask
|
||||||
|
task: FoodCompound
|
||||||
- tasks:
|
- tasks:
|
||||||
- id: IdleCompound
|
- !type:HTNCompoundTask
|
||||||
|
task: IdleCompound
|
||||||
|
|
||||||
- type: htnCompound
|
- type: htnCompound
|
||||||
id: RuminantCompound
|
id: RuminantCompound
|
||||||
branches:
|
branches:
|
||||||
- tasks:
|
- tasks:
|
||||||
- id: FoodCompound
|
- !type:HTNCompoundTask
|
||||||
|
task: FoodCompound
|
||||||
- tasks:
|
- tasks:
|
||||||
- id: IdleCompound
|
- !type:HTNCompoundTask
|
||||||
|
task: IdleCompound
|
||||||
|
|
||||||
- type: htnCompound
|
- type: htnCompound
|
||||||
id: DragonCarpCompound
|
id: DragonCarpCompound
|
||||||
branches:
|
branches:
|
||||||
- tasks:
|
- tasks:
|
||||||
- id: MeleeCombatCompound
|
- !type:HTNCompoundTask
|
||||||
|
task: MeleeCombatCompound
|
||||||
- tasks:
|
- tasks:
|
||||||
- id: FollowCompound
|
- !type:HTNCompoundTask
|
||||||
|
task: FollowCompound
|
||||||
- tasks:
|
- tasks:
|
||||||
- id: IdleCompound
|
- !type:HTNCompoundTask
|
||||||
|
task: IdleCompound
|
||||||
|
|||||||
@@ -1,31 +1,56 @@
|
|||||||
- type: htnCompound
|
- type: htnCompound
|
||||||
id: FoodCompound
|
id: FoodCompound
|
||||||
branches:
|
branches:
|
||||||
- tasks:
|
# Picks a nearby food, moves into range, then eats it and waits the idle time.
|
||||||
- id: PickFoodTargetPrimitive
|
- tasks:
|
||||||
- id: MoveToCombatTargetPrimitive
|
- !type:HTNPrimitiveTask
|
||||||
- id: EatPrimitive
|
operator: !type:UtilityOperator
|
||||||
- id: WaitIdleTimePrimitive
|
proto: NearbyFood
|
||||||
- tasks:
|
|
||||||
- id: PickDrinkTargetPrimitive
|
|
||||||
- id: MoveToCombatTargetPrimitive
|
|
||||||
- id: EatPrimitive
|
|
||||||
- id: WaitIdleTimePrimitive
|
|
||||||
|
|
||||||
|
- !type:HTNPrimitiveTask
|
||||||
|
operator: !type:MoveToOperator
|
||||||
|
pathfindInPlanning: true
|
||||||
|
removeKeyOnFinish: false
|
||||||
|
targetKey: TargetCoordinates
|
||||||
|
pathfindKey: TargetPathfind
|
||||||
|
rangeKey: MeleeRange
|
||||||
|
|
||||||
- type: htnPrimitive
|
- !type:HTNPrimitiveTask
|
||||||
id: PickFoodTargetPrimitive
|
preconditions:
|
||||||
operator: !type:UtilityOperator
|
- !type:KeyExistsPrecondition
|
||||||
proto: NearbyFood
|
key: Target
|
||||||
|
operator: !type:AltInteractOperator
|
||||||
|
|
||||||
- type: htnPrimitive
|
- !type:HTNPrimitiveTask
|
||||||
id: PickDrinkTargetPrimitive
|
preconditions:
|
||||||
operator: !type:UtilityOperator
|
- !type:KeyExistsPrecondition
|
||||||
proto: NearbyDrink
|
key: IdleTime
|
||||||
|
operator: !type:WaitOperator
|
||||||
|
key: IdleTime
|
||||||
|
|
||||||
- type: htnPrimitive
|
# Picks nearby drink then consumes it and waits idle time
|
||||||
id: EatPrimitive
|
- tasks:
|
||||||
preconditions:
|
- !type:HTNPrimitiveTask
|
||||||
- !type:KeyExistsPrecondition
|
operator: !type:UtilityOperator
|
||||||
key: CombatTarget
|
proto: NearbyDrink
|
||||||
operator: !type:AltInteractOperator
|
|
||||||
|
- !type:HTNPrimitiveTask
|
||||||
|
operator: !type:MoveToOperator
|
||||||
|
pathfindInPlanning: true
|
||||||
|
removeKeyOnFinish: false
|
||||||
|
targetKey: TargetCoordinates
|
||||||
|
pathfindKey: TargetPathfind
|
||||||
|
rangeKey: MeleeRange
|
||||||
|
|
||||||
|
- !type:HTNPrimitiveTask
|
||||||
|
preconditions:
|
||||||
|
- !type:KeyExistsPrecondition
|
||||||
|
key: Target
|
||||||
|
operator: !type:AltInteractOperator
|
||||||
|
|
||||||
|
- !type:HTNPrimitiveTask
|
||||||
|
preconditions:
|
||||||
|
- !type:KeyExistsPrecondition
|
||||||
|
key: IdleTime
|
||||||
|
operator: !type:WaitOperator
|
||||||
|
key: IdleTime
|
||||||
|
|||||||
57
Resources/Prototypes/NPCs/root.yml
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# Specific Root compound tasks being used for NPCs.
|
||||||
|
|
||||||
|
# Tries to shoot a target in LOS in range.
|
||||||
|
- type: htnCompound
|
||||||
|
id: TurretCompound
|
||||||
|
branches:
|
||||||
|
- tasks:
|
||||||
|
# Shoot target if in range
|
||||||
|
- !type:HTNPrimitiveTask
|
||||||
|
preconditions:
|
||||||
|
- !type:KeyExistsPrecondition
|
||||||
|
key: Target
|
||||||
|
- !type:TargetInRangePrecondition
|
||||||
|
targetKey: Target
|
||||||
|
# TODO: Non-scuffed
|
||||||
|
rangeKey: RangedRange
|
||||||
|
- !type:TargetInLOSPrecondition
|
||||||
|
targetKey: Target
|
||||||
|
rangeKey: RangedRange
|
||||||
|
operator: !type:GunOperator
|
||||||
|
targetKey: Target
|
||||||
|
requireLOS: true
|
||||||
|
services:
|
||||||
|
- !type:UtilityService
|
||||||
|
id: RangedService
|
||||||
|
proto: NearbyGunTargets
|
||||||
|
key: Target
|
||||||
|
|
||||||
|
- tasks:
|
||||||
|
- !type:HTNCompoundTask
|
||||||
|
task: IdleSpinCompound
|
||||||
|
|
||||||
|
- type: htnCompound
|
||||||
|
id: SimpleRangedHostileCompound
|
||||||
|
branches:
|
||||||
|
- tasks:
|
||||||
|
- !type:HTNCompoundTask
|
||||||
|
task: InnateRangedCombatCompound
|
||||||
|
- tasks:
|
||||||
|
- !type:HTNCompoundTask
|
||||||
|
task: MeleeCombatCompound
|
||||||
|
- tasks:
|
||||||
|
- !type:HTNCompoundTask
|
||||||
|
task: IdleCompound
|
||||||
|
|
||||||
|
- type: htnCompound
|
||||||
|
id: SimpleHumanoidHostileCompound
|
||||||
|
branches:
|
||||||
|
- tasks:
|
||||||
|
- !type:HTNCompoundTask
|
||||||
|
task: RangedCombatCompound
|
||||||
|
- tasks:
|
||||||
|
- !type:HTNCompoundTask
|
||||||
|
task: MeleeCombatCompound
|
||||||
|
- tasks:
|
||||||
|
- !type:HTNCompoundTask
|
||||||
|
task: IdleCompound
|
||||||
@@ -3,13 +3,13 @@
|
|||||||
id: MoveToPathfindPointCompound
|
id: MoveToPathfindPointCompound
|
||||||
branches:
|
branches:
|
||||||
- tasks:
|
- tasks:
|
||||||
- id: PickPathfindPointPrimitive
|
- !type:HTNPrimitiveTask
|
||||||
- id: MoveToAccessiblePrimitive
|
operator: !type:PickPathfindPointOperator
|
||||||
|
|
||||||
|
- !type:HTNPrimitiveTask
|
||||||
|
operator: !type:MoveToOperator
|
||||||
|
pathfindInPlanning: false
|
||||||
|
|
||||||
- type: htnPrimitive
|
|
||||||
id: PickPathfindPointPrimitive
|
|
||||||
operator: !type:PickPathfindPointOperator
|
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: MobPathfindDummy
|
id: MobPathfindDummy
|
||||||
@@ -18,4 +18,5 @@
|
|||||||
parent: MobXenoRouny
|
parent: MobXenoRouny
|
||||||
components:
|
components:
|
||||||
- type: HTN
|
- type: HTN
|
||||||
rootTask: MoveToPathfindPointCompound
|
rootTask:
|
||||||
|
task: MoveToPathfindPointCompound
|
||||||
|
|||||||
@@ -1,3 +1,31 @@
|
|||||||
|
- type: utilityQuery
|
||||||
|
id: InventoryGuns
|
||||||
|
query:
|
||||||
|
- !type:InventoryQuery
|
||||||
|
- !type:ComponentFilter
|
||||||
|
components:
|
||||||
|
- type: Gun
|
||||||
|
considerations:
|
||||||
|
- !type:TargetAmmoCon
|
||||||
|
curve: !type:QuadraticCurve
|
||||||
|
|
||||||
|
- type: utilityQuery
|
||||||
|
id: NearbyAmmo
|
||||||
|
query:
|
||||||
|
- !type:ComponentQuery
|
||||||
|
components:
|
||||||
|
- type: Ammo
|
||||||
|
- type: Item
|
||||||
|
considerations:
|
||||||
|
- !type:TargetAmmoMatchesCon
|
||||||
|
curve: !type:BoolCurve
|
||||||
|
- !type:TargetDistanceCon
|
||||||
|
curve: !type:PresetCurve
|
||||||
|
preset: TargetDistance
|
||||||
|
# TODO: Get ammo count.
|
||||||
|
- !type:TargetAccessibleCon
|
||||||
|
curve: !type:BoolCurve
|
||||||
|
|
||||||
- type: utilityQuery
|
- type: utilityQuery
|
||||||
id: NearbyFood
|
id: NearbyFood
|
||||||
query:
|
query:
|
||||||
@@ -36,6 +64,23 @@
|
|||||||
- !type:TargetAccessibleCon
|
- !type:TargetAccessibleCon
|
||||||
curve: !type:BoolCurve
|
curve: !type:BoolCurve
|
||||||
|
|
||||||
|
- type: utilityQuery
|
||||||
|
id: NearbyGuns
|
||||||
|
query:
|
||||||
|
- !type:ComponentQuery
|
||||||
|
components:
|
||||||
|
- type: Gun
|
||||||
|
- type: Item
|
||||||
|
considerations:
|
||||||
|
# TODO: Prefer highest DPC probably?
|
||||||
|
- !type:TargetAmmoCon
|
||||||
|
curve: !type:BoolCurve
|
||||||
|
- !type:TargetDistanceCon
|
||||||
|
curve: !type:PresetCurve
|
||||||
|
preset: TargetDistance
|
||||||
|
- !type:TargetAccessibleCon
|
||||||
|
curve: !type:BoolCurve
|
||||||
|
|
||||||
- type: utilityQuery
|
- type: utilityQuery
|
||||||
id: NearbyMeleeTargets
|
id: NearbyMeleeTargets
|
||||||
query:
|
query:
|
||||||
@@ -54,6 +99,29 @@
|
|||||||
- !type:TargetInLOSOrCurrentCon
|
- !type:TargetInLOSOrCurrentCon
|
||||||
curve: !type:BoolCurve
|
curve: !type:BoolCurve
|
||||||
|
|
||||||
|
- type: utilityQuery
|
||||||
|
id: NearbyMeleeWeapons
|
||||||
|
query:
|
||||||
|
- !type:ComponentQuery
|
||||||
|
components:
|
||||||
|
# Just serializer things
|
||||||
|
- type: MeleeWeapon
|
||||||
|
damage:
|
||||||
|
types:
|
||||||
|
Brute: 0
|
||||||
|
- type: Item
|
||||||
|
considerations:
|
||||||
|
- !type:TargetMeleeCon
|
||||||
|
curve: !type:QuadraticCurve
|
||||||
|
slope: 0.5
|
||||||
|
exponent: 0.5
|
||||||
|
yOffset: 0
|
||||||
|
- !type:TargetDistanceCon
|
||||||
|
curve: !type:PresetCurve
|
||||||
|
preset: TargetDistance
|
||||||
|
- !type:TargetAccessibleCon
|
||||||
|
curve: !type:BoolCurve
|
||||||
|
|
||||||
- type: utilityQuery
|
- type: utilityQuery
|
||||||
id: NearbyPuddles
|
id: NearbyPuddles
|
||||||
query:
|
query:
|
||||||
@@ -71,7 +139,7 @@
|
|||||||
curve: !type:BoolCurve
|
curve: !type:BoolCurve
|
||||||
|
|
||||||
- type: utilityQuery
|
- type: utilityQuery
|
||||||
id: NearbyRangedTargets
|
id: NearbyGunTargets
|
||||||
query:
|
query:
|
||||||
- !type:NearbyHostilesQuery
|
- !type:NearbyHostilesQuery
|
||||||
considerations:
|
considerations:
|
||||||
@@ -88,6 +156,21 @@
|
|||||||
- !type:TargetInLOSOrCurrentCon
|
- !type:TargetInLOSOrCurrentCon
|
||||||
curve: !type:BoolCurve
|
curve: !type:BoolCurve
|
||||||
|
|
||||||
|
#- type: utilityQuery
|
||||||
|
# id: NearbyShoes
|
||||||
|
# query:
|
||||||
|
# - !type:ComponentQuery
|
||||||
|
# components:
|
||||||
|
# - type: Clothing
|
||||||
|
# - !type:ClothingSlotFilter
|
||||||
|
# slotFlags: Feet
|
||||||
|
# considerations:
|
||||||
|
# - !type:TargetDistanceCon
|
||||||
|
# curve: !type:PresetCurve
|
||||||
|
# preset: TargetDistance
|
||||||
|
# - !type:TargetAccessibleCon
|
||||||
|
# curve: !type:BoolCurve
|
||||||
|
|
||||||
|
|
||||||
# Presets
|
# Presets
|
||||||
- type: utilityCurvePreset
|
- type: utilityCurvePreset
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
id: XenoCompound
|
id: XenoCompound
|
||||||
branches:
|
branches:
|
||||||
- tasks:
|
- tasks:
|
||||||
- id: MeleeCombatCompound
|
- !type:HTNCompoundTask
|
||||||
|
task: MeleeCombatCompound
|
||||||
- tasks:
|
- tasks:
|
||||||
- id: IdleCompound
|
- !type:HTNCompoundTask
|
||||||
|
task: IdleCompound
|
||||||
|
|||||||
@@ -21,6 +21,10 @@
|
|||||||
- id: WeaponTurretXeno
|
- id: WeaponTurretXeno
|
||||||
amount: 3
|
amount: 3
|
||||||
prob: 0.25
|
prob: 0.25
|
||||||
|
- entries:
|
||||||
|
- id: MobXenoSpitter
|
||||||
|
amount: 2
|
||||||
|
prob: 0.25
|
||||||
- entries:
|
- entries:
|
||||||
- id: MobXenoRavager
|
- id: MobXenoRavager
|
||||||
amount: 1
|
amount: 1
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -11,7 +11,7 @@
|
|||||||
"name": "icon"
|
"name": "icon"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "base_dead",
|
"name": "dead",
|
||||||
"delays": [
|
"delays": [
|
||||||
[
|
[
|
||||||
0.1,
|
0.1,
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "base",
|
"name": "alive",
|
||||||
"directions": 4,
|
"directions": 4,
|
||||||
"delays": [
|
"delays": [
|
||||||
[
|
[
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 491 B After Width: | Height: | Size: 491 B |
@@ -11,10 +11,10 @@
|
|||||||
"name": "icon"
|
"name": "icon"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "base_dead"
|
"name": "dead"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "base",
|
"name": "alive",
|
||||||
"directions": 4,
|
"directions": 4,
|
||||||
"delays": [
|
"delays": [
|
||||||
[
|
[
|
||||||
|
|||||||