diff --git a/Content.Client/AI/ClientPathfindingDebugSystem.cs b/Content.Client/AI/ClientPathfindingDebugSystem.cs index 638bbc2976..23414557dc 100644 --- a/Content.Client/AI/ClientPathfindingDebugSystem.cs +++ b/Content.Client/AI/ClientPathfindingDebugSystem.cs @@ -335,7 +335,7 @@ namespace Content.Client.AI { Regions[gridId][chunk].Add(region, nodes); _regionColors[gridId][chunk][region] = new Color(robustRandom.NextFloat(), robustRandom.NextFloat(), - robustRandom.NextFloat(), 0.3f); + robustRandom.NextFloat(), 0.5f); } } } diff --git a/Content.IntegrationTests/Tests/Pathfinding/PathfindingChunkTest.cs b/Content.IntegrationTests/Tests/Pathfinding/PathfindingChunkTest.cs deleted file mode 100644 index 09a2247578..0000000000 --- a/Content.IntegrationTests/Tests/Pathfinding/PathfindingChunkTest.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.Linq; -using System.Threading.Tasks; -using Content.Server.AI.Pathfinding; -using NUnit.Framework; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Map; -using Robust.Shared.Maths; - -namespace Content.IntegrationTests.Tests.Pathfinding -{ - [TestFixture] - [TestOf(typeof(PathfindingChunk))] - public sealed class PathfindingChunkTest : ContentIntegrationTest - { - [Test] - public async Task Test() - { - var server = StartServer(); - - server.Assert(() => - { - var pathfindingSystem = EntitySystem.Get(); - var mapMan = IoCManager.Resolve(); - - // Setup - var grid = GetMainGrid(mapMan); - var chunkTile = grid.GetTileRef(new Vector2i(0, 0)); - var chunk = pathfindingSystem.GetChunk(chunkTile); - Assert.That(chunk.Nodes.Length == PathfindingChunk.ChunkSize * PathfindingChunk.ChunkSize); - - // Neighbors - var chunkNeighbors = chunk.GetNeighbors().ToList(); - Assert.That(chunkNeighbors.Count == 0); - var neighborChunkTile = grid.GetTileRef(new Vector2i(PathfindingChunk.ChunkSize, PathfindingChunk.ChunkSize)); - var neighborChunk = pathfindingSystem.GetChunk(neighborChunkTile); - chunkNeighbors = chunk.GetNeighbors().ToList(); - Assert.That(chunkNeighbors.Count == 1); - - // Directions - Assert.That(PathfindingHelpers.RelativeDirection(neighborChunk, chunk) == Direction.NorthEast); - Assert.That(PathfindingHelpers.RelativeDirection(chunk.Nodes[0, 1], chunk.Nodes[0, 0]) == Direction.North); - - // Nodes - var node = chunk.Nodes[1, 1]; - var nodeNeighbors = node.GetNeighbors().ToList(); - Assert.That(nodeNeighbors.Count == 8); - - // Bottom-left corner with no chunk neighbor - node = chunk.Nodes[0, 0]; - nodeNeighbors = node.GetNeighbors().ToList(); - Assert.That(nodeNeighbors.Count == 3); - - // Given we have 1 NE neighbor then NE corner should have 4 neighbors due to the 1 extra from the neighbor chunk - node = chunk.Nodes[PathfindingChunk.ChunkSize - 1, PathfindingChunk.ChunkSize - 1]; - nodeNeighbors = node.GetNeighbors().ToList(); - Assert.That(nodeNeighbors.Count == 4); - }); - await server.WaitIdleAsync(); - } - } -} diff --git a/Content.Server/AI/Pathfinding/GridPathfindingComponent.cs b/Content.Server/AI/Pathfinding/GridPathfindingComponent.cs new file mode 100644 index 0000000000..a78841abf3 --- /dev/null +++ b/Content.Server/AI/Pathfinding/GridPathfindingComponent.cs @@ -0,0 +1,8 @@ +namespace Content.Server.AI.Pathfinding; + +[RegisterComponent] +[Friend(typeof(PathfindingSystem))] +public sealed class GridPathfindingComponent : Component, IPathfindingGraph +{ + public Dictionary Graph = new(); +} diff --git a/Content.Server/AI/Pathfinding/IPathfindingGraph.cs b/Content.Server/AI/Pathfinding/IPathfindingGraph.cs new file mode 100644 index 0000000000..645b980c8c --- /dev/null +++ b/Content.Server/AI/Pathfinding/IPathfindingGraph.cs @@ -0,0 +1,6 @@ +namespace Content.Server.AI.Pathfinding; + +public interface IPathfindingGraph +{ + +} diff --git a/Content.Server/AI/Pathfinding/PathfindingChunk.cs b/Content.Server/AI/Pathfinding/PathfindingChunk.cs index 353dd2e6d9..1254540ce8 100644 --- a/Content.Server/AI/Pathfinding/PathfindingChunk.cs +++ b/Content.Server/AI/Pathfinding/PathfindingChunk.cs @@ -17,7 +17,7 @@ namespace Content.Server.AI.Pathfinding public sealed class PathfindingChunk { public TimeSpan LastUpdate { get; private set; } - public GridId GridId { get; } + public EntityUid GridId { get; } public Vector2i Indices => _indices; private readonly Vector2i _indices; @@ -27,7 +27,7 @@ namespace Content.Server.AI.Pathfinding public PathfindingNode[,] Nodes => _nodes; private readonly PathfindingNode[,] _nodes = new PathfindingNode[ChunkSize,ChunkSize]; - public PathfindingChunk(GridId gridId, Vector2i indices) + public PathfindingChunk(EntityUid gridId, Vector2i indices) { GridId = gridId; _indices = indices; @@ -57,10 +57,10 @@ namespace Content.Server.AI.Pathfinding .RaiseEvent(EventSource.Local, new PathfindingChunkUpdateMessage(this)); } - public IEnumerable GetNeighbors() + public IEnumerable GetNeighbors(IEntityManager? entManager = null) { - var pathfindingSystem = EntitySystem.Get(); - var chunkGrid = pathfindingSystem.Graph[GridId]; + IoCManager.Resolve(ref entManager); + var chunkGrid = entManager.GetComponent(GridId).Graph; for (var x = -1; x <= 1; x++) { diff --git a/Content.Server/AI/Pathfinding/PathfindingNode.cs b/Content.Server/AI/Pathfinding/PathfindingNode.cs index 0641d84c8e..d99bdc3511 100644 --- a/Content.Server/AI/Pathfinding/PathfindingNode.cs +++ b/Content.Server/AI/Pathfinding/PathfindingNode.cs @@ -38,17 +38,6 @@ namespace Content.Server.AI.Pathfinding GenerateMask(); } - public static bool IsRelevant(EntityUid entity, IPhysBody physicsComponent) - { - if (IoCManager.Resolve().GetComponent(entity).GridID == GridId.Invalid || - (PathfindingSystem.TrackedCollisionLayers & physicsComponent.CollisionLayer) == 0) - { - return false; - } - - return true; - } - /// /// Return our neighboring nodes (even across chunks) /// @@ -93,7 +82,7 @@ namespace Content.Server.AI.Pathfinding } } - public PathfindingNode? GetNeighbor(Direction direction) + public PathfindingNode? GetNeighbor(Direction direction, IEntityManager? entManager = null) { var chunkXOffset = TileRef.X - ParentChunk.Indices.X; var chunkYOffset = TileRef.Y - ParentChunk.Indices.Y; @@ -254,9 +243,9 @@ namespace Content.Server.AI.Pathfinding /// /// TODO: These 2 methods currently don't account for a bunch of changes (e.g. airlock unpowered, wrenching, etc.) /// TODO: Could probably optimise this slightly more. - public void AddEntity(EntityUid entity, IPhysBody physicsComponent) + public void AddEntity(EntityUid entity, IPhysBody physicsComponent, IEntityManager? entMan = null) { - var entMan = IoCManager.Resolve(); + IoCManager.Resolve(ref entMan); // If we're a door if (entMan.HasComponent(entity) || entMan.HasComponent(entity)) { diff --git a/Content.Server/AI/Pathfinding/PathfindingSystem.Grid.cs b/Content.Server/AI/Pathfinding/PathfindingSystem.Grid.cs new file mode 100644 index 0000000000..4c41f4d4a4 --- /dev/null +++ b/Content.Server/AI/Pathfinding/PathfindingSystem.Grid.cs @@ -0,0 +1,273 @@ +using Content.Server.Access; +using Content.Shared.Access.Systems; +using Content.Shared.GameTicking; +using Robust.Shared.Map; +using Robust.Shared.Physics; +using Robust.Shared.Utility; + +namespace Content.Server.AI.Pathfinding; + +/// +/// Handles pathfinding while on a grid. +/// +public sealed partial class PathfindingSystem +{ + [Dependency] private readonly AccessReaderSystem _accessReader = default!; + [Dependency] private readonly IMapManager _mapManager = default!; + + // Queued pathfinding graph updates + private readonly Queue _moveUpdateQueue = new(); + private readonly Queue _accessReaderUpdateQueue = new(); + private readonly Queue _tileUpdateQueue = new(); + + public override void Initialize() + { + SubscribeLocalEvent(OnRoundRestart); + SubscribeLocalEvent(OnCollisionChange); + SubscribeLocalEvent(OnMoveEvent); + SubscribeLocalEvent(OnAccessChange); + SubscribeLocalEvent(OnGridAdd); + SubscribeLocalEvent(OnTileChange); + + // Handle all the base grid changes + // Anything that affects traversal (i.e. collision layer) is handled separately. + } + + private void OnGridAdd(GridAddEvent ev) + { + EnsureComp(ev.EntityUid); + } + + private void OnCollisionChange(ref CollisionChangeEvent collisionEvent) + { + if (collisionEvent.CanCollide) + { + OnEntityAdd(collisionEvent.Body.Owner); + } + else + { + OnEntityRemove(collisionEvent.Body.Owner); + } + } + + private void OnMoveEvent(ref MoveEvent moveEvent) + { + _moveUpdateQueue.Enqueue(moveEvent); + } + + private void OnTileChange(TileChangedEvent ev) + { + _tileUpdateQueue.Enqueue(ev.NewTile); + } + + private void OnAccessChange(AccessReaderChangeEvent message) + { + _accessReaderUpdateQueue.Enqueue(message); + } + + private PathfindingChunk GetOrCreateChunk(TileRef tile) + { + var chunkX = (int) (Math.Floor((float) tile.X / PathfindingChunk.ChunkSize) * PathfindingChunk.ChunkSize); + var chunkY = (int) (Math.Floor((float) tile.Y / PathfindingChunk.ChunkSize) * PathfindingChunk.ChunkSize); + var vector2i = new Vector2i(chunkX, chunkY); + var comp = Comp(tile.GridUid); + var chunks = comp.Graph; + + if (!chunks.TryGetValue(vector2i, out var chunk)) + { + chunk = CreateChunk(comp, vector2i); + } + + return chunk; + } + + private PathfindingChunk CreateChunk(GridPathfindingComponent comp, Vector2i indices) + { + var grid = _mapManager.GetGrid(comp.Owner); + var newChunk = new PathfindingChunk(grid.Index, indices); + comp.Graph.Add(indices, newChunk); + newChunk.Initialize(grid); + + return newChunk; + } + + /// + /// Return the corresponding PathfindingNode for this tile + /// + /// + /// + public PathfindingNode GetNode(TileRef tile) + { + var chunk = GetOrCreateChunk(tile); + var node = chunk.GetNode(tile); + + return node; + } + + private void OnTileUpdate(TileRef tile) + { + if (!_mapManager.GridExists(tile.GridIndex)) return; + + var node = GetNode(tile); + node.UpdateTile(tile); + } + + private bool IsRelevant(TransformComponent xform, PhysicsComponent physics) + { + return xform.GridID != GridId.Invalid && (TrackedCollisionLayers & physics.CollisionLayer) != 0; + } + + /// + /// Tries to add the entity to the relevant pathfinding node + /// + /// The node will filter it to the correct category (if possible) + /// + private void OnEntityAdd(EntityUid entity, TransformComponent? xform = null, PhysicsComponent? physics = null) + { + if (!Resolve(entity, ref xform, false) || + !Resolve(entity, ref physics, false)) return; + + if (!IsRelevant(xform, physics) || + !_mapManager.TryGetGrid(xform.GridID, out var grid)) + { + return; + } + + var tileRef = grid.GetTileRef(xform.Coordinates); + + var chunk = GetOrCreateChunk(tileRef); + var node = chunk.GetNode(tileRef); + node.AddEntity(entity, physics, EntityManager); + } + + private void OnEntityRemove(EntityUid entity, TransformComponent? xform = null) + { + if (!Resolve(entity, ref xform, false) || + !_mapManager.TryGetGrid(xform.GridID, out var grid)) return; + + var node = GetNode(grid.GetTileRef(xform.Coordinates)); + node.RemoveEntity(entity); + } + + /// + /// When an entity moves around we'll remove it from its old node and add it to its new node (if applicable) + /// + /// + private void OnEntityMove(MoveEvent moveEvent) + { + if (!TryComp(moveEvent.Sender, out var xform)) return; + + // If we've moved to space or the likes then remove us. + if (!TryComp(moveEvent.Sender, out var physics) || + !IsRelevant(xform, physics) || + moveEvent.NewPosition.GetGridId(EntityManager) == GridId.Invalid) + { + OnEntityRemove(moveEvent.Sender, xform); + return; + } + + var oldGridId = moveEvent.OldPosition.GetGridId(EntityManager); + var gridId = moveEvent.NewPosition.GetGridId(EntityManager); + + if (_mapManager.TryGetGrid(oldGridId, out var oldGrid)) + { + var oldNode = GetNode(oldGrid.GetTileRef(moveEvent.OldPosition)); + oldNode.RemoveEntity(moveEvent.Sender); + } + + if (_mapManager.TryGetGrid(gridId, out var grid)) + { + var newNode = GetNode(grid.GetTileRef(moveEvent.OldPosition)); + newNode.AddEntity(moveEvent.Sender, physics, EntityManager); + } + } + + // TODO: Need to rethink the pathfinder utils (traversable etc.). Maybe just chuck them all in PathfindingSystem + // Otherwise you get the steerer using this and the pathfinders using a different traversable. + // Also look at increasing tile cost the more physics entities are on it + public bool CanTraverse(EntityUid entity, EntityCoordinates coordinates) + { + var gridId = coordinates.GetGridId(EntityManager); + var tile = _mapManager.GetGrid(gridId).GetTileRef(coordinates); + var node = GetNode(tile); + return CanTraverse(entity, node); + } + + private bool CanTraverse(EntityUid entity, PathfindingNode node) + { + if (EntityManager.TryGetComponent(entity, out IPhysBody? physics) && + (physics.CollisionMask & node.BlockedCollisionMask) != 0) + { + return false; + } + + var access = _accessReader.FindAccessTags(entity); + foreach (var reader in node.AccessReaders) + { + if (!_accessReader.IsAllowed(reader, access)) + { + return false; + } + } + + return true; + } + + public void OnRoundRestart(RoundRestartCleanupEvent ev) + { + _moveUpdateQueue.Clear(); + _accessReaderUpdateQueue.Clear(); + _tileUpdateQueue.Clear(); + } + + private void ProcessGridUpdates() + { + var totalUpdates = 0; + var bodyQuery = GetEntityQuery(); + var xformQuery = GetEntityQuery(); + + foreach (var update in _accessReaderUpdateQueue) + { + if (!xformQuery.TryGetComponent(update.Sender, out var xform) || + !bodyQuery.TryGetComponent(update.Sender, out var body)) continue; + + if (update.Enabled) + { + OnEntityAdd(update.Sender, xform, body); + } + else + { + OnEntityRemove(update.Sender, xform); + } + + totalUpdates++; + } + + _accessReaderUpdateQueue.Clear(); + + foreach (var tile in _tileUpdateQueue) + { + OnTileUpdate(tile); + totalUpdates++; + } + + _tileUpdateQueue.Clear(); + var moveUpdateCount = Math.Max(50 - totalUpdates, 0); + + // Other updates are high priority so for this we'll just defer it if there's a spike (explosion, etc.) + // If the move updates grow too large then we'll just do it + if (_moveUpdateQueue.Count > 100) + { + moveUpdateCount = _moveUpdateQueue.Count - 100; + } + + moveUpdateCount = Math.Min(moveUpdateCount, _moveUpdateQueue.Count); + + for (var i = 0; i < moveUpdateCount; i++) + { + OnEntityMove(_moveUpdateQueue.Dequeue()); + } + + DebugTools.Assert(_moveUpdateQueue.Count < 1000); + } +} diff --git a/Content.Server/AI/Pathfinding/PathfindingSystem.cs b/Content.Server/AI/Pathfinding/PathfindingSystem.cs index b90c9894a1..e02066638f 100644 --- a/Content.Server/AI/Pathfinding/PathfindingSystem.cs +++ b/Content.Server/AI/Pathfinding/PathfindingSystem.cs @@ -12,35 +12,14 @@ using Robust.Shared.Utility; namespace Content.Server.AI.Pathfinding { - /* - // TODO: IMO use rectangular symmetry reduction on the nodes with collision at all. (currently planned to be implemented via AiReachableSystem and expanded later). - alternatively store all rooms and have an alternative graph for humanoid mobs (same collision mask, needs access etc). You could also just path from room to room as needed. - // TODO: Longer term -> Handle collision layer changes? - TODO: Handle container entities so they're not tracked. - */ /// /// This system handles pathfinding graph updates as well as dispatches to the pathfinder /// (90% of what it's doing is graph updates so not much point splitting the 2 roles) /// - public sealed class PathfindingSystem : EntitySystem + public sealed partial class PathfindingSystem : EntitySystem { - [Dependency] private readonly IMapManager _mapManager = default!; - [Dependency] private readonly AccessReaderSystem _accessReader = default!; - - public IReadOnlyDictionary> Graph => _graph; - private readonly Dictionary> _graph = new(); - private readonly PathfindingJobQueue _pathfindingQueue = new(); - // Queued pathfinding graph updates - private readonly Queue _collidableUpdateQueue = new(); - private readonly Queue _moveUpdateQueue = new(); - private readonly Queue _accessReaderUpdateQueue = new(); - private readonly Queue _tileUpdateQueue = new(); - - // Need to store previously known entity positions for collidables for when they move - private readonly Dictionary _lastKnownPositions = new(); - public const int TrackedCollisionLayers = (int) (CollisionGroup.Impassable | CollisionGroup.MidImpassable | @@ -66,315 +45,8 @@ namespace Content.Server.AI.Pathfinding public override void Update(float frameTime) { base.Update(frameTime); - - // Make sure graph is updated, then get pathfinders - ProcessGraphUpdates(); + ProcessGridUpdates(); _pathfindingQueue.Process(); } - - private void ProcessGraphUpdates() - { - var totalUpdates = 0; - - foreach (var update in _collidableUpdateQueue) - { - if (!EntityManager.EntityExists(update.Owner)) continue; - - if (update.CanCollide) - { - HandleEntityAdd(update.Owner); - } - else - { - HandleEntityRemove(update.Owner); - } - - totalUpdates++; - } - - _collidableUpdateQueue.Clear(); - - foreach (var update in _accessReaderUpdateQueue) - { - if (update.Enabled) - { - HandleEntityAdd(update.Sender); - } - else - { - HandleEntityRemove(update.Sender); - } - - totalUpdates++; - } - - _accessReaderUpdateQueue.Clear(); - - foreach (var tile in _tileUpdateQueue) - { - HandleTileUpdate(tile); - totalUpdates++; - } - - _tileUpdateQueue.Clear(); - var moveUpdateCount = Math.Max(50 - totalUpdates, 0); - - // Other updates are high priority so for this we'll just defer it if there's a spike (explosion, etc.) - // If the move updates grow too large then we'll just do it - if (_moveUpdateQueue.Count > 100) - { - moveUpdateCount = _moveUpdateQueue.Count - 100; - } - - moveUpdateCount = Math.Min(moveUpdateCount, _moveUpdateQueue.Count); - - for (var i = 0; i < moveUpdateCount; i++) - { - HandleEntityMove(_moveUpdateQueue.Dequeue()); - } - - DebugTools.Assert(_moveUpdateQueue.Count < 1000); - } - - public PathfindingChunk GetChunk(TileRef tile) - { - var chunkX = (int) (Math.Floor((float) tile.X / PathfindingChunk.ChunkSize) * PathfindingChunk.ChunkSize); - var chunkY = (int) (Math.Floor((float) tile.Y / PathfindingChunk.ChunkSize) * PathfindingChunk.ChunkSize); - var vector2i = new Vector2i(chunkX, chunkY); - - if (_graph.TryGetValue(tile.GridIndex, out var chunks)) - { - if (!chunks.ContainsKey(vector2i)) - { - CreateChunk(tile.GridIndex, vector2i); - } - - return chunks[vector2i]; - } - - var newChunk = CreateChunk(tile.GridIndex, vector2i); - return newChunk; - } - - private PathfindingChunk CreateChunk(GridId gridId, Vector2i indices) - { - var newChunk = new PathfindingChunk(gridId, indices); - if (!_graph.ContainsKey(gridId)) - { - _graph.Add(gridId, new Dictionary()); - } - - _graph[gridId].Add(indices, newChunk); - newChunk.Initialize(_mapManager.GetGrid(gridId)); - - return newChunk; - } - - /// - /// Get the entity's tile position, then get the corresponding node - /// - /// - /// - public PathfindingNode GetNode(EntityUid entity) - { - var tile = _mapManager.GetGrid(EntityManager.GetComponent(entity).GridID).GetTileRef(EntityManager.GetComponent(entity).Coordinates); - return GetNode(tile); - } - - /// - /// Return the corresponding PathfindingNode for this tile - /// - /// - /// - public PathfindingNode GetNode(TileRef tile) - { - var chunk = GetChunk(tile); - var node = chunk.GetNode(tile); - - return node; - } - - public override void Initialize() - { - SubscribeLocalEvent(Reset); - SubscribeLocalEvent(QueueCollisionChangeMessage); - SubscribeLocalEvent(QueueMoveEvent); - SubscribeLocalEvent(QueueAccessChangeMessage); - SubscribeLocalEvent(HandleGridRemoval); - SubscribeLocalEvent(QueueTileChange); - - // Handle all the base grid changes - // Anything that affects traversal (i.e. collision layer) is handled separately. - - } - - private void HandleTileUpdate(TileRef tile) - { - if (!_mapManager.GridExists(tile.GridIndex)) return; - - var node = GetNode(tile); - node.UpdateTile(tile); - } - - private void HandleGridRemoval(GridRemovalEvent ev) - { - if (_graph.ContainsKey(ev.GridId)) - { - _graph.Remove(ev.GridId); - } - } - - private void QueueTileChange(TileChangedEvent ev) - { - _tileUpdateQueue.Enqueue(ev.NewTile); - } - - private void QueueAccessChangeMessage(AccessReaderChangeMessage message) - { - _accessReaderUpdateQueue.Enqueue(message); - } - - /// - /// Tries to add the entity to the relevant pathfinding node - /// - /// The node will filter it to the correct category (if possible) - /// - private void HandleEntityAdd(EntityUid entity) - { - if (Deleted(entity) || - _lastKnownPositions.ContainsKey(entity) || - !EntityManager.TryGetComponent(entity, out IPhysBody? physics) || - !PathfindingNode.IsRelevant(entity, physics)) - { - return; - } - - var xform = EntityManager.GetComponent(entity); - var grid = _mapManager.GetGrid(xform.GridID); - var tileRef = grid.GetTileRef(xform.Coordinates); - - var chunk = GetChunk(tileRef); - var node = chunk.GetNode(tileRef); - node.AddEntity(entity, physics); - _lastKnownPositions.Add(entity, node); - } - - private void HandleEntityRemove(EntityUid entity) - { - if (!_lastKnownPositions.TryGetValue(entity, out var node)) - { - return; - } - - node.RemoveEntity(entity); - _lastKnownPositions.Remove(entity); - } - - private void QueueMoveEvent(ref MoveEvent moveEvent) - { - _moveUpdateQueue.Enqueue(moveEvent); - } - - /// - /// When an entity moves around we'll remove it from its old node and add it to its new node (if applicable) - /// - /// - private void HandleEntityMove(MoveEvent moveEvent) - { - // If we've moved to space or the likes then remove us. - if ((!EntityManager.EntityExists(moveEvent.Sender) ? EntityLifeStage.Deleted : EntityManager.GetComponent(moveEvent.Sender).EntityLifeStage) >= EntityLifeStage.Deleted || - !EntityManager.TryGetComponent(moveEvent.Sender, out IPhysBody? physics) || - !PathfindingNode.IsRelevant(moveEvent.Sender, physics) || - moveEvent.NewPosition.GetGridId(EntityManager) == GridId.Invalid) - { - HandleEntityRemove(moveEvent.Sender); - return; - } - - // Memory leak protection until grid parenting confirmed fix / you REALLY need the performance - var xform = EntityManager.GetComponent(moveEvent.Sender); - var gridBounds = _mapManager.GetGrid(xform.GridID).WorldBounds; - - if (!gridBounds.Contains(xform.WorldPosition)) - { - HandleEntityRemove(moveEvent.Sender); - return; - } - - // If we move from space to a grid we may need to start tracking it. - if (!_lastKnownPositions.TryGetValue(moveEvent.Sender, out var oldNode)) - { - HandleEntityAdd(moveEvent.Sender); - return; - } - - var newGridId = moveEvent.NewPosition.GetGridId(EntityManager); - if (newGridId == GridId.Invalid) - { - HandleEntityRemove(moveEvent.Sender); - return; - } - - // The pathfinding graph is tile-based so first we'll check if they're on a different tile and if we need to update. - // If you get entities bigger than 1 tile wide you'll need some other system so god help you. - var newTile = _mapManager.GetGrid(newGridId).GetTileRef(moveEvent.NewPosition); - - if (oldNode == null || oldNode.TileRef == newTile) - { - return; - } - - var newNode = GetNode(newTile); - _lastKnownPositions[moveEvent.Sender] = newNode; - - oldNode.RemoveEntity(moveEvent.Sender); - newNode.AddEntity(moveEvent.Sender, physics); - } - - private void QueueCollisionChangeMessage(CollisionChangeMessage collisionMessage) - { - _collidableUpdateQueue.Enqueue(collisionMessage); - } - - // TODO: Need to rethink the pathfinder utils (traversable etc.). Maybe just chuck them all in PathfindingSystem - // Otherwise you get the steerer using this and the pathfinders using a different traversable. - // Also look at increasing tile cost the more physics entities are on it - public bool CanTraverse(EntityUid entity, EntityCoordinates coordinates) - { - var gridId = coordinates.GetGridId(EntityManager); - var tile = _mapManager.GetGrid(gridId).GetTileRef(coordinates); - var node = GetNode(tile); - return CanTraverse(entity, node); - } - - public bool CanTraverse(EntityUid entity, PathfindingNode node) - { - if (EntityManager.TryGetComponent(entity, out IPhysBody? physics) && - (physics.CollisionMask & node.BlockedCollisionMask) != 0) - { - return false; - } - - var access = _accessReader.FindAccessTags(entity); - foreach (var reader in node.AccessReaders) - { - if (!_accessReader.IsAllowed(reader, access)) - { - return false; - } - } - - return true; - } - - public void Reset(RoundRestartCleanupEvent ev) - { - _graph.Clear(); - _collidableUpdateQueue.Clear(); - _moveUpdateQueue.Clear(); - _accessReaderUpdateQueue.Clear(); - _tileUpdateQueue.Clear(); - _lastKnownPositions.Clear(); - } } } diff --git a/Content.Server/AI/Pathfinding/ServerPathfindingDebugSystem.cs b/Content.Server/AI/Pathfinding/ServerPathfindingDebugSystem.cs index aa9bc144c0..85c24a835c 100644 --- a/Content.Server/AI/Pathfinding/ServerPathfindingDebugSystem.cs +++ b/Content.Server/AI/Pathfinding/ServerPathfindingDebugSystem.cs @@ -14,7 +14,6 @@ namespace Content.Server.AI.Pathfinding base.Initialize(); AStarPathfindingJob.DebugRoute += DispatchAStarDebug; JpsPathfindingJob.DebugRoute += DispatchJpsDebug; - SubscribeNetworkEvent(DispatchGraph); } public override void Shutdown() @@ -88,37 +87,6 @@ namespace Content.Server.AI.Pathfinding RaiseNetworkEvent(systemMessage); } - - private void DispatchGraph(SharedAiDebug.RequestPathfindingGraphMessage message) - { - var pathfindingSystem = EntityManager.EntitySysManager.GetEntitySystem(); - var mapManager = IoCManager.Resolve(); - var result = new Dictionary>(); - - var idx = 0; - - foreach (var (gridId, chunks) in pathfindingSystem.Graph) - { - var gridManager = mapManager.GetGrid(gridId); - - foreach (var chunk in chunks.Values) - { - var nodes = new List(); - foreach (var node in chunk.Nodes) - { - var worldTile = gridManager.GridTileToWorldPos(node.TileRef.GridIndices); - - nodes.Add(worldTile); - } - - result.Add(idx, nodes); - idx++; - } - } - - var systemMessage = new SharedAiDebug.PathfindingGraphMessage(result); - RaiseNetworkEvent(systemMessage); - } } #endif } diff --git a/Content.Server/AI/Steering/AiSteeringSystem.cs b/Content.Server/AI/Steering/AiSteeringSystem.cs index 5fd83d4258..0ffe5c5e83 100644 --- a/Content.Server/AI/Steering/AiSteeringSystem.cs +++ b/Content.Server/AI/Steering/AiSteeringSystem.cs @@ -387,19 +387,6 @@ namespace Content.Server.AI.Steering movementVector += CollisionAvoidance(entity, movementVector, ignoredCollision); } - // TODO: Jesus this code is shit, slork is a cute dork, but the pathfinder should annotate this. - if (_mapManager.TryGetGrid(nextGrid.Value.EntityId, out var grid)) - { - foreach (var ent in grid.GetAnchoredEntities(nextGrid.Value)) - { - if (HasComp(ent)) - { - _interactionSystem.InteractHand(entity, ent); - break; - } - } - } - // Group behaviors would also go here e.g. separation, cohesion, alignment // Move towards it diff --git a/Content.Server/Access/AccessReaderChangeMessage.cs b/Content.Server/Access/AccessReaderChangeEvent.cs similarity index 59% rename from Content.Server/Access/AccessReaderChangeMessage.cs rename to Content.Server/Access/AccessReaderChangeEvent.cs index 38c5a3e3d7..f1ffed46a0 100644 --- a/Content.Server/Access/AccessReaderChangeMessage.cs +++ b/Content.Server/Access/AccessReaderChangeEvent.cs @@ -1,12 +1,12 @@ namespace Content.Server.Access { - public sealed class AccessReaderChangeMessage : EntityEventArgs + public sealed class AccessReaderChangeEvent : EntityEventArgs { public EntityUid Sender { get; } public bool Enabled { get; } - public AccessReaderChangeMessage(EntityUid entity, bool enabled) + public AccessReaderChangeEvent(EntityUid entity, bool enabled) { Sender = entity; Enabled = enabled; diff --git a/Content.Server/Doors/Systems/DoorSystem.cs b/Content.Server/Doors/Systems/DoorSystem.cs index b1f539d594..9c47776006 100644 --- a/Content.Server/Doors/Systems/DoorSystem.cs +++ b/Content.Server/Doors/Systems/DoorSystem.cs @@ -67,7 +67,7 @@ public sealed class DoorSystem : SharedDoorSystem _airtightSystem.SetAirblocked(airtight, collidable); // Pathfinding / AI stuff. - RaiseLocalEvent(new AccessReaderChangeMessage(uid, collidable)); + RaiseLocalEvent(new AccessReaderChangeEvent(uid, collidable)); base.SetCollidable(uid, collidable, door, physics, occluder); }