Remove pathfinding graph node directions (#1223)

Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
This commit is contained in:
metalgearsloth
2020-06-29 01:43:06 +10:00
committed by GitHub
parent 24831bf8a0
commit 29f1730d71
7 changed files with 462 additions and 372 deletions

View File

@@ -0,0 +1,65 @@
using System.Linq;
using System.Threading.Tasks;
using Content.Server.GameObjects.EntitySystems.AI.Pathfinding;
using NUnit.Framework;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
namespace Content.IntegrationTests.Tests.Pathfinding
{
[TestFixture]
[TestOf(typeof(PathfindingChunk))]
public class PathfindingChunkTest : ContentIntegrationTest
{
[Test]
public async Task Test()
{
var server = StartServerDummyTicker();
server.Assert(() =>
{
var pathfindingSystem = EntitySystem.Get<PathfindingSystem>();
var mapMan = IoCManager.Resolve<IMapManager>();
// Setup
var mapId = mapMan.CreateMap(new MapId(1));
var gridId = new GridId(2);
mapMan.CreateGrid(mapId, gridId);
var chunkTile = mapMan.GetGrid(gridId).GetTileRef(new MapIndices(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 = mapMan.GetGrid(gridId).GetTileRef(new MapIndices(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

@@ -80,7 +80,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders
closedTiles.Add(currentNode); closedTiles.Add(currentNode);
foreach (var (direction, nextNode) in currentNode.Neighbors) foreach (var nextNode in currentNode.GetNeighbors())
{ {
if (closedTiles.Contains(nextNode)) if (closedTiles.Contains(nextNode))
{ {
@@ -89,6 +89,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders
// If tile is untraversable it'll be null // If tile is untraversable it'll be null
var tileCost = PathfindingHelpers.GetTileCost(_pathfindingArgs, currentNode, nextNode); var tileCost = PathfindingHelpers.GetTileCost(_pathfindingArgs, currentNode, nextNode);
var direction = PathfindingHelpers.RelativeDirection(nextNode, currentNode);
if (tileCost == null || !PathfindingHelpers.DirectionTraversable(_pathfindingArgs.CollisionMask, _pathfindingArgs.Access, currentNode, direction)) if (tileCost == null || !PathfindingHelpers.DirectionTraversable(_pathfindingArgs.CollisionMask, _pathfindingArgs.Access, currentNode, direction))
{ {

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Server.GameObjects.EntitySystems.JobQueues; using Content.Server.GameObjects.EntitySystems.JobQueues;
@@ -14,6 +15,8 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders
{ {
public class JpsPathfindingJob : Job<Queue<TileRef>> public class JpsPathfindingJob : Job<Queue<TileRef>>
{ {
// Some of this is probably fugly due to other structural changes in pathfinding so it could do with optimisation
// Realistically it's probably not getting used given it doesn't support tile costs which can be very useful
public static event Action<SharedAiDebug.JpsRouteDebug> DebugRoute; public static event Action<SharedAiDebug.JpsRouteDebug> DebugRoute;
private PathfindingNode _startNode; private PathfindingNode _startNode;
@@ -78,8 +81,9 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders
break; break;
} }
foreach (var (direction, _) in currentNode.Neighbors) foreach (var node in currentNode.GetNeighbors())
{ {
var direction = PathfindingHelpers.RelativeDirection(node, currentNode);
var jumpNode = GetJumpPoint(currentNode, direction, _endNode); var jumpNode = GetJumpPoint(currentNode, direction, _endNode);
if (jumpNode != null && !closedTiles.Contains(jumpNode)) if (jumpNode != null && !closedTiles.Contains(jumpNode))
@@ -156,7 +160,15 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders
while (count < 1000) while (count < 1000)
{ {
count++; count++;
var nextNode = currentNode.GetNeighbor(direction); PathfindingNode nextNode = null;
foreach (var node in currentNode.GetNeighbors())
{
if (PathfindingHelpers.RelativeDirection(node, currentNode) == direction)
{
nextNode = node;
break;
}
}
// We'll do opposite DirectionTraversable just because of how the method's setup // We'll do opposite DirectionTraversable just because of how the method's setup
// Nodes should be 2-way anyway. // Nodes should be 2-way anyway.
@@ -270,43 +282,99 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders
private bool IsDiagonalJumpPoint(Direction direction, PathfindingNode currentNode) private bool IsDiagonalJumpPoint(Direction direction, PathfindingNode currentNode)
{ {
// If we're going diagonally need to check all cardinals. // If we're going diagonally need to check all cardinals.
// I just just using casts int casts and offset to make it smaller but brain no workyand it wasn't working. // I tried just casting direction ints and offsets to make it smaller but brain no worky.
// From NorthEast we check (Closed / Open) S - SE, W - NW // From NorthEast we check (Closed / Open) S - SE, W - NW
PathfindingNode openNeighborOne; PathfindingNode openNeighborOne = null;
PathfindingNode closedNeighborOne; PathfindingNode closedNeighborOne = null;
PathfindingNode openNeighborTwo; PathfindingNode openNeighborTwo = null;
PathfindingNode closedNeighborTwo; PathfindingNode closedNeighborTwo = null;
switch (direction) switch (direction)
{ {
case Direction.NorthEast: case Direction.NorthEast:
openNeighborOne = currentNode.GetNeighbor(Direction.SouthEast); foreach (var neighbor in currentNode.GetNeighbors())
closedNeighborOne = currentNode.GetNeighbor(Direction.South); {
var neighborDirection = PathfindingHelpers.RelativeDirection(neighbor, currentNode);
openNeighborTwo = currentNode.GetNeighbor(Direction.NorthWest); switch (neighborDirection)
closedNeighborTwo = currentNode.GetNeighbor(Direction.West); {
case Direction.SouthEast:
openNeighborOne = neighbor;
break;
case Direction.South:
closedNeighborOne = neighbor;
break;
case Direction.NorthWest:
openNeighborTwo = neighbor;
break;
case Direction.West:
closedNeighborTwo = neighbor;
break;
}
}
break; break;
case Direction.SouthEast: case Direction.SouthEast:
openNeighborOne = currentNode.GetNeighbor(Direction.NorthEast); foreach (var neighbor in currentNode.GetNeighbors())
closedNeighborOne = currentNode.GetNeighbor(Direction.North); {
var neighborDirection = PathfindingHelpers.RelativeDirection(neighbor, currentNode);
openNeighborTwo = currentNode.GetNeighbor(Direction.SouthWest); switch (neighborDirection)
closedNeighborTwo = currentNode.GetNeighbor(Direction.West); {
case Direction.NorthEast:
openNeighborOne = neighbor;
break;
case Direction.North:
closedNeighborOne = neighbor;
break;
case Direction.SouthWest:
openNeighborTwo = neighbor;
break;
case Direction.West:
closedNeighborTwo = neighbor;
break;
}
}
break; break;
case Direction.SouthWest: case Direction.SouthWest:
openNeighborOne = currentNode.GetNeighbor(Direction.NorthWest); foreach (var neighbor in currentNode.GetNeighbors())
closedNeighborOne = currentNode.GetNeighbor(Direction.North); {
var neighborDirection = PathfindingHelpers.RelativeDirection(neighbor, currentNode);
openNeighborTwo = currentNode.GetNeighbor(Direction.SouthEast); switch (neighborDirection)
closedNeighborTwo = currentNode.GetNeighbor(Direction.East); {
case Direction.NorthWest:
openNeighborOne = neighbor;
break;
case Direction.North:
closedNeighborOne = neighbor;
break;
case Direction.SouthEast:
openNeighborTwo = neighbor;
break;
case Direction.East:
closedNeighborTwo = neighbor;
break;
}
}
break; break;
case Direction.NorthWest: case Direction.NorthWest:
openNeighborOne = currentNode.GetNeighbor(Direction.SouthWest); foreach (var neighbor in currentNode.GetNeighbors())
closedNeighborOne = currentNode.GetNeighbor(Direction.South); {
var neighborDirection = PathfindingHelpers.RelativeDirection(neighbor, currentNode);
openNeighborTwo = currentNode.GetNeighbor(Direction.NorthEast); switch (neighborDirection)
closedNeighborTwo = currentNode.GetNeighbor(Direction.East); {
case Direction.SouthWest:
openNeighborOne = neighbor;
break;
case Direction.South:
closedNeighborOne = neighbor;
break;
case Direction.NorthEast:
openNeighborTwo = neighbor;
break;
case Direction.East:
closedNeighborTwo = neighbor;
break;
}
}
break; break;
default: default:
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException();
@@ -332,40 +400,96 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders
/// </summary> /// </summary>
private bool IsCardinalJumpPoint(Direction direction, PathfindingNode currentNode) private bool IsCardinalJumpPoint(Direction direction, PathfindingNode currentNode)
{ {
PathfindingNode openNeighborOne; PathfindingNode openNeighborOne = null;
PathfindingNode closedNeighborOne; PathfindingNode closedNeighborOne = null;
PathfindingNode openNeighborTwo; PathfindingNode openNeighborTwo = null;
PathfindingNode closedNeighborTwo; PathfindingNode closedNeighborTwo = null;
switch (direction) switch (direction)
{ {
case Direction.North: case Direction.North:
openNeighborOne = currentNode.GetNeighbor(Direction.NorthEast); foreach (var neighbor in currentNode.GetNeighbors())
closedNeighborOne = currentNode.GetNeighbor(Direction.East); {
var neighborDirection = PathfindingHelpers.RelativeDirection(neighbor, currentNode);
openNeighborTwo = currentNode.GetNeighbor(Direction.NorthWest); switch (neighborDirection)
closedNeighborTwo = currentNode.GetNeighbor(Direction.West); {
case Direction.NorthEast:
openNeighborOne = neighbor;
break;
case Direction.East:
closedNeighborOne = neighbor;
break;
case Direction.NorthWest:
openNeighborTwo = neighbor;
break;
case Direction.West:
closedNeighborTwo = neighbor;
break;
}
}
break; break;
case Direction.East: case Direction.East:
openNeighborOne = currentNode.GetNeighbor(Direction.NorthEast); foreach (var neighbor in currentNode.GetNeighbors())
closedNeighborOne = currentNode.GetNeighbor(Direction.North); {
var neighborDirection = PathfindingHelpers.RelativeDirection(neighbor, currentNode);
openNeighborTwo = currentNode.GetNeighbor(Direction.SouthEast); switch (neighborDirection)
closedNeighborTwo = currentNode.GetNeighbor(Direction.South); {
case Direction.NorthEast:
openNeighborOne = neighbor;
break;
case Direction.North:
closedNeighborOne = neighbor;
break;
case Direction.SouthEast:
openNeighborTwo = neighbor;
break;
case Direction.South:
closedNeighborTwo = neighbor;
break;
}
}
break; break;
case Direction.South: case Direction.South:
openNeighborOne = currentNode.GetNeighbor(Direction.SouthEast); foreach (var neighbor in currentNode.GetNeighbors())
closedNeighborOne = currentNode.GetNeighbor(Direction.East); {
var neighborDirection = PathfindingHelpers.RelativeDirection(neighbor, currentNode);
openNeighborTwo = currentNode.GetNeighbor(Direction.SouthWest); switch (neighborDirection)
closedNeighborTwo = currentNode.GetNeighbor(Direction.West); {
case Direction.SouthEast:
openNeighborOne = neighbor;
break;
case Direction.East:
closedNeighborOne = neighbor;
break;
case Direction.SouthWest:
openNeighborTwo = neighbor;
break;
case Direction.West:
closedNeighborTwo = neighbor;
break;
}
}
break; break;
case Direction.West: case Direction.West:
openNeighborOne = currentNode.GetNeighbor(Direction.NorthWest); foreach (var neighbor in currentNode.GetNeighbors())
closedNeighborOne = currentNode.GetNeighbor(Direction.North); {
var neighborDirection = PathfindingHelpers.RelativeDirection(neighbor, currentNode);
openNeighborTwo = currentNode.GetNeighbor(Direction.SouthWest); switch (neighborDirection)
closedNeighborTwo = currentNode.GetNeighbor(Direction.South); {
case Direction.NorthWest:
openNeighborOne = neighbor;
break;
case Direction.North:
closedNeighborOne = neighbor;
break;
case Direction.SouthWest:
openNeighborTwo = neighbor;
break;
case Direction.South:
closedNeighborTwo = neighbor;
break;
}
}
break; break;
default: default:
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException();

View File

@@ -1,6 +1,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq;
using Content.Server.GameObjects.EntitySystems.Pathfinding; using Content.Server.GameObjects.EntitySystems.Pathfinding;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.Map; using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Map; using Robust.Shared.Map;
@@ -19,7 +22,6 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
public static int ChunkSize => 16; public static int ChunkSize => 16;
public PathfindingNode[,] Nodes => _nodes; public PathfindingNode[,] Nodes => _nodes;
private PathfindingNode[,] _nodes = new PathfindingNode[ChunkSize,ChunkSize]; private PathfindingNode[,] _nodes = new PathfindingNode[ChunkSize,ChunkSize];
public Dictionary<Direction, PathfindingChunk> Neighbors { get; } = new Dictionary<Direction, PathfindingChunk>(8);
public PathfindingChunk(GridId gridId, MapIndices indices) public PathfindingChunk(GridId gridId, MapIndices indices)
{ {
@@ -38,244 +40,31 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
CreateNode(tileRef); CreateNode(tileRef);
} }
} }
RefreshNodeNeighbors();
} }
/// <summary> public IEnumerable<PathfindingChunk> GetNeighbors()
/// Updates all internal nodes with references to every other internal node
/// </summary>
private void RefreshNodeNeighbors()
{ {
for (var x = 0; x < ChunkSize; x++) var pathfindingSystem = EntitySystem.Get<PathfindingSystem>();
var chunkGrid = pathfindingSystem.Graph[GridId];
for (var x = -1; x <= 1; x++)
{ {
for (var y = 0; y < ChunkSize; y++) for (var y = -1; y <= 1; y++)
{ {
var node = _nodes[x, y]; if (x == 0 && y == 0) continue;
// West var (neighborX, neighborY) = (_indices.X + ChunkSize * x, _indices.Y + ChunkSize * y);
if (x != 0) if (chunkGrid.TryGetValue(new MapIndices(neighborX, neighborY), out var neighbor))
{ {
if (y != ChunkSize - 1) yield return neighbor;
{
node.AddNeighbor(Direction.NorthWest, _nodes[x - 1, y + 1]);
}
node.AddNeighbor(Direction.West, _nodes[x - 1, y]);
if (y != 0)
{
node.AddNeighbor(Direction.SouthWest, _nodes[x - 1, y - 1]);
}
}
// Same column
if (y != ChunkSize - 1)
{
node.AddNeighbor(Direction.North, _nodes[x, y + 1]);
}
if (y != 0)
{
node.AddNeighbor(Direction.South, _nodes[x, y - 1]);
}
// East
if (x != ChunkSize - 1)
{
if (y != ChunkSize - 1)
{
node.AddNeighbor(Direction.NorthEast, _nodes[x + 1, y + 1]);
}
node.AddNeighbor(Direction.East, _nodes[x + 1, y]);
if (y != 0)
{
node.AddNeighbor(Direction.SouthEast, _nodes[x + 1, y - 1]);
}
} }
} }
} }
} }
/// <summary> public bool InBounds(MapIndices mapIndices)
/// This will work both ways
/// </summary>
/// <param name="chunk"></param>
/// <exception cref="InvalidOperationException"></exception>
public void AddNeighbor(PathfindingChunk chunk)
{ {
if (chunk == this) return; if (mapIndices.X < _indices.X || mapIndices.Y < _indices.Y) return false;
if (Neighbors.ContainsValue(chunk)) if (mapIndices.X >= _indices.X + ChunkSize || mapIndices.Y >= _indices.Y + ChunkSize) return false;
{
return;
}
Direction direction;
if (chunk.Indices.X < _indices.X)
{
if (chunk.Indices.Y > _indices.Y)
{
direction = Direction.NorthWest;
} else if (chunk.Indices.Y < _indices.Y)
{
direction = Direction.SouthWest;
}
else
{
direction = Direction.West;
}
}
else if (chunk.Indices.X > _indices.X)
{
if (chunk.Indices.Y > _indices.Y)
{
direction = Direction.NorthEast;
} else if (chunk.Indices.Y < _indices.Y)
{
direction = Direction.SouthEast;
}
else
{
direction = Direction.East;
}
}
else
{
if (chunk.Indices.Y > _indices.Y)
{
direction = Direction.North;
} else if (chunk.Indices.Y < _indices.Y)
{
direction = Direction.South;
}
else
{
throw new InvalidOperationException();
}
}
Neighbors.TryAdd(direction, chunk);
foreach (var node in GetBorderNodes(direction))
{
foreach (var counter in chunk.GetCounterpartNodes(direction))
{
var xDiff = node.TileRef.X - counter.TileRef.X;
var yDiff = node.TileRef.Y - counter.TileRef.Y;
if (Math.Abs(xDiff) <= 1 && Math.Abs(yDiff) <= 1)
{
node.AddNeighbor(counter);
counter.AddNeighbor(node);
}
}
}
chunk.Neighbors.TryAdd(OppositeDirection(direction), this);
if (Neighbors.Count > 8)
{
throw new InvalidOperationException();
}
}
private Direction OppositeDirection(Direction direction)
{
return (Direction) (((int) direction + 4) % 8);
}
// TODO I was too tired to think of an easier system. Could probably just google an array wraparound
private IEnumerable<PathfindingNode> GetCounterpartNodes(Direction direction)
{
switch (direction)
{
case Direction.West:
for (var i = 0; i < ChunkSize; i++)
{
yield return _nodes[ChunkSize - 1, i];
}
break;
case Direction.SouthWest:
yield return _nodes[ChunkSize - 1, ChunkSize - 1];
break;
case Direction.South:
for (var i = 0; i < ChunkSize; i++)
{
yield return _nodes[i, ChunkSize - 1];
}
break;
case Direction.SouthEast:
yield return _nodes[0, ChunkSize - 1];
break;
case Direction.East:
for (var i = 0; i < ChunkSize; i++)
{
yield return _nodes[0, i];
}
break;
case Direction.NorthEast:
yield return _nodes[0, 0];
break;
case Direction.North:
for (var i = 0; i < ChunkSize; i++)
{
yield return _nodes[i, 0];
}
break;
case Direction.NorthWest:
yield return _nodes[ChunkSize - 1, 0];
break;
default:
throw new ArgumentOutOfRangeException(nameof(direction), direction, null);
}
}
public IEnumerable<PathfindingNode> GetBorderNodes(Direction direction)
{
switch (direction)
{
case Direction.East:
for (var i = 0; i < ChunkSize; i++)
{
yield return _nodes[ChunkSize - 1, i];
}
break;
case Direction.NorthEast:
yield return _nodes[ChunkSize - 1, ChunkSize - 1];
break;
case Direction.North:
for (var i = 0; i < ChunkSize; i++)
{
yield return _nodes[i, ChunkSize - 1];
}
break;
case Direction.NorthWest:
yield return _nodes[0, ChunkSize - 1];
break;
case Direction.West:
for (var i = 0; i < ChunkSize; i++)
{
yield return _nodes[0, i];
}
break;
case Direction.SouthWest:
yield return _nodes[0, 0];
break;
case Direction.South:
for (var i = 0; i < ChunkSize; i++)
{
yield return _nodes[i, 0];
}
break;
case Direction.SouthEast:
yield return _nodes[ChunkSize - 1, 0];
break;
default:
throw new ArgumentOutOfRangeException(nameof(direction), direction, null);
}
}
public bool InBounds(TileRef tile)
{
if (tile.X < _indices.X || tile.Y < _indices.Y) return false;
if (tile.X >= _indices.X + ChunkSize || tile.Y >= _indices.Y + ChunkSize) return false;
return true; return true;
} }
@@ -293,6 +82,73 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
return false; return false;
} }
/// <summary>
/// Gets our neighbors that are relevant for the node to retrieve its own neighbors
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
public IEnumerable<PathfindingChunk> RelevantChunks(PathfindingNode node)
{
var relevantDirections = GetEdges(node).ToList();
foreach (var chunk in GetNeighbors())
{
var chunkDirection = PathfindingHelpers.RelativeDirection(chunk, this);
if (relevantDirections.Contains(chunkDirection))
{
yield return chunk;
}
}
}
private IEnumerable<Direction> GetEdges(PathfindingNode node)
{
// West Edge
if (node.TileRef.X == _indices.X)
{
yield return Direction.West;
if (node.TileRef.Y == _indices.Y)
{
yield return Direction.SouthWest;
yield return Direction.South;
} else if (node.TileRef.Y == _indices.Y + ChunkSize - 1)
{
yield return Direction.NorthWest;
yield return Direction.North;
}
yield break;
}
// East edge
if (node.TileRef.X == _indices.X + ChunkSize - 1)
{
yield return Direction.East;
if (node.TileRef.Y == _indices.Y)
{
yield return Direction.SouthEast;
yield return Direction.South;
} else if (node.TileRef.Y == _indices.Y + ChunkSize - 1)
{
yield return Direction.NorthEast;
yield return Direction.North;
}
yield break;
}
// South edge
if (node.TileRef.Y == _indices.Y)
{
yield return Direction.South;
// Given we already checked south-west and south-east above shouldn't need any more
}
// North edge
if (node.TileRef.Y == _indices.Y + ChunkSize - 1)
{
yield return Direction.North;
}
}
public PathfindingNode GetNode(TileRef tile) public PathfindingNode GetNode(TileRef tile)
{ {
var chunkX = tile.X - _indices.X; var chunkX = tile.X - _indices.X;

View File

@@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Content.Server.GameObjects.Components.Access;
using Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders; using Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders;
using Content.Server.GameObjects.EntitySystems.Pathfinding; using Content.Server.GameObjects.EntitySystems.Pathfinding;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
@@ -21,7 +20,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
{ {
// TODO: Should make this account for proximities, // TODO: Should make this account for proximities,
// probably some kind of breadth-first search to find a valid one // probably some kind of breadth-first search to find a valid one
foreach (var (_, node) in endNode.Neighbors) foreach (var node in endNode.GetNeighbors())
{ {
if (Traversable(pathfindingArgs.CollisionMask, pathfindingArgs.Access, node)) if (Traversable(pathfindingArgs.CollisionMask, pathfindingArgs.Access, node))
{ {
@@ -43,10 +42,40 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
// Given there's different collision layers stored for each node in the graph it's probably not worth it to cache this // Given there's different collision layers stored for each node in the graph it's probably not worth it to cache this
// Also this will help with corner-cutting // Also this will help with corner-cutting
currentNode.Neighbors.TryGetValue(Direction.North, out var northNeighbor); PathfindingNode northNeighbor = null;
currentNode.Neighbors.TryGetValue(Direction.South, out var southNeighbor); PathfindingNode southNeighbor = null;
currentNode.Neighbors.TryGetValue(Direction.East, out var eastNeighbor); PathfindingNode eastNeighbor = null;
currentNode.Neighbors.TryGetValue(Direction.West, out var westNeighbor); PathfindingNode westNeighbor = null;
foreach (var neighbor in currentNode.GetNeighbors())
{
if (neighbor.TileRef.X == currentNode.TileRef.X &&
neighbor.TileRef.Y == currentNode.TileRef.Y + 1)
{
northNeighbor = neighbor;
continue;
}
if (neighbor.TileRef.X == currentNode.TileRef.X + 1 &&
neighbor.TileRef.Y == currentNode.TileRef.Y)
{
eastNeighbor = neighbor;
continue;
}
if (neighbor.TileRef.X == currentNode.TileRef.X &&
neighbor.TileRef.Y == currentNode.TileRef.Y - 1)
{
southNeighbor = neighbor;
continue;
}
if (neighbor.TileRef.X == currentNode.TileRef.X - 1 &&
neighbor.TileRef.Y == currentNode.TileRef.Y)
{
westNeighbor = neighbor;
continue;
}
}
switch (direction) switch (direction)
{ {
@@ -255,5 +284,66 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
return cost; return cost;
} }
public static Direction RelativeDirection(PathfindingChunk endChunk, PathfindingChunk startChunk)
{
var xDiff = (endChunk.Indices.X - startChunk.Indices.X) / PathfindingChunk.ChunkSize;
var yDiff = (endChunk.Indices.Y - startChunk.Indices.Y) / PathfindingChunk.ChunkSize;
return RelativeDirection(xDiff, yDiff);
}
public static Direction RelativeDirection(PathfindingNode endNode, PathfindingNode startNode)
{
var xDiff = endNode.TileRef.X - startNode.TileRef.X;
var yDiff = endNode.TileRef.Y - startNode.TileRef.Y;
return RelativeDirection(xDiff, yDiff);
}
public static Direction RelativeDirection(int x, int y)
{
switch (x)
{
case -1:
switch (y)
{
case -1:
return Direction.SouthWest;
case 0:
return Direction.West;
case 1:
return Direction.NorthWest;
default:
throw new InvalidOperationException();
}
case 0:
switch (y)
{
case -1:
return Direction.South;
case 0:
throw new InvalidOperationException();
case 1:
return Direction.North;
default:
throw new InvalidOperationException();
}
case 1:
switch (y)
{
case -1:
return Direction.SouthEast;
case 0:
return Direction.East;
case 1:
return Direction.NorthEast;
default:
throw new InvalidOperationException();
}
default:
throw new InvalidOperationException();
}
}
} }
} }

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Content.Server.GameObjects.Components.Access; using Content.Server.GameObjects.Components.Access;
using Content.Server.GameObjects.Components.Doors; using Content.Server.GameObjects.Components.Doors;
using Content.Server.GameObjects.EntitySystems.AI.Pathfinding; using Content.Server.GameObjects.EntitySystems.AI.Pathfinding;
@@ -9,6 +10,7 @@ using Robust.Shared.GameObjects.Components;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Maths; using Robust.Shared.Maths;
using Robust.Shared.Utility;
namespace Content.Server.GameObjects.EntitySystems.Pathfinding namespace Content.Server.GameObjects.EntitySystems.Pathfinding
{ {
@@ -17,9 +19,6 @@ namespace Content.Server.GameObjects.EntitySystems.Pathfinding
public PathfindingChunk ParentChunk => _parentChunk; public PathfindingChunk ParentChunk => _parentChunk;
private readonly PathfindingChunk _parentChunk; private readonly PathfindingChunk _parentChunk;
public Dictionary<Direction, PathfindingNode> Neighbors => _neighbors;
private Dictionary<Direction, PathfindingNode> _neighbors = new Dictionary<Direction, PathfindingNode>();
public TileRef TileRef { get; private set; } public TileRef TileRef { get; private set; }
/// <summary> /// <summary>
@@ -45,72 +44,48 @@ namespace Content.Server.GameObjects.EntitySystems.Pathfinding
GenerateMask(); GenerateMask();
} }
public void AddNeighbor(Direction direction, PathfindingNode node) /// <summary>
/// Return our neighboring nodes (even across chunks)
/// </summary>
/// <returns></returns>
public IEnumerable<PathfindingNode> GetNeighbors()
{ {
_neighbors.Add(direction, node); List<PathfindingChunk> neighborChunks = null;
} if (ParentChunk.OnEdge(this))
public void AddNeighbor(PathfindingNode node)
{
if (node.TileRef.GridIndex != TileRef.GridIndex)
{ {
throw new InvalidOperationException(); neighborChunks = ParentChunk.RelevantChunks(this).ToList();
} }
Direction direction; for (var x = -1; x <= 1; x++)
if (node.TileRef.X < TileRef.X)
{ {
if (node.TileRef.Y > TileRef.Y) for (var y = -1; y <= 1; y++)
{ {
direction = Direction.NorthWest; if (x == 0 && y == 0) continue;
} else if (node.TileRef.Y < TileRef.Y) var indices = new MapIndices(TileRef.X + x, TileRef.Y + y);
{ if (ParentChunk.InBounds(indices))
direction = Direction.SouthWest; {
} var (relativeX, relativeY) = (indices.X - ParentChunk.Indices.X,
else indices.Y - ParentChunk.Indices.Y);
{ yield return ParentChunk.Nodes[relativeX, relativeY];
direction = Direction.West; }
else
{
DebugTools.AssertNotNull(neighborChunks);
// Get the relevant chunk and then get the node on it
foreach (var neighbor in neighborChunks)
{
// A lot of edge transitions are going to have a single neighboring chunk
// (given > 1 only affects corners)
// So we can just check the count to see if it's inbound
if (neighborChunks.Count > 0 && !neighbor.InBounds(indices)) continue;
var (relativeX, relativeY) = (indices.X - neighbor.Indices.X,
indices.Y - neighbor.Indices.Y);
yield return neighbor.Nodes[relativeX, relativeY];
break;
}
}
} }
} }
else if (node.TileRef.X > TileRef.X)
{
if (node.TileRef.Y > TileRef.Y)
{
direction = Direction.NorthEast;
} else if (node.TileRef.Y < TileRef.Y)
{
direction = Direction.SouthEast;
}
else
{
direction = Direction.East;
}
}
else
{
if (node.TileRef.Y > TileRef.Y)
{
direction = Direction.North;
}
else
{
direction = Direction.South;
}
}
if (_neighbors.ContainsKey(direction))
{
// Should we verify that they align?
return;
}
_neighbors.Add(direction, node);
}
public PathfindingNode GetNeighbor(Direction direction)
{
_neighbors.TryGetValue(direction, out var node);
return node;
} }
public void UpdateTile(TileRef newTile) public void UpdateTile(TileRef newTile)

View File

@@ -167,32 +167,11 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding
{ {
var newChunk = new PathfindingChunk(gridId, indices); var newChunk = new PathfindingChunk(gridId, indices);
newChunk.Initialize(); newChunk.Initialize();
if (_graph.TryGetValue(gridId, out var chunks)) if (!_graph.ContainsKey(gridId))
{
for (var x = -1; x < 2; x++)
{
for (var y = -1; y < 2; y++)
{
if (x == 0 && y == 0) continue;
var neighborIndices = new MapIndices(
indices.X + x * PathfindingChunk.ChunkSize,
indices.Y + y * PathfindingChunk.ChunkSize);
if (chunks.TryGetValue(neighborIndices, out var neighborChunk))
{
neighborChunk.AddNeighbor(newChunk);
}
}
}
}
else
{ {
_graph.Add(gridId, new Dictionary<MapIndices, PathfindingChunk>()); _graph.Add(gridId, new Dictionary<MapIndices, PathfindingChunk>());
} }
_graph[gridId].Add(indices, newChunk); _graph[gridId].Add(indices, newChunk);
return newChunk; return newChunk;
} }