Pathfinder hotfixes (#8201)

This commit is contained in:
metalgearsloth
2022-05-16 13:21:00 +10:00
committed by GitHub
parent 638ccb8500
commit 01d2d174fc
12 changed files with 301 additions and 460 deletions

View File

@@ -335,7 +335,7 @@ namespace Content.Client.AI
{ {
Regions[gridId][chunk].Add(region, nodes); Regions[gridId][chunk].Add(region, nodes);
_regionColors[gridId][chunk][region] = new Color(robustRandom.NextFloat(), robustRandom.NextFloat(), _regionColors[gridId][chunk][region] = new Color(robustRandom.NextFloat(), robustRandom.NextFloat(),
robustRandom.NextFloat(), 0.3f); robustRandom.NextFloat(), 0.5f);
} }
} }
} }

View File

@@ -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<PathfindingSystem>();
var mapMan = IoCManager.Resolve<IMapManager>();
// 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();
}
}
}

View File

@@ -0,0 +1,8 @@
namespace Content.Server.AI.Pathfinding;
[RegisterComponent]
[Friend(typeof(PathfindingSystem))]
public sealed class GridPathfindingComponent : Component, IPathfindingGraph
{
public Dictionary<Vector2i, PathfindingChunk> Graph = new();
}

View File

@@ -0,0 +1,6 @@
namespace Content.Server.AI.Pathfinding;
public interface IPathfindingGraph
{
}

View File

