Pathfinder rework (#11452)
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Server.NPC.HTN.PrimitiveTasks;
|
||||
|
||||
/// <summary>
|
||||
/// Concrete code that gets run for an NPC task.
|
||||
/// </summary>
|
||||
[ImplicitDataDefinitionForInheritors]
|
||||
[ImplicitDataDefinitionForInheritors, MeansImplicitUse]
|
||||
public abstract class HTNOperator
|
||||
{
|
||||
/// <summary>
|
||||
@@ -20,9 +22,11 @@ public abstract class HTNOperator
|
||||
/// Called during planning.
|
||||
/// </summary>
|
||||
/// <param name="blackboard">The blackboard for the NPC.</param>
|
||||
/// <param name="cancelToken"></param>
|
||||
/// <returns>Whether the plan is still valid and the effects to apply to the blackboard.
|
||||
/// These get re-applied during execution and are up to the operator to use or discard.</returns>
|
||||
public virtual async Task<(bool Valid, Dictionary<string, object>? Effects)> Plan(NPCBlackboard blackboard)
|
||||
public virtual async Task<(bool Valid, Dictionary<string, object>? Effects)> Plan(NPCBlackboard blackboard,
|
||||
CancellationToken cancelToken)
|
||||
{
|
||||
return (true, null);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.MobState;
|
||||
using Content.Server.NPC.Components;
|
||||
@@ -34,7 +35,8 @@ public sealed class MeleeOperator : HTNOperator
|
||||
melee.Target = blackboard.GetValue<EntityUid>(TargetKey);
|
||||
}
|
||||
|
||||
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,
|
||||
CancellationToken cancelToken)
|
||||
{
|
||||
// Don't attack if they're already as wounded as we want them.
|
||||
if (!blackboard.TryGetValue<EntityUid>(TargetKey, out var target))
|
||||
@@ -62,7 +64,6 @@ public sealed class MeleeOperator : HTNOperator
|
||||
public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime)
|
||||
{
|
||||
base.Update(blackboard, frameTime);
|
||||
// TODO:
|
||||
var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
|
||||
var status = HTNOperatorStatus.Continuing;
|
||||
|
||||
@@ -79,6 +80,7 @@ public sealed class MeleeOperator : HTNOperator
|
||||
{
|
||||
switch (combat.Status)
|
||||
{
|
||||
case CombatStatus.TargetOutOfRange:
|
||||
case CombatStatus.Normal:
|
||||
status = HTNOperatorStatus.Continuing;
|
||||
break;
|
||||
|
||||
@@ -1,32 +1,24 @@
|
||||
using Robust.Shared.Map;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Melee;
|
||||
|
||||
/// <summary>
|
||||
/// Selects a target for melee.
|
||||
/// </summary>
|
||||
[MeansImplicitUse]
|
||||
public sealed class PickMeleeTargetOperator : NPCCombatOperator
|
||||
{
|
||||
protected override float GetRating(NPCBlackboard blackboard, EntityUid uid, EntityUid existingTarget, bool canMove, EntityQuery<TransformComponent> xformQuery)
|
||||
protected override float GetRating(NPCBlackboard blackboard, EntityUid uid, EntityUid existingTarget, float distance, bool canMove, EntityQuery<TransformComponent> xformQuery)
|
||||
{
|
||||
var ourCoordinates = blackboard.GetValueOrDefault<EntityCoordinates>(NPCBlackboard.OwnerCoordinates);
|
||||
|
||||
if (!xformQuery.TryGetComponent(uid, out var targetXform))
|
||||
return -1f;
|
||||
|
||||
var targetCoordinates = targetXform.Coordinates;
|
||||
|
||||
if (!ourCoordinates.TryDistance(EntManager, targetCoordinates, out var distance))
|
||||
return -1f;
|
||||
|
||||
var rating = 0f;
|
||||
|
||||
if (existingTarget == uid)
|
||||
{
|
||||
rating += 3f;
|
||||
rating += 2f;
|
||||
}
|
||||
|
||||
rating += 1f / distance * 4f;
|
||||
if (distance > 0f)
|
||||
rating += 50f / distance;
|
||||
|
||||
return rating;
|
||||
}
|
||||
|
||||
@@ -2,10 +2,11 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.NPC.Components;
|
||||
using Content.Server.NPC.Pathfinding;
|
||||
using Content.Server.NPC.Pathfinding.Pathfinders;
|
||||
using Content.Server.NPC.Systems;
|
||||
using Content.Shared.NPC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using YamlDotNet.Core.Tokens;
|
||||
|
||||
namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators;
|
||||
|
||||
@@ -58,7 +59,8 @@ public sealed class MoveToOperator : HTNOperator
|
||||
_steering = sysManager.GetEntitySystem<NPCSteeringSystem>();
|
||||
}
|
||||
|
||||
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,
|
||||
CancellationToken cancelToken)
|
||||
{
|
||||
if (!blackboard.TryGetValue<EntityCoordinates>(TargetKey, out var targetCoordinates))
|
||||
{
|
||||
@@ -72,8 +74,7 @@ public sealed class MoveToOperator : HTNOperator
|
||||
return (false, null);
|
||||
|
||||
if (!_mapManager.TryGetGrid(xform.GridUid, out var ownerGrid) ||
|
||||
!_mapManager.TryGetGrid(targetCoordinates.GetGridUid(_entManager), out var targetGrid) ||
|
||||
ownerGrid != targetGrid)
|
||||
!_mapManager.TryGetGrid(targetCoordinates.GetGridUid(_entManager), out var targetGrid))
|
||||
{
|
||||
return (false, null);
|
||||
}
|
||||
@@ -97,30 +98,25 @@ public sealed class MoveToOperator : HTNOperator
|
||||
});
|
||||
}
|
||||
|
||||
var cancelToken = new CancellationTokenSource();
|
||||
var access = blackboard.GetValueOrDefault<ICollection<string>>(NPCBlackboard.Access) ?? new List<string>();
|
||||
var path = await _pathfind.GetPath(
|
||||
blackboard.GetValue<EntityUid>(NPCBlackboard.Owner),
|
||||
xform.Coordinates,
|
||||
targetCoordinates,
|
||||
range,
|
||||
cancelToken,
|
||||
_pathfind.GetFlags(blackboard));
|
||||
|
||||
var job = _pathfind.RequestPath(
|
||||
new PathfindingArgs(
|
||||
blackboard.GetValue<EntityUid>(NPCBlackboard.Owner),
|
||||
access,
|
||||
body.CollisionMask,
|
||||
ownerGrid.GetTileRef(xform.Coordinates),
|
||||
ownerGrid.GetTileRef(targetCoordinates),
|
||||
range), cancelToken.Token);
|
||||
|
||||
job.Run();
|
||||
|
||||
await job.AsTask.WaitAsync(cancelToken.Token);
|
||||
|
||||
if (job.Result == null)
|
||||
if (path.Result != PathResult.Path)
|
||||
{
|
||||
return (false, null);
|
||||
}
|
||||
|
||||
return (true, new Dictionary<string, object>()
|
||||
{
|
||||
{NPCBlackboard.OwnerCoordinates, targetCoordinates},
|
||||
{PathfindKey, job.Result}
|
||||
{PathfindKey, path}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// Given steering is complicated we'll hand it off to a dedicated system rather than this singleton operator.
|
||||
@@ -131,23 +127,25 @@ public sealed class MoveToOperator : HTNOperator
|
||||
|
||||
// Need to remove the planning value for execution.
|
||||
blackboard.Remove<EntityCoordinates>(NPCBlackboard.OwnerCoordinates);
|
||||
var targetCoordinates = blackboard.GetValue<EntityCoordinates>(TargetKey);
|
||||
|
||||
// Re-use the path we may have if applicable.
|
||||
var comp = _steering.Register(blackboard.GetValue<EntityUid>(NPCBlackboard.Owner), blackboard.GetValue<EntityCoordinates>(TargetKey));
|
||||
var comp = _steering.Register(blackboard.GetValue<EntityUid>(NPCBlackboard.Owner), targetCoordinates);
|
||||
|
||||
if (blackboard.TryGetValue<float>(RangeKey, out var range))
|
||||
{
|
||||
comp.Range = range;
|
||||
}
|
||||
|
||||
if (blackboard.TryGetValue<Queue<TileRef>>(PathfindKey, out var path))
|
||||
if (blackboard.TryGetValue<PathResultEvent>(PathfindKey, out var result))
|
||||
{
|
||||
if (blackboard.TryGetValue<EntityCoordinates>(NPCBlackboard.OwnerCoordinates, out var coordinates))
|
||||
{
|
||||
_steering.PrunePath(coordinates, path);
|
||||
var mapCoords = coordinates.ToMap(_entManager);
|
||||
_steering.PrunePath(mapCoords, targetCoordinates.ToMapPos(_entManager) - mapCoords.Position, result.Path);
|
||||
}
|
||||
|
||||
comp.CurrentPath = path;
|
||||
comp.CurrentPath = result.Path;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,7 +161,7 @@ public sealed class MoveToOperator : HTNOperator
|
||||
}
|
||||
|
||||
// OwnerCoordinates is only used in planning so dump it.
|
||||
blackboard.Remove<Queue<TileRef>>(PathfindKey);
|
||||
blackboard.Remove<PathResultEvent>(PathfindKey);
|
||||
|
||||
if (RemoveKeyOnFinish)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Interaction;
|
||||
using Content.Server.NPC.Pathfinding;
|
||||
using Content.Server.NPC.Systems;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.MobState;
|
||||
using Content.Shared.MobState.Components;
|
||||
using Robust.Shared.Map;
|
||||
@@ -10,8 +14,9 @@ namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators;
|
||||
public abstract class NPCCombatOperator : HTNOperator
|
||||
{
|
||||
[Dependency] protected readonly IEntityManager EntManager = default!;
|
||||
private FactionSystem _tags = default!;
|
||||
private FactionSystem _factions = default!;
|
||||
protected InteractionSystem Interaction = default!;
|
||||
private PathfindingSystem _pathfinding = default!;
|
||||
|
||||
[ViewVariables, DataField("key")] public string Key = "CombatTarget";
|
||||
|
||||
@@ -21,16 +26,25 @@ public abstract class NPCCombatOperator : HTNOperator
|
||||
[ViewVariables, DataField("keyCoordinates")]
|
||||
public string KeyCoordinates = "CombatTargetCoordinates";
|
||||
|
||||
/// <summary>
|
||||
/// Regardless of pathfinding or LOS these are the max we'll check
|
||||
/// </summary>
|
||||
private const int MaxConsideredTargets = 10;
|
||||
private const int MaxTargetCount = 5;
|
||||
|
||||
public override void Initialize(IEntitySystemManager sysManager)
|
||||
{
|
||||
base.Initialize(sysManager);
|
||||
_tags = sysManager.GetEntitySystem<FactionSystem>();
|
||||
sysManager.GetEntitySystem<ExamineSystemShared>();
|
||||
_factions = sysManager.GetEntitySystem<FactionSystem>();
|
||||
Interaction = sysManager.GetEntitySystem<InteractionSystem>();
|
||||
_pathfinding = sysManager.GetEntitySystem<PathfindingSystem>();
|
||||
}
|
||||
|
||||
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,
|
||||
CancellationToken cancelToken)
|
||||
{
|
||||
var targets = GetTargets(blackboard);
|
||||
var targets = await GetTargets(blackboard);
|
||||
|
||||
if (targets.Count == 0)
|
||||
{
|
||||
@@ -49,34 +63,68 @@ public abstract class NPCCombatOperator : HTNOperator
|
||||
return (true, effects);
|
||||
}
|
||||
|
||||
private List<(EntityUid Entity, float Rating)> GetTargets(NPCBlackboard blackboard)
|
||||
private async Task<List<(EntityUid Entity, float Rating, float Distance)>> GetTargets(NPCBlackboard blackboard)
|
||||
{
|
||||
var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
|
||||
var radius = blackboard.GetValueOrDefault<float>(NPCBlackboard.VisionRadius, EntManager);
|
||||
var targets = new List<(EntityUid Entity, float Rating)>();
|
||||
var targets = new List<(EntityUid Entity, float Rating, float Distance)>();
|
||||
|
||||
blackboard.TryGetValue<EntityUid>(Key, out var existingTarget);
|
||||
var xformQuery = EntManager.GetEntityQuery<TransformComponent>();
|
||||
var mobQuery = EntManager.GetEntityQuery<MobStateComponent>();
|
||||
var canMove = blackboard.GetValueOrDefault<bool>(NPCBlackboard.CanMove, EntManager);
|
||||
var cancelToken = new CancellationTokenSource();
|
||||
var count = 0;
|
||||
|
||||
if (xformQuery.TryGetComponent(existingTarget, out var targetXform))
|
||||
{
|
||||
var distance = await _pathfinding.GetPathDistance(owner, targetXform.Coordinates,
|
||||
SharedInteractionSystem.InteractionRange, cancelToken.Token, _pathfinding.GetFlags(blackboard));
|
||||
|
||||
if (distance != null)
|
||||
{
|
||||
targets.Add((existingTarget, GetRating(blackboard, existingTarget, existingTarget, distance.Value, canMove, xformQuery), distance.Value));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Need a perception system instead
|
||||
foreach (var target in _tags
|
||||
// TODO: This will be expensive so will be good to optimise and cut corners.
|
||||
foreach (var target in _factions
|
||||
.GetNearbyHostiles(owner, radius))
|
||||
{
|
||||
if (mobQuery.TryGetComponent(target, out var mobState) &&
|
||||
mobState.CurrentState > DamageState.Alive)
|
||||
mobState.CurrentState > DamageState.Alive ||
|
||||
!xformQuery.TryGetComponent(target, out targetXform))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
targets.Add((target, GetRating(blackboard, target, existingTarget, canMove, xformQuery)));
|
||||
count++;
|
||||
|
||||
if (count >= MaxConsideredTargets)
|
||||
break;
|
||||
|
||||
if (!ExamineSystemShared.InRangeUnOccluded(owner, target, radius, null))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var distance = await _pathfinding.GetPathDistance(owner, targetXform.Coordinates,
|
||||
SharedInteractionSystem.InteractionRange, cancelToken.Token, _pathfinding.GetFlags(blackboard));
|
||||
|
||||
if (distance == null)
|
||||
continue;
|
||||
|
||||
targets.Add((target, GetRating(blackboard, target, existingTarget, distance.Value, canMove, xformQuery), distance.Value));
|
||||
|
||||
if (targets.Count >= MaxTargetCount)
|
||||
break;
|
||||
}
|
||||
|
||||
targets.Sort((x, y) => y.Rating.CompareTo(x.Rating));
|
||||
return targets;
|
||||
}
|
||||
|
||||
protected abstract float GetRating(NPCBlackboard blackboard, EntityUid uid, EntityUid existingTarget, bool canMove,
|
||||
protected abstract float GetRating(NPCBlackboard blackboard, EntityUid uid, EntityUid existingTarget, float distance, bool canMove,
|
||||
EntityQuery<TransformComponent> xformQuery);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.NPC.Pathfinding;
|
||||
@@ -13,8 +14,7 @@ public sealed class PickAccessibleComponentOperator : HTNOperator
|
||||
{
|
||||
[Dependency] private readonly IComponentFactory _factory = default!;
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
private PathfindingSystem _path = default!;
|
||||
private PathfindingSystem _pathfinding = default!;
|
||||
private EntityLookupSystem _lookup = default!;
|
||||
|
||||
[DataField("rangeKey", required: true)]
|
||||
@@ -26,15 +26,22 @@ public sealed class PickAccessibleComponentOperator : HTNOperator
|
||||
[ViewVariables, DataField("component", required: true)]
|
||||
public string Component = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Where the pathfinding result will be stored (if applicable). This gets removed after execution.
|
||||
/// </summary>
|
||||
[ViewVariables, DataField("pathfindKey")]
|
||||
public string PathfindKey = "MovementPathfind";
|
||||
|
||||
public override void Initialize(IEntitySystemManager sysManager)
|
||||
{
|
||||
base.Initialize(sysManager);
|
||||
_path = sysManager.GetEntitySystem<PathfindingSystem>();
|
||||
_lookup = sysManager.GetEntitySystem<EntityLookupSystem>();
|
||||
_pathfinding = sysManager.GetEntitySystem<PathfindingSystem>();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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,
|
||||
CancellationToken cancelToken)
|
||||
{
|
||||
// Check if the component exists
|
||||
if (!_factory.TryGetRegistration(Component, out var registration))
|
||||
@@ -56,6 +63,7 @@ public sealed class PickAccessibleComponentOperator : HTNOperator
|
||||
|
||||
// TODO: Need to get ones that are accessible.
|
||||
// TODO: Look at unreal HTN to see repeatable ones maybe?
|
||||
// TODO: Need type
|
||||
foreach (var entity in _lookup.GetEntitiesInRange(coordinates, range))
|
||||
{
|
||||
if (entity == owner || !query.TryGetComponent(entity, out var comp))
|
||||
@@ -69,27 +77,31 @@ public sealed class PickAccessibleComponentOperator : HTNOperator
|
||||
return (false, null);
|
||||
}
|
||||
|
||||
blackboard.TryGetValue<float>(RangeKey, out var maxRange);
|
||||
|
||||
if (maxRange == 0f)
|
||||
maxRange = 7f;
|
||||
|
||||
while (targets.Count > 0)
|
||||
{
|
||||
// TODO: Get nearest at some stage
|
||||
var target = _random.PickAndTake(targets);
|
||||
var path = await _pathfinding.GetRandomPath(
|
||||
owner,
|
||||
1.4f,
|
||||
maxRange,
|
||||
cancelToken,
|
||||
flags: _pathfinding.GetFlags(blackboard));
|
||||
|
||||
// TODO: God the path api sucks PLUS I need some fast way to get this.
|
||||
var job = _path.RequestPath(owner, target.Owner, CancellationToken.None);
|
||||
|
||||
if (job == null)
|
||||
continue;
|
||||
|
||||
await job.AsTask;
|
||||
|
||||
if (job.Result == null || !_entManager.TryGetComponent<TransformComponent>(target.Owner, out var targetXform))
|
||||
if (path.Result != PathResult.Path)
|
||||
{
|
||||
continue;
|
||||
return (false, null);
|
||||
}
|
||||
|
||||
var target = path.Path.Last().Coordinates;
|
||||
|
||||
return (true, new Dictionary<string, object>()
|
||||
{
|
||||
{ TargetKey, targetXform.Coordinates },
|
||||
{ TargetKey, target },
|
||||
{ PathfindKey, path}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.NPC.Pathfinding;
|
||||
using Content.Server.NPC.Pathfinding.Accessible;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators;
|
||||
@@ -10,9 +11,7 @@ namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators;
|
||||
/// </summary>
|
||||
public sealed class PickAccessibleOperator : HTNOperator
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
private AiReachableSystem _reachable = default!;
|
||||
private PathfindingSystem _pathfinding = default!;
|
||||
|
||||
[DataField("rangeKey", required: true)]
|
||||
public string RangeKey = string.Empty;
|
||||
@@ -20,44 +19,48 @@ public sealed class PickAccessibleOperator : HTNOperator
|
||||
[ViewVariables, DataField("targetKey", required: true)]
|
||||
public string TargetKey = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Where the pathfinding result will be stored (if applicable). This gets removed after execution.
|
||||
/// </summary>
|
||||
[ViewVariables, DataField("pathfindKey")]
|
||||
public string PathfindKey = "MovementPathfind";
|
||||
|
||||
public override void Initialize(IEntitySystemManager sysManager)
|
||||
{
|
||||
base.Initialize(sysManager);
|
||||
_reachable = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<AiReachableSystem>();
|
||||
_pathfinding = sysManager.GetEntitySystem<PathfindingSystem>();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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,
|
||||
CancellationToken cancelToken)
|
||||
{
|
||||
// Very inefficient (should weight each region by its node count) but better than the old system
|
||||
var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
|
||||
|
||||
if (!_entManager.TryGetComponent(_entManager.GetComponent<TransformComponent>(owner).GridUid, out IMapGridComponent? grid))
|
||||
return (false, null);
|
||||
blackboard.TryGetValue<float>(RangeKey, out var maxRange);
|
||||
|
||||
var reachableArgs = ReachableArgs.GetArgs(owner, blackboard.GetValueOrDefault<float>(RangeKey));
|
||||
var entityRegion = _reachable.GetRegion(owner);
|
||||
var reachableRegions = _reachable.GetReachableRegions(reachableArgs, entityRegion);
|
||||
if (maxRange == 0f)
|
||||
maxRange = 7f;
|
||||
|
||||
if (reachableRegions.Count == 0)
|
||||
return (false, null);
|
||||
var path = await _pathfinding.GetRandomPath(
|
||||
owner,
|
||||
1.4f,
|
||||
maxRange,
|
||||
cancelToken,
|
||||
flags: _pathfinding.GetFlags(blackboard));
|
||||
|
||||
var reachableNodes = new List<PathfindingNode>();
|
||||
|
||||
foreach (var region in reachableRegions)
|
||||
if (path.Result != PathResult.Path)
|
||||
{
|
||||
foreach (var node in region.Nodes)
|
||||
{
|
||||
reachableNodes.Add(node);
|
||||
}
|
||||
return (false, null);
|
||||
}
|
||||
|
||||
var targetNode = _random.Pick(reachableNodes);
|
||||
var target = path.Path.Last().Coordinates;
|
||||
|
||||
var target = grid.Grid.GridTileToLocal(targetNode.TileRef.GridIndices);
|
||||
return (true, new Dictionary<string, object>()
|
||||
{
|
||||
{ TargetKey, target },
|
||||
{ PathfindKey, path}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
@@ -10,7 +11,8 @@ public sealed class PickRandomRotationOperator : HTNOperator
|
||||
[ViewVariables, DataField("targetKey")]
|
||||
public string TargetKey = "RotateTarget";
|
||||
|
||||
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,
|
||||
CancellationToken cancelToken)
|
||||
{
|
||||
var rotation = _random.NextAngle();
|
||||
return (true, new Dictionary<string, object>()
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
@@ -22,7 +23,8 @@ public sealed class RandomOperator : HTNOperator
|
||||
/// </summary>
|
||||
[DataField("maxKey", required: true)] public string MaxKey = string.Empty;
|
||||
|
||||
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,
|
||||
CancellationToken cancelToken)
|
||||
{
|
||||
return (true, new Dictionary<string, object>()
|
||||
{
|
||||
|
||||
@@ -1,38 +1,19 @@
|
||||
using Robust.Shared.Map;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Ranged;
|
||||
|
||||
/// <summary>
|
||||
/// Selects a target for ranged combat.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed class PickRangedTargetOperator : NPCCombatOperator
|
||||
{
|
||||
protected override float GetRating(NPCBlackboard blackboard, EntityUid uid, EntityUid existingTarget, bool canMove, EntityQuery<TransformComponent> xformQuery)
|
||||
protected override float GetRating(NPCBlackboard blackboard, EntityUid uid, EntityUid existingTarget, float distance, bool canMove, EntityQuery<TransformComponent> xformQuery)
|
||||
{
|
||||
var ourCoordinates = blackboard.GetValueOrDefault<EntityCoordinates>(NPCBlackboard.OwnerCoordinates);
|
||||
|
||||
if (!xformQuery.TryGetComponent(uid, out var targetXform))
|
||||
return -1f;
|
||||
|
||||
var targetCoordinates = targetXform.Coordinates;
|
||||
|
||||
if (!ourCoordinates.TryDistance(EntManager, targetCoordinates, out var distance))
|
||||
return -1f;
|
||||
|
||||
// TODO: Uhh make this better with penetration or something.
|
||||
var inLOS = Interaction.InRangeUnobstructed(blackboard.GetValue<EntityUid>(NPCBlackboard.Owner),
|
||||
uid, distance + 0.1f);
|
||||
|
||||
if (!canMove && !inLOS)
|
||||
return -1f;
|
||||
|
||||
// Yeah look I just came up with values that seemed okay but they will need a lot of tweaking.
|
||||
// Having a debug overlay just to project these would be very useful when finetuning in future.
|
||||
var rating = 0f;
|
||||
|
||||
if (inLOS)
|
||||
rating += 4f;
|
||||
|
||||
if (existingTarget == uid)
|
||||
{
|
||||
rating += 2f;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.NPC.Components;
|
||||
using Content.Shared.MobState;
|
||||
@@ -24,7 +25,8 @@ public sealed class RangedOperator : HTNOperator
|
||||
|
||||
// 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,
|
||||
CancellationToken cancelToken)
|
||||
{
|
||||
// Don't attack if they're already as wounded as we want them.
|
||||
if (!blackboard.TryGetValue<EntityUid>(TargetKey, out var target))
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators;
|
||||
@@ -12,7 +13,8 @@ public sealed class SetFloatOperator : HTNOperator
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("amount")]
|
||||
public float Amount;
|
||||
|
||||
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,
|
||||
CancellationToken cancelToken)
|
||||
{
|
||||
return (true, new Dictionary<string, object>()
|
||||
{
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Chemistry.Components.SolutionManager;
|
||||
using Content.Server.NPC.Components;
|
||||
@@ -31,7 +32,8 @@ public sealed class PickNearbyInjectableOperator : HTNOperator
|
||||
_lookup = sysManager.GetEntitySystem<EntityLookupSystem>();
|
||||
}
|
||||
|
||||
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,
|
||||
CancellationToken cancelToken)
|
||||
{
|
||||
var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.NPC.Components;
|
||||
using Robust.Shared.Random;
|
||||
@@ -10,7 +11,8 @@ public sealed class PickPathfindPointOperator : HTNOperator
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
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,
|
||||
CancellationToken cancelToken)
|
||||
{
|
||||
var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user