Skip to content

Commit

Permalink
VGRoid support (space-wizards#27659)
Browse files Browse the repository at this point in the history
* 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
metalgearsloth authored and aspiringLich committed Jul 21, 2024
1 parent 43a9337 commit c29d796
Show file tree
Hide file tree
Showing 103 changed files with 4,903 additions and 2,602 deletions.
123 changes: 123 additions & 0 deletions Content.Server/NPC/Pathfinding/PathfindingSystem.Breadth.cs
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;
}
}
74 changes: 74 additions & 0 deletions Content.Server/NPC/Pathfinding/PathfindingSystem.Line.cs
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 Content.Server/NPC/Pathfinding/PathfindingSystem.Simple.cs
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;
}
}
Loading

0 comments on commit c29d796

Please sign in to comment.