@@ -17,7 +17,7 @@ namespace Content.Server.AI.Pathfinding
public sealed class PathfindingChunk public sealed class PathfindingChunk
{ {
public TimeSpan LastUpdate { get; private set; } public TimeSpan LastUpdate { get; private set; }
public GridId GridId { get; } public EntityUid GridId { get; }
public Vector2i Indices => _indices; public Vector2i Indices => _indices;
private readonly Vector2i _indices; private readonly Vector2i _indices;
@@ -27,7 +27,7 @@ namespace Content.Server.AI.Pathfinding
public PathfindingNode[,] Nodes => _nodes; public PathfindingNode[,] Nodes => _nodes;
private readonly PathfindingNode[,] _nodes = new PathfindingNode[ChunkSize,ChunkSize]; private readonly PathfindingNode[,] _nodes = new PathfindingNode[ChunkSize,ChunkSize];
public PathfindingChunk(GridId gridId, Vector2i indices) public PathfindingChunk(EntityUid gridId, Vector2i indices)
{ {
GridId = gridId; GridId = gridId;
_indices = indices; _indices = indices;
@@ -57,10 +57,10 @@ namespace Content.Server.AI.Pathfinding
.RaiseEvent(EventSource.Local, new PathfindingChunkUpdateMessage(this)); .RaiseEvent(EventSource.Local, new PathfindingChunkUpdateMessage(this));
} }
public IEnumerable<PathfindingChunk> GetNeighbors() public IEnumerable<PathfindingChunk> GetNeighbors(IEntityManager? entManager = null)
{ {
var pathfindingSystem = EntitySystem.Get<PathfindingSystem>(); IoCManager.Resolve(ref entManager);
var chunkGrid = pathfindingSystem.Graph[GridId]; var chunkGrid = entManager.GetComponent<GridPathfindingComponent>(GridId).Graph;
for (var x = -1; x <= 1; x++) for (var x = -1; x <= 1; x++)
{ {

View File

@@ -38,17 +38,6 @@ namespace Content.Server.AI.Pathfinding
GenerateMask(); GenerateMask();
} }
public static bool IsRelevant(EntityUid entity, IPhysBody physicsComponent)
{
if (IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(entity).GridID == GridId.Invalid ||
(PathfindingSystem.TrackedCollisionLayers & physicsComponent.CollisionLayer) == 0)
{
return false;
}
return true;
}
/// <summary> /// <summary>
/// Return our neighboring nodes (even across chunks) /// Return our neighboring nodes (even across chunks)
/// </summary> /// </summary>
@@ -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 chunkXOffset = TileRef.X - ParentChunk.Indices.X;
var chunkYOffset = TileRef.Y - ParentChunk.Indices.Y; var chunkYOffset = TileRef.Y - ParentChunk.Indices.Y;
@@ -254,9 +243,9 @@ namespace Content.Server.AI.Pathfinding
/// <param name="entity"></param> /// <param name="entity"></param>
/// TODO: These 2 methods currently don't account for a bunch of changes (e.g. airlock unpowered, wrenching, etc.) /// 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. /// 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<IEntityManager>(); IoCManager.Resolve(ref entMan);
// If we're a door // If we're a door
if (entMan.HasComponent<AirlockComponent>(entity) || entMan.HasComponent<DoorComponent>(entity)) if (entMan.HasComponent<AirlockComponent>(entity) || entMan.HasComponent<DoorComponent>(entity))
{ {

View File

@@ -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;
/// <summary>
/// Handles pathfinding while on a grid.
/// </summary>
public sealed partial class PathfindingSystem
{
[Dependency] private readonly AccessReaderSystem _accessReader = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
// Queued pathfinding graph updates
private readonly Queue<MoveEvent> _moveUpdateQueue = new();
private readonly Queue<AccessReaderChangeEvent> _accessReaderUpdateQueue = new();
private readonly Queue<TileRef> _tileUpdateQueue = new();
public override void Initialize()
{
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundRestart);
SubscribeLocalEvent<CollisionChangeEvent>(OnCollisionChange);
SubscribeLocalEvent<MoveEvent>(OnMoveEvent);
SubscribeLocalEvent<AccessReaderChangeEvent>(OnAccessChange);
SubscribeLocalEvent<GridAddEvent>(OnGridAdd);
SubscribeLocalEvent<TileChangedEvent>(OnTileChange);
// Handle all the base grid changes
// Anything that affects traversal (i.e. collision layer) is handled separately.
}
private void OnGridAdd(GridAddEvent ev)
{
EnsureComp<GridPathfindingComponent>(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<GridPathfindingComponent>(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;
}
/// <summary>
/// Return the corresponding PathfindingNode for this tile
/// </summary>
/// <param name="tile"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Tries to add the entity to the relevant pathfinding node
/// </summary>
/// The node will filter it to the correct category (if possible)
/// <param name="entity"></param>
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);
}
/// <summary>
/// When an entity moves around we'll remove it from its old node and add it to its new node (if applicable)
/// </summary>
/// <param name="moveEvent"></param>
private void OnEntityMove(MoveEvent moveEvent)
{
if (!TryComp<TransformComponent>(moveEvent.Sender, out var xform)) return;
// If we've moved to space or the likes then remove us.
if (!TryComp<PhysicsComponent>(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<PhysicsComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
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);
}
}

View File

@@ -12,35 +12,14 @@ using Robust.Shared.Utility;
namespace Content.Server.AI.Pathfinding 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.
*/
/// <summary> /// <summary>
/// This system handles pathfinding graph updates as well as dispatches to the pathfinder /// 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) /// (90% of what it's doing is graph updates so not much point splitting the 2 roles)
/// </summary> /// </summary>
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<GridId, Dictionary<Vector2i, PathfindingChunk>> Graph => _graph;
private readonly Dictionary<GridId, Dictionary<Vector2i, PathfindingChunk>> _graph = new();
private readonly PathfindingJobQueue _pathfindingQueue = new(); private readonly PathfindingJobQueue _pathfindingQueue = new();
// Queued pathfinding graph updates
private readonly Queue<CollisionChangeMessage> _collidableUpdateQueue = new();
private readonly Queue<MoveEvent> _moveUpdateQueue = new();
private readonly Queue<AccessReaderChangeMessage> _accessReaderUpdateQueue = new();
private readonly Queue<TileRef> _tileUpdateQueue = new();
// Need to store previously known entity positions for collidables for when they move
private readonly Dictionary<EntityUid, PathfindingNode> _lastKnownPositions = new();
public const int TrackedCollisionLayers = (int) public const int TrackedCollisionLayers = (int)
(CollisionGroup.Impassable | (CollisionGroup.Impassable |
CollisionGroup.MidImpassable | CollisionGroup.MidImpassable |
@@ -66,315 +45,8 @@ namespace Content.Server.AI.Pathfinding
public override void Update(float frameTime) public override void Update(float frameTime)
{ {
base.Update(frameTime); base.Update(frameTime);
ProcessGridUpdates();
// Make sure graph is updated, then get pathfinders
ProcessGraphUpdates();
_pathfindingQueue.Process(); _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<Vector2i, PathfindingChunk>());
}
_graph[gridId].Add(indices, newChunk);
newChunk.Initialize(_mapManager.GetGrid(gridId));
return newChunk;
}
/// <summary>
/// Get the entity's tile position, then get the corresponding node
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
public PathfindingNode GetNode(EntityUid entity)
{
var tile = _mapManager.GetGrid(EntityManager.GetComponent<TransformComponent>(entity).GridID).GetTileRef(EntityManager.GetComponent<TransformComponent>(entity).Coordinates);
return GetNode(tile);
}
/// <summary>
/// Return the corresponding PathfindingNode for this tile
/// </summary>
/// <param name="tile"></param>
/// <returns></returns>
public PathfindingNode GetNode(TileRef tile)
{
var chunk = GetChunk(tile);
var node = chunk.GetNode(tile);
return node;
}
public override void Initialize()
{
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
SubscribeLocalEvent<CollisionChangeMessage>(QueueCollisionChangeMessage);
SubscribeLocalEvent<MoveEvent>(QueueMoveEvent);
SubscribeLocalEvent<AccessReaderChangeMessage>(QueueAccessChangeMessage);
SubscribeLocalEvent<GridRemovalEvent>(HandleGridRemoval);
SubscribeLocalEvent<TileChangedEvent>(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);
}
/// <summary>
/// Tries to add the entity to the relevant pathfinding node
/// </summary>
/// The node will filter it to the correct category (if possible)
/// <param name="entity"></param>
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<TransformComponent>(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);
}
/// <summary>
/// When an entity moves around we'll remove it from its old node and add it to its new node (if applicable)
/// </summary>
/// <param name="moveEvent"></param>
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<MetaDataComponent>(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<TransformComponent>(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();
}
} }
} }

View File

@@ -14,7 +14,6 @@ namespace Content.Server.AI.Pathfinding
base.Initialize(); base.Initialize();
AStarPathfindingJob.DebugRoute += DispatchAStarDebug; AStarPathfindingJob.DebugRoute += DispatchAStarDebug;
JpsPathfindingJob.DebugRoute += DispatchJpsDebug; JpsPathfindingJob.DebugRoute += DispatchJpsDebug;
SubscribeNetworkEvent<SharedAiDebug.RequestPathfindingGraphMessage>(DispatchGraph);
} }
public override void Shutdown() public override void Shutdown()
@@ -88,37 +87,6 @@ namespace Content.Server.AI.Pathfinding
RaiseNetworkEvent(systemMessage); RaiseNetworkEvent(systemMessage);
} }
private void DispatchGraph(SharedAiDebug.RequestPathfindingGraphMessage message)
{
var pathfindingSystem = EntityManager.EntitySysManager.GetEntitySystem<PathfindingSystem>();
var mapManager = IoCManager.Resolve<IMapManager>();
var result = new Dictionary<int, List<Vector2>>();
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<Vector2>();
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 #endif
} }

