From a5b04b49b6c7eee95c9f4d33d6d95b5ce69fcc3f Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Thu, 15 Dec 2022 15:30:28 +1100 Subject: [PATCH] Fix NPC obstacle handling (#13007) --- .../NPC/Components/NPCRVOComponent.cs | 38 --------------- Content.Server/NPC/NPCBlackboard.cs | 5 ++ Content.Server/NPC/NPCBlackboardSerializer.cs | 15 +++++- .../Systems/NPCSteeringSystem.Avoidance.cs | 38 --------------- .../NPC/Systems/NPCSteeringSystem.Context.cs | 12 +++-- .../Systems/NPCSteeringSystem.Obstacles.cs | 48 +++++++++++-------- .../NPC/Systems/NPCSteeringSystem.cs | 12 +++-- Content.Shared/CCVar/CCVars.cs | 2 - 8 files changed, 63 insertions(+), 107 deletions(-) delete mode 100644 Content.Server/NPC/Components/NPCRVOComponent.cs delete mode 100644 Content.Server/NPC/Systems/NPCSteeringSystem.Avoidance.cs diff --git a/Content.Server/NPC/Components/NPCRVOComponent.cs b/Content.Server/NPC/Components/NPCRVOComponent.cs deleted file mode 100644 index 3fe76fc24e..0000000000 --- a/Content.Server/NPC/Components/NPCRVOComponent.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Content.Server.NPC.Systems; - -namespace Content.Server.NPC.Components; - -/// -/// Stores data for RVO collision avoidance -/// -[RegisterComponent] -public sealed class NPCRVOComponent : Component -{ - /// - /// Maximum number of dynamic neighbors to consider for collision avoidance. - /// - [ViewVariables(VVAccess.ReadWrite), DataField("maxNeighbors")] - public int MaxNeighbors = 5; - - /// - /// Time horizon to consider for dynamic neighbor collision - /// - [ViewVariables(VVAccess.ReadWrite)] public float TimeHorizon = 3f; - - /// - /// Time horizon to consider for static neighbor collision. - /// - [ViewVariables(VVAccess.ReadWrite)] public float ObstacleTimeHorizon = 3f; - - /// - /// Range considered for neighbor agents - /// - [ViewVariables(VVAccess.ReadWrite), DataField("neighborRange")] - public float NeighborRange = 3f; - - [ViewVariables] - public readonly HashSet ObstacleNeighbors = new(); - - [ViewVariables] - public readonly HashSet AgentNeighbors = new(); -} diff --git a/Content.Server/NPC/NPCBlackboard.cs b/Content.Server/NPC/NPCBlackboard.cs index f0a87a2098..e60be3fb96 100644 --- a/Content.Server/NPC/NPCBlackboard.cs +++ b/Content.Server/NPC/NPCBlackboard.cs @@ -42,6 +42,11 @@ public sealed class NPCBlackboard : IEnumerable> /// public bool ReadOnly = false; + public void Clear() + { + _blackboard.Clear(); + } + public NPCBlackboard ShallowClone() { var dict = new NPCBlackboard(); diff --git a/Content.Server/NPC/NPCBlackboardSerializer.cs b/Content.Server/NPC/NPCBlackboardSerializer.cs index e2335d5aad..ec7a0756c2 100644 --- a/Content.Server/NPC/NPCBlackboardSerializer.cs +++ b/Content.Server/NPC/NPCBlackboardSerializer.cs @@ -8,7 +8,7 @@ using Robust.Shared.Utility; namespace Content.Server.NPC; -public sealed class NPCBlackboardSerializer : ITypeReader +public sealed class NPCBlackboardSerializer : ITypeReader, ITypeCopier { public ValidationNode Validate(ISerializationManager serializationManager, MappingDataNode node, IDependencyCollection dependencies, ISerializationContext? context = null) @@ -79,4 +79,17 @@ public sealed class NPCBlackboardSerializer : ITypeReader(); - configManager.OnValueChanged(CCVars.NPCCollisionAvoidance, SetCollisionAvoidance); - } - - private void ShutdownAvoidance() - { - var configManager = IoCManager.Resolve(); - 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; - } -} diff --git a/Content.Server/NPC/Systems/NPCSteeringSystem.Context.cs b/Content.Server/NPC/Systems/NPCSteeringSystem.Context.cs index 14a087b6f3..34c53b8e98 100644 --- a/Content.Server/NPC/Systems/NPCSteeringSystem.Context.cs +++ b/Content.Server/NPC/Systems/NPCSteeringSystem.Context.cs @@ -86,7 +86,7 @@ public sealed partial class NPCSteeringSystem } else { - arrivalDistance = SharedInteractionSystem.InteractionRange - 0.8f; + arrivalDistance = SharedInteractionSystem.InteractionRange - 0.65f; } // Check if mapids match. @@ -105,9 +105,15 @@ public sealed partial class NPCSteeringSystem if (direction.Length <= arrivalDistance) { // 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. switch (status) diff --git a/Content.Server/NPC/Systems/NPCSteeringSystem.Obstacles.cs b/Content.Server/NPC/Systems/NPCSteeringSystem.Obstacles.cs index 042e9df734..03f1fb06f9 100644 --- a/Content.Server/NPC/Systems/NPCSteeringSystem.Obstacles.cs +++ b/Content.Server/NPC/Systems/NPCSteeringSystem.Obstacles.cs @@ -1,11 +1,12 @@ +using Content.Server.CombatMode; using Content.Server.Destructible; using Content.Server.NPC.Components; using Content.Server.NPC.Pathfinding; using Content.Shared.Doors.Components; using Content.Shared.NPC; -using Content.Shared.Weapons.Melee; using Robust.Shared.Physics; using Robust.Shared.Physics.Components; +using Robust.Shared.Utility; namespace Content.Server.NPC.Systems; @@ -33,9 +34,7 @@ public sealed partial class NPCSteeringSystem private SteeringObstacleStatus TryHandleFlags(NPCSteeringComponent component, PathPoly poly, EntityQuery bodyQuery) { - if (poly.Data.IsFreeSpace) - return SteeringObstacleStatus.Completed; - + DebugTools.Assert(!poly.Data.IsFreeSpace); // TODO: Store PathFlags on the steering comp // and be able to re-check it. @@ -111,26 +110,35 @@ public sealed partial class NPCSteeringSystem return SteeringObstacleStatus.Completed; } // Try smashing obstacles. - else if ((component.Flags & PathFlags.Smashing) != 0x0 && TryComp(component.Owner, out var melee) && - TryComp(melee.Weapon, out var meleeWeapon)) + else if ((component.Flags & PathFlags.Smashing) != 0x0) { - var destructibleQuery = GetEntityQuery(); + var meleeWeapon = _melee.GetWeapon(component.Owner); - // TODO: This is a hack around grilles and windows. - _random.Shuffle(obstacleEnts); - - foreach (var ent in obstacleEnts) + if (meleeWeapon != null && meleeWeapon.NextAttack <= _timing.CurTime && TryComp(component.Owner, out var combatMode)) { - // TODO: Validate we can damage it - if (destructibleQuery.HasComponent(ent)) - { - _melee.AttemptLightAttack(component.Owner, meleeWeapon, ent); - return SteeringObstacleStatus.Continuing; - } - } + combatMode.IsInCombatMode = true; + var destructibleQuery = GetEntityQuery(); - if (obstacleEnts.Count == 0) - return SteeringObstacleStatus.Completed; + // TODO: This is a hack around grilles and windows. + _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; diff --git a/Content.Server/NPC/Systems/NPCSteeringSystem.cs b/Content.Server/NPC/Systems/NPCSteeringSystem.cs index 1f5a369188..6423d026f3 100644 --- a/Content.Server/NPC/Systems/NPCSteeringSystem.cs +++ b/Content.Server/NPC/Systems/NPCSteeringSystem.cs @@ -41,6 +41,7 @@ namespace Content.Server.NPC.Systems [Dependency] private readonly IAdminManager _admin = default!; [Dependency] private readonly IConfigurationManager _configManager = default!; + [Dependency] private readonly IDependencyCollection _dependencies = default!; [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IParallelManager _parallel = default!; @@ -66,6 +67,8 @@ namespace Content.Server.NPC.Systems private readonly HashSet _subscribedSessions = new(); + private object _obstacles = new(); + public override void Initialize() { base.Initialize(); @@ -76,7 +79,6 @@ namespace Content.Server.NPC.Systems } UpdatesBefore.Add(typeof(SharedPhysicsSystem)); - InitializeAvoidance(); _configManager.OnValueChanged(CCVars.NPCEnabled, SetNPCEnabled); _configManager.OnValueChanged(CCVars.NPCPathfinding, SetNPCPathfinding); @@ -114,7 +116,6 @@ namespace Content.Server.NPC.Systems public override void Shutdown() { base.Shutdown(); - ShutdownAvoidance(); _configManager.UnsubValueChanged(CCVars.NPCEnabled, SetNPCEnabled); } @@ -131,6 +132,7 @@ namespace Content.Server.NPC.Systems private void OnSteeringShutdown(EntityUid uid, NPCSteeringComponent component, ComponentShutdown args) { + // Cancel any active pathfinding jobs as they're irrelevant. component.PathfindToken?.Cancel(); } @@ -151,7 +153,6 @@ namespace Content.Server.NPC.Systems component.Flags = _pathfindingSystem.GetFlags(uid); } - EnsureComp(uid); component.Coordinates = coordinates; return component; } @@ -185,7 +186,6 @@ namespace Content.Server.NPC.Systems component.PathfindToken?.Cancel(); component.PathfindToken = null; - RemComp(uid); RemComp(uid); } @@ -203,7 +203,7 @@ namespace Content.Server.NPC.Systems var npcs = EntityQuery() .ToArray(); - var options = new ParallelOptions() + var options = new ParallelOptions { MaxDegreeOfParallelism = _parallel.ParallelProcessCount, }; @@ -261,6 +261,8 @@ namespace Content.Server.NPC.Systems EntityQuery xformQuery, float frameTime) { + IoCManager.InitThread(_dependencies, replaceExisting: true); + if (Deleted(steering.Coordinates.EntityId)) { SetDirection(mover, steering, Vector2.Zero); diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index 461246482d..36931b3eb6 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -542,8 +542,6 @@ namespace Content.Shared.CCVar /// public static readonly CVarDef NPCPathfinding = CVarDef.Create("npc.pathfinding", true); - public static readonly CVarDef NPCCollisionAvoidance = CVarDef.Create("npc.collision_avoidance", true); - /* * Net */