forked from space-wizards/space-station-14
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
VGRoid support (space-wizards#27659)
* Dungeon spawn support for grid spawns * Recursive dungeons working * Mask approach working * zack * More work * Fix recursive dungeons * Heap of work * weh * the cud * rar * Job * weh * weh * weh * Master merges * orch * weh * vgroid most of the work * Tweaks * Tweaks * weh * do do do do do do * Basic layout * Ore spawning working * Big breaking changes * Mob gen working * weh * Finalising * emo * More finalising * reverty * Reduce distance
- Loading branch information
1 parent
43a9337
commit c29d796
Showing
103 changed files
with
4,903 additions
and
2,602 deletions.
There are no files selected for viewing
123 changes: 123 additions & 0 deletions
123
Content.Server/NPC/Pathfinding/PathfindingSystem.Breadth.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
namespace Content.Server.NPC.Pathfinding; | ||
|
||
public sealed partial class PathfindingSystem | ||
{ | ||
/* | ||
* Handle BFS searches from Start->End. Doesn't consider NPC pathfinding. | ||
*/ | ||
|
||
/// <summary> | ||
/// Pathfinding args for a 1-many path. | ||
/// </summary> | ||
public record struct BreadthPathArgs() | ||
{ | ||
public Vector2i Start; | ||
public List<Vector2i> Ends; | ||
|
||
public bool Diagonals = false; | ||
|
||
public Func<Vector2i, float>? TileCost; | ||
|
||
public int Limit = 10000; | ||
} | ||
|
||
/// <summary> | ||
/// Gets a BFS path from start to any end. Can also supply an optional tile-cost for tiles. | ||
/// </summary> | ||
public SimplePathResult GetBreadthPath(BreadthPathArgs args) | ||
{ | ||
var cameFrom = new Dictionary<Vector2i, Vector2i>(); | ||
var costSoFar = new Dictionary<Vector2i, float>(); | ||
var frontier = new PriorityQueue<Vector2i, float>(); | ||
|
||
costSoFar[args.Start] = 0f; | ||
frontier.Enqueue(args.Start, 0f); | ||
var count = 0; | ||
|
||
while (frontier.TryDequeue(out var node, out _) && count < args.Limit) | ||
{ | ||
count++; | ||
|
||
if (args.Ends.Contains(node)) | ||
{ | ||
// Found target | ||
var path = ReconstructPath(node, cameFrom); | ||
|
||
return new SimplePathResult() | ||
{ | ||
CameFrom = cameFrom, | ||
Path = path, | ||
}; | ||
} | ||
|
||
var gCost = costSoFar[node]; | ||
|
||
if (args.Diagonals) | ||
{ | ||
for (var x = -1; x <= 1; x++) | ||
{ | ||
for (var y = -1; y <= 1; y++) | ||
{ | ||
var neighbor = node + new Vector2i(x, y); | ||
var neighborCost = OctileDistance(node, neighbor) * args.TileCost?.Invoke(neighbor) ?? 1f; | ||
|
||
if (neighborCost.Equals(0f)) | ||
{ | ||
continue; | ||
} | ||
|
||
// f = g + h | ||
// gScore is distance to the start node | ||
// hScore is distance to the end node | ||
var gScore = gCost + neighborCost; | ||
|
||
// Slower to get here so just ignore it. | ||
if (costSoFar.TryGetValue(neighbor, out var nextValue) && gScore >= nextValue) | ||
{ | ||
continue; | ||
} | ||
|
||
cameFrom[neighbor] = node; | ||
costSoFar[neighbor] = gScore; | ||
// pFactor is tie-breaker where the fscore is otherwise equal. | ||
// See http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html#breaking-ties | ||
// There's other ways to do it but future consideration | ||
// The closer the fScore is to the actual distance then the better the pathfinder will be | ||
// (i.e. somewhere between 1 and infinite) | ||
// Can use hierarchical pathfinder or whatever to improve the heuristic but this is fine for now. | ||
frontier.Enqueue(neighbor, gScore); | ||
} | ||
} | ||
} | ||
else | ||
{ | ||
for (var x = -1; x <= 1; x++) | ||
{ | ||
for (var y = -1; y <= 1; y++) | ||
{ | ||
if (x != 0 && y != 0) | ||
continue; | ||
|
||
var neighbor = node + new Vector2i(x, y); | ||
var neighborCost = ManhattanDistance(node, neighbor) * args.TileCost?.Invoke(neighbor) ?? 1f; | ||
|
||
if (neighborCost.Equals(0f)) | ||
continue; | ||
|
||
var gScore = gCost + neighborCost; | ||
|
||
if (costSoFar.TryGetValue(neighbor, out var nextValue) && gScore >= nextValue) | ||
continue; | ||
|
||
cameFrom[neighbor] = node; | ||
costSoFar[neighbor] = gScore; | ||
|
||
frontier.Enqueue(neighbor, gScore); | ||
} | ||
} | ||
} | ||
} | ||
|
||
return SimplePathResult.NoPath; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
namespace Content.Server.NPC.Pathfinding; | ||
|
||
public sealed partial class PathfindingSystem | ||
{ | ||
public void GridCast(Vector2i start, Vector2i end, Vector2iCallback callback) | ||
{ | ||
// https://gist.github.com/Pyr3z/46884d67641094d6cf353358566db566 | ||
// declare all locals at the top so it's obvious how big the footprint is | ||
int dx, dy, xinc, yinc, side, i, error; | ||
|
||
// starting cell is always returned | ||
if (!callback(start)) | ||
return; | ||
|
||
xinc = (end.X < start.X) ? -1 : 1; | ||
yinc = (end.Y < start.Y) ? -1 : 1; | ||
dx = xinc * (end.X - start.X); | ||
dy = yinc * (end.Y - start.Y); | ||
var ax = start.X; | ||
var ay = start.Y; | ||
|
||
if (dx == dy) // Handle perfect diagonals | ||
{ | ||
// I include this "optimization" for more aesthetic reasons, actually. | ||
// While Bresenham's Line can handle perfect diagonals just fine, it adds | ||
// additional cells to the line that make it not a perfect diagonal | ||
// anymore. So, while this branch is ~twice as fast as the next branch, | ||
// the real reason it is here is for style. | ||
|
||
// Also, there *is* the reason of performance. If used for cell-based | ||
// raycasts, for example, then perfect diagonals will check half as many | ||
// cells. | ||
|
||
while (dx --> 0) | ||
{ | ||
ax += xinc; | ||
ay += yinc; | ||
if (!callback(new Vector2i(ax, ay))) | ||
return; | ||
} | ||
|
||
return; | ||
} | ||
|
||
// Handle all other lines | ||
|
||
side = -1 * ((dx == 0 ? yinc : xinc) - 1); | ||
|
||
i = dx + dy; | ||
error = dx - dy; | ||
|
||
dx *= 2; | ||
dy *= 2; | ||
|
||
while (i --> 0) | ||
{ | ||
if (error > 0 || error == side) | ||
{ | ||
ax += xinc; | ||
error -= dy; | ||
} | ||
else | ||
{ | ||
ay += yinc; | ||
error += dx; | ||
} | ||
|
||
if (!callback(new Vector2i(ax, ay))) | ||
return; | ||
} | ||
} | ||
|
||
public delegate bool Vector2iCallback(Vector2i index); | ||
} |
154 changes: 154 additions & 0 deletions
154
Content.Server/NPC/Pathfinding/PathfindingSystem.Simple.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
namespace Content.Server.NPC.Pathfinding; | ||
|
||
public sealed partial class PathfindingSystem | ||
{ | ||
/// <summary> | ||
/// Pathfinding args for a 1-1 path. | ||
/// </summary> | ||
public record struct SimplePathArgs() | ||
{ | ||
public Vector2i Start; | ||
public Vector2i End; | ||
|
||
public bool Diagonals = false; | ||
|
||
public int Limit = 10000; | ||
|
||
/// <summary> | ||
/// Custom tile-costs if applicable. | ||
/// </summary> | ||
public Func<Vector2i, float>? TileCost; | ||
} | ||
|
||
public record struct SimplePathResult | ||
{ | ||
public static SimplePathResult NoPath = new(); | ||
|
||
public List<Vector2i> Path; | ||
public Dictionary<Vector2i, Vector2i> CameFrom; | ||
} | ||
|
||
/// <summary> | ||
/// Gets simple A* path from start to end. Can also supply an optional tile-cost for tiles. | ||
/// </summary> | ||
public SimplePathResult GetPath(SimplePathArgs args) | ||
{ | ||
var cameFrom = new Dictionary<Vector2i, Vector2i>(); | ||
var costSoFar = new Dictionary<Vector2i, float>(); | ||
var frontier = new PriorityQueue<Vector2i, float>(); | ||
|
||
costSoFar[args.Start] = 0f; | ||
frontier.Enqueue(args.Start, 0f); | ||
var count = 0; | ||
|
||
while (frontier.TryDequeue(out var node, out _) && count < args.Limit) | ||
{ | ||
count++; | ||
|
||
if (node == args.End) | ||
{ | ||
// Found target | ||
var path = ReconstructPath(args.End, cameFrom); | ||
|
||
return new SimplePathResult() | ||
{ | ||
CameFrom = cameFrom, | ||
Path = path, | ||
}; | ||
} | ||
|
||
var gCost = costSoFar[node]; | ||
|
||
if (args.Diagonals) | ||
{ | ||
for (var x = -1; x <= 1; x++) | ||
{ | ||
for (var y = -1; y <= 1; y++) | ||
{ | ||
var neighbor = node + new Vector2i(x, y); | ||
var neighborCost = OctileDistance(node, neighbor) * args.TileCost?.Invoke(neighbor) ?? 1f; | ||
|
||
if (neighborCost.Equals(0f)) | ||
{ | ||
continue; | ||
} | ||
|
||
// f = g + h | ||
// gScore is distance to the start node | ||
// hScore is distance to the end node | ||
var gScore = gCost + neighborCost; | ||
|
||
// Slower to get here so just ignore it. | ||
if (costSoFar.TryGetValue(neighbor, out var nextValue) && gScore >= nextValue) | ||
{ | ||
continue; | ||
} | ||
|
||
cameFrom[neighbor] = node; | ||
costSoFar[neighbor] = gScore; | ||
// pFactor is tie-breaker where the fscore is otherwise equal. | ||
// See http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html#breaking-ties | ||
// There's other ways to do it but future consideration | ||
// The closer the fScore is to the actual distance then the better the pathfinder will be | ||
// (i.e. somewhere between 1 and infinite) | ||
// Can use hierarchical pathfinder or whatever to improve the heuristic but this is fine for now. | ||
var hScore = OctileDistance(args.End, neighbor) * (1.0f + 1.0f / 1000.0f); | ||
var fScore = gScore + hScore; | ||
frontier.Enqueue(neighbor, fScore); | ||
} | ||
} | ||
} | ||
else | ||
{ | ||
for (var x = -1; x <= 1; x++) | ||
{ | ||
for (var y = -1; y <= 1; y++) | ||
{ | ||
if (x != 0 && y != 0) | ||
continue; | ||
|
||
var neighbor = node + new Vector2i(x, y); | ||
var neighborCost = ManhattanDistance(node, neighbor) * args.TileCost?.Invoke(neighbor) ?? 1f; | ||
|
||
if (neighborCost.Equals(0f)) | ||
continue; | ||
|
||
var gScore = gCost + neighborCost; | ||
|
||
if (costSoFar.TryGetValue(neighbor, out var nextValue) && gScore >= nextValue) | ||
continue; | ||
|
||
cameFrom[neighbor] = node; | ||
costSoFar[neighbor] = gScore; | ||
|
||
// Still use octile even for manhattan distance. | ||
var hScore = OctileDistance(args.End, neighbor) * 1.001f; | ||
var fScore = gScore + hScore; | ||
frontier.Enqueue(neighbor, fScore); | ||
} | ||
} | ||
} | ||
} | ||
|
||
return SimplePathResult.NoPath; | ||
} | ||
|
||
private List<Vector2i> ReconstructPath(Vector2i end, Dictionary<Vector2i, Vector2i> cameFrom) | ||
{ | ||
var path = new List<Vector2i>() | ||
{ | ||
end, | ||
}; | ||
var node = end; | ||
|
||
while (cameFrom.TryGetValue(node, out var source)) | ||
{ | ||
path.Add(source); | ||
node = source; | ||
} | ||
|
||
path.Reverse(); | ||
|
||
return path; | ||
} | ||
} |
Oops, something went wrong.