View File

@@ -387,19 +387,6 @@ namespace Content.Server.AI.Steering
movementVector += CollisionAvoidance(entity, movementVector, ignoredCollision); 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<DoorComponent>(ent))
{
_interactionSystem.InteractHand(entity, ent);
break;
}
}
}
// Group behaviors would also go here e.g. separation, cohesion, alignment // Group behaviors would also go here e.g. separation, cohesion, alignment
// Move towards it // Move towards it

View File

@@ -1,12 +1,12 @@
namespace Content.Server.Access namespace Content.Server.Access
{ {
public sealed class AccessReaderChangeMessage : EntityEventArgs public sealed class AccessReaderChangeEvent : EntityEventArgs
{ {
public EntityUid Sender { get; } public EntityUid Sender { get; }
public bool Enabled { get; } public bool Enabled { get; }
public AccessReaderChangeMessage(EntityUid entity, bool enabled) public AccessReaderChangeEvent(EntityUid entity, bool enabled)
{ {
Sender = entity; Sender = entity;
Enabled = enabled; Enabled = enabled;

View File

@@ -67,7 +67,7 @@ public sealed class DoorSystem : SharedDoorSystem
_airtightSystem.SetAirblocked(airtight, collidable); _airtightSystem.SetAirblocked(airtight, collidable);
// Pathfinding / AI stuff. // Pathfinding / AI stuff.
RaiseLocalEvent(new AccessReaderChangeMessage(uid, collidable)); RaiseLocalEvent(new AccessReaderChangeEvent(uid, collidable));
base.SetCollidable(uid, collidable, door, physics, occluder); base.SetCollidable(uid, collidable, door, physics, occluder);
} }