Fix NPC obstacle handling (#13007)
This commit is contained in:
@@ -1,38 +0,0 @@
|
|||||||
using Content.Server.NPC.Systems;
|
|
||||||
|
|
||||||
namespace Content.Server.NPC.Components;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stores data for RVO collision avoidance
|
|
||||||
/// </summary>
|
|
||||||
[RegisterComponent]
|
|
||||||
public sealed class NPCRVOComponent : Component
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Maximum number of dynamic neighbors to consider for collision avoidance.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables(VVAccess.ReadWrite), DataField("maxNeighbors")]
|
|
||||||
public int MaxNeighbors = 5;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Time horizon to consider for dynamic neighbor collision
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)] public float TimeHorizon = 3f;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Time horizon to consider for static neighbor collision.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)] public float ObstacleTimeHorizon = 3f;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Range considered for neighbor agents
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables(VVAccess.ReadWrite), DataField("neighborRange")]
|
|
||||||
public float NeighborRange = 3f;
|
|
||||||
|
|
||||||
[ViewVariables]
|
|
||||||
public readonly HashSet<EntityUid> ObstacleNeighbors = new();
|
|
||||||
|
|
||||||
[ViewVariables]
|
|
||||||
public readonly HashSet<EntityUid> AgentNeighbors = new();
|
|
||||||
}
|
|
||||||
@@ -42,6 +42,11 @@ public sealed class NPCBlackboard : IEnumerable<KeyValuePair<string, object>>
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ReadOnly = false;
|
public bool ReadOnly = false;
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
_blackboard.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
public NPCBlackboard ShallowClone()
|
public NPCBlackboard ShallowClone()
|
||||||
{
|
{
|
||||||
var dict = new NPCBlackboard();
|
var dict = new NPCBlackboard();
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ using Robust.Shared.Utility;
|
|||||||
|
|
||||||
namespace Content.Server.NPC;
|
namespace Content.Server.NPC;
|
||||||
|
|
||||||
public sealed class NPCBlackboardSerializer : ITypeReader<NPCBlackboard, MappingDataNode>
|
public sealed class NPCBlackboardSerializer : ITypeReader<NPCBlackboard, MappingDataNode>, ITypeCopier<NPCBlackboard>
|
||||||
{
|
{
|
||||||
public ValidationNode Validate(ISerializationManager serializationManager, MappingDataNode node,
|
public ValidationNode Validate(ISerializationManager serializationManager, MappingDataNode node,
|
||||||
IDependencyCollection dependencies, ISerializationContext? context = null)
|
IDependencyCollection dependencies, ISerializationContext? context = null)
|
||||||
@@ -79,4 +79,17 @@ public sealed class NPCBlackboardSerializer : ITypeReader<NPCBlackboard, Mapping
|
|||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void CopyTo(ISerializationManager serializationManager, NPCBlackboard source, ref NPCBlackboard target, bool skipHook,
|
||||||
|
ISerializationContext? context = null)
|
||||||
|
{
|
||||||
|
target.Clear();
|
||||||
|
using var enumerator = source.GetEnumerator();
|
||||||
|
|
||||||
|
while (enumerator.MoveNext())
|
||||||
|
{
|
||||||
|
var current = enumerator.Current;
|
||||||
|
target.SetValue(current.Key, current.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
using Content.Shared.CCVar;
|
|
||||||
using Robust.Shared.Configuration;
|
|
||||||
|
|
||||||
namespace Content.Server.NPC.Systems;
|
|
||||||
|
|
||||||
public sealed partial class NPCSteeringSystem
|
|
||||||
{
|
|
||||||
// TODO
|
|
||||||
|
|
||||||
// Derived from RVO2 library which uses ORCA (optimal reciprocal collision avoidance).
|
|
||||||
// Could also potentially use something force based or RVO or detour crowd.
|
|
||||||
|
|
||||||
public bool CollisionAvoidanceEnabled { get; set; } = true;
|
|
||||||
|
|
||||||
public bool ObstacleAvoidanceEnabled { get; set; } = true;
|
|
||||||
|
|
||||||
private const float Radius = 0.35f;
|
|
||||||
private const float RVO_EPSILON = 0.00001f;
|
|
||||||
|
|
||||||
private void InitializeAvoidance()
|
|
||||||
{
|
|
||||||
var configManager = IoCManager.Resolve<IConfigurationManager>();
|
|
||||||
configManager.OnValueChanged(CCVars.NPCCollisionAvoidance, SetCollisionAvoidance);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ShutdownAvoidance()
|
|
||||||
{
|
|
||||||
var configManager = IoCManager.Resolve<IConfigurationManager>();
|
|
||||||
configManager.UnsubValueChanged(CCVars.NPCCollisionAvoidance, SetCollisionAvoidance);
|
|
||||||
}
|
|
||||||
|
|
||||||
// I deleted all of my relevant code for now as I only had dynamic body avoidance working and not static
|
|
||||||
// but it will be added back real soon.
|
|
||||||
private void SetCollisionAvoidance(bool obj)
|
|
||||||
{
|
|
||||||
CollisionAvoidanceEnabled = obj;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -86,7 +86,7 @@ public sealed partial class NPCSteeringSystem
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
arrivalDistance = SharedInteractionSystem.InteractionRange - 0.8f;
|
arrivalDistance = SharedInteractionSystem.InteractionRange - 0.65f;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if mapids match.
|
// Check if mapids match.
|
||||||
@@ -105,9 +105,15 @@ public sealed partial class NPCSteeringSystem
|
|||||||
if (direction.Length <= arrivalDistance)
|
if (direction.Length <= arrivalDistance)
|
||||||
{
|
{
|
||||||
// Node needs some kind of special handling like access or smashing.
|
// Node needs some kind of special handling like access or smashing.
|
||||||
if (steering.CurrentPath.TryPeek(out var node))
|
if (steering.CurrentPath.TryPeek(out var node) && !node.Data.IsFreeSpace)
|
||||||
{
|
{
|
||||||
var status = TryHandleFlags(steering, node, bodyQuery);
|
SteeringObstacleStatus status;
|
||||||
|
|
||||||
|
// Breaking behaviours and the likes.
|
||||||
|
lock (_obstacles)
|
||||||
|
{
|
||||||
|
status = TryHandleFlags(steering, node, bodyQuery);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Need to handle re-pathing in case the target moves around.
|
// TODO: Need to handle re-pathing in case the target moves around.
|
||||||
switch (status)
|
switch (status)
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
|
using Content.Server.CombatMode;
|
||||||
using Content.Server.Destructible;
|
using Content.Server.Destructible;
|
||||||
using Content.Server.NPC.Components;
|
using Content.Server.NPC.Components;
|
||||||
using Content.Server.NPC.Pathfinding;
|
using Content.Server.NPC.Pathfinding;
|
||||||
using Content.Shared.Doors.Components;
|
using Content.Shared.Doors.Components;
|
||||||
using Content.Shared.NPC;
|
using Content.Shared.NPC;
|
||||||
using Content.Shared.Weapons.Melee;
|
|
||||||
using Robust.Shared.Physics;
|
using Robust.Shared.Physics;
|
||||||
using Robust.Shared.Physics.Components;
|
using Robust.Shared.Physics.Components;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Server.NPC.Systems;
|
namespace Content.Server.NPC.Systems;
|
||||||
|
|
||||||
@@ -33,9 +34,7 @@ public sealed partial class NPCSteeringSystem
|
|||||||
|
|
||||||
private SteeringObstacleStatus TryHandleFlags(NPCSteeringComponent component, PathPoly poly, EntityQuery<PhysicsComponent> bodyQuery)
|
private SteeringObstacleStatus TryHandleFlags(NPCSteeringComponent component, PathPoly poly, EntityQuery<PhysicsComponent> bodyQuery)
|
||||||
{
|
{
|
||||||
if (poly.Data.IsFreeSpace)
|
DebugTools.Assert(!poly.Data.IsFreeSpace);
|
||||||
return SteeringObstacleStatus.Completed;
|
|
||||||
|
|
||||||
// TODO: Store PathFlags on the steering comp
|
// TODO: Store PathFlags on the steering comp
|
||||||
// and be able to re-check it.
|
// and be able to re-check it.
|
||||||
|
|
||||||
@@ -111,26 +110,35 @@ public sealed partial class NPCSteeringSystem
|
|||||||
return SteeringObstacleStatus.Completed;
|
return SteeringObstacleStatus.Completed;
|
||||||
}
|
}
|
||||||
// Try smashing obstacles.
|
// Try smashing obstacles.
|
||||||
else if ((component.Flags & PathFlags.Smashing) != 0x0 && TryComp<NPCMeleeCombatComponent>(component.Owner, out var melee) &&
|
else if ((component.Flags & PathFlags.Smashing) != 0x0)
|
||||||
TryComp<MeleeWeaponComponent>(melee.Weapon, out var meleeWeapon))
|
|
||||||
{
|
{
|
||||||
var destructibleQuery = GetEntityQuery<DestructibleComponent>();
|
var meleeWeapon = _melee.GetWeapon(component.Owner);
|
||||||
|
|
||||||
// TODO: This is a hack around grilles and windows.
|
if (meleeWeapon != null && meleeWeapon.NextAttack <= _timing.CurTime && TryComp<CombatModeComponent>(component.Owner, out var combatMode))
|
||||||
_random.Shuffle(obstacleEnts);
|
|
||||||
|
|
||||||
foreach (var ent in obstacleEnts)
|
|
||||||
{
|
{
|
||||||
// TODO: Validate we can damage it
|
combatMode.IsInCombatMode = true;
|
||||||
if (destructibleQuery.HasComponent(ent))
|
var destructibleQuery = GetEntityQuery<DestructibleComponent>();
|
||||||
{
|
|
||||||
_melee.AttemptLightAttack(component.Owner, meleeWeapon, ent);
|
|
||||||
return SteeringObstacleStatus.Continuing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (obstacleEnts.Count == 0)
|
// TODO: This is a hack around grilles and windows.
|
||||||
return SteeringObstacleStatus.Completed;
|
_random.Shuffle(obstacleEnts);
|
||||||
|
|
||||||
|
foreach (var ent in obstacleEnts)
|
||||||
|
{
|
||||||
|
// TODO: Validate we can damage it
|
||||||
|
if (destructibleQuery.HasComponent(ent))
|
||||||
|
{
|
||||||
|
_melee.AttemptLightAttack(component.Owner, meleeWeapon, ent);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
combatMode.IsInCombatMode = false;
|
||||||
|
|
||||||
|
if (obstacleEnts.Count == 0)
|
||||||
|
return SteeringObstacleStatus.Completed;
|
||||||
|
|
||||||
|
return SteeringObstacleStatus.Continuing;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return SteeringObstacleStatus.Failed;
|
return SteeringObstacleStatus.Failed;
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ namespace Content.Server.NPC.Systems
|
|||||||
|
|
||||||
[Dependency] private readonly IAdminManager _admin = default!;
|
[Dependency] private readonly IAdminManager _admin = default!;
|
||||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||||
|
[Dependency] private readonly IDependencyCollection _dependencies = default!;
|
||||||
[Dependency] private readonly IGameTiming _timing = default!;
|
[Dependency] private readonly IGameTiming _timing = default!;
|
||||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||||
[Dependency] private readonly IParallelManager _parallel = default!;
|
[Dependency] private readonly IParallelManager _parallel = default!;
|
||||||
@@ -66,6 +67,8 @@ namespace Content.Server.NPC.Systems
|
|||||||
|
|
||||||
private readonly HashSet<ICommonSession> _subscribedSessions = new();
|
private readonly HashSet<ICommonSession> _subscribedSessions = new();
|
||||||
|
|
||||||
|
private object _obstacles = new();
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
@@ -76,7 +79,6 @@ namespace Content.Server.NPC.Systems
|
|||||||
}
|
}
|
||||||
|
|
||||||
UpdatesBefore.Add(typeof(SharedPhysicsSystem));
|
UpdatesBefore.Add(typeof(SharedPhysicsSystem));
|
||||||
InitializeAvoidance();
|
|
||||||
_configManager.OnValueChanged(CCVars.NPCEnabled, SetNPCEnabled);
|
_configManager.OnValueChanged(CCVars.NPCEnabled, SetNPCEnabled);
|
||||||
_configManager.OnValueChanged(CCVars.NPCPathfinding, SetNPCPathfinding);
|
_configManager.OnValueChanged(CCVars.NPCPathfinding, SetNPCPathfinding);
|
||||||
|
|
||||||
@@ -114,7 +116,6 @@ namespace Content.Server.NPC.Systems
|
|||||||
public override void Shutdown()
|
public override void Shutdown()
|
||||||
{
|
{
|
||||||
base.Shutdown();
|
base.Shutdown();
|
||||||
ShutdownAvoidance();
|
|
||||||
_configManager.UnsubValueChanged(CCVars.NPCEnabled, SetNPCEnabled);
|
_configManager.UnsubValueChanged(CCVars.NPCEnabled, SetNPCEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,6 +132,7 @@ namespace Content.Server.NPC.Systems
|
|||||||
|
|
||||||
private void OnSteeringShutdown(EntityUid uid, NPCSteeringComponent component, ComponentShutdown args)
|
private void OnSteeringShutdown(EntityUid uid, NPCSteeringComponent component, ComponentShutdown args)
|
||||||
{
|
{
|
||||||
|
// Cancel any active pathfinding jobs as they're irrelevant.
|
||||||
component.PathfindToken?.Cancel();
|
component.PathfindToken?.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +153,6 @@ namespace Content.Server.NPC.Systems
|
|||||||
component.Flags = _pathfindingSystem.GetFlags(uid);
|
component.Flags = _pathfindingSystem.GetFlags(uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
EnsureComp<NPCRVOComponent>(uid);
|
|
||||||
component.Coordinates = coordinates;
|
component.Coordinates = coordinates;
|
||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
@@ -185,7 +186,6 @@ namespace Content.Server.NPC.Systems
|
|||||||
|
|
||||||
component.PathfindToken?.Cancel();
|
component.PathfindToken?.Cancel();
|
||||||
component.PathfindToken = null;
|
component.PathfindToken = null;
|
||||||
RemComp<NPCRVOComponent>(uid);
|
|
||||||
RemComp<NPCSteeringComponent>(uid);
|
RemComp<NPCSteeringComponent>(uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,7 +203,7 @@ namespace Content.Server.NPC.Systems
|
|||||||
|
|
||||||
var npcs = EntityQuery<NPCSteeringComponent, ActiveNPCComponent, InputMoverComponent, TransformComponent>()
|
var npcs = EntityQuery<NPCSteeringComponent, ActiveNPCComponent, InputMoverComponent, TransformComponent>()
|
||||||
.ToArray();
|
.ToArray();
|
||||||
var options = new ParallelOptions()
|
var options = new ParallelOptions
|
||||||
{
|
{
|
||||||
MaxDegreeOfParallelism = _parallel.ParallelProcessCount,
|
MaxDegreeOfParallelism = _parallel.ParallelProcessCount,
|
||||||
};
|
};
|
||||||
@@ -261,6 +261,8 @@ namespace Content.Server.NPC.Systems
|
|||||||
EntityQuery<TransformComponent> xformQuery,
|
EntityQuery<TransformComponent> xformQuery,
|
||||||
float frameTime)
|
float frameTime)
|
||||||
{
|
{
|
||||||
|
IoCManager.InitThread(_dependencies, replaceExisting: true);
|
||||||
|
|
||||||
if (Deleted(steering.Coordinates.EntityId))
|
if (Deleted(steering.Coordinates.EntityId))
|
||||||
{
|
{
|
||||||
SetDirection(mover, steering, Vector2.Zero);
|
SetDirection(mover, steering, Vector2.Zero);
|
||||||
|
|||||||
@@ -542,8 +542,6 @@ namespace Content.Shared.CCVar
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly CVarDef<bool> NPCPathfinding = CVarDef.Create("npc.pathfinding", true);
|
public static readonly CVarDef<bool> NPCPathfinding = CVarDef.Create("npc.pathfinding", true);
|
||||||
|
|
||||||
public static readonly CVarDef<bool> NPCCollisionAvoidance = CVarDef.Create("npc.collision_avoidance", true);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Net
|
* Net
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user