VGRoid support (#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
This commit is contained in:
123
Content.Server/NPC/Pathfinding/PathfindingSystem.Breadth.cs
Normal file
123
Content.Server/NPC/Pathfinding/PathfindingSystem.Breadth.cs
Normal file
@@ -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
Content.Server/NPC/Pathfinding/PathfindingSystem.Line.cs
Normal file
74
Content.Server/NPC/Pathfinding/PathfindingSystem.Line.cs
Normal file
@@ -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
Content.Server/NPC/Pathfinding/PathfindingSystem.Simple.cs
Normal file
154
Content.Server/NPC/Pathfinding/PathfindingSystem.Simple.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
180
Content.Server/NPC/Pathfinding/PathfindingSystem.Splines.cs
Normal file
180
Content.Server/NPC/Pathfinding/PathfindingSystem.Splines.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.NPC.Pathfinding;
|
||||
|
||||
public sealed partial class PathfindingSystem
|
||||
{
|
||||
public record struct SimplifyPathArgs
|
||||
{
|
||||
public Vector2i Start;
|
||||
public Vector2i End;
|
||||
public List<Vector2i> Path;
|
||||
}
|
||||
|
||||
public record struct SplinePathResult()
|
||||
{
|
||||
public static SplinePathResult NoPath = new();
|
||||
|
||||
public List<Vector2i> Points = new();
|
||||
|
||||
public List<Vector2i> Path = new();
|
||||
public Dictionary<Vector2i, Vector2i> CameFrom;
|
||||
}
|
||||
|
||||
public record struct SplinePathArgs(SimplePathArgs Args)
|
||||
{
|
||||
public SimplePathArgs Args = Args;
|
||||
|
||||
public float MaxRatio = 0.25f;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum distance between subdivisions.
|
||||
/// </summary>
|
||||
public int Distance = 20;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a spline path from start to end.
|
||||
/// </summary>
|
||||
public SplinePathResult GetSplinePath(SplinePathArgs args, Random random)
|
||||
{
|
||||
var start = args.Args.Start;
|
||||
var end = args.Args.End;
|
||||
|
||||
var path = new List<Vector2i>();
|
||||
|
||||
var pairs = new ValueList<(Vector2i Start, Vector2i End)> { (start, end) };
|
||||
var subdivided = true;
|
||||
|
||||
// Sub-divide recursively
|
||||
while (subdivided)
|
||||
{
|
||||
// Sometimes we might inadvertantly get 2 nodes too close together so better to just check each one as it comes up instead.
|
||||
var i = 0;
|
||||
subdivided = false;
|
||||
|
||||
while (i < pairs.Count)
|
||||
{
|
||||
var pointA = pairs[i].Start;
|
||||
var pointB = pairs[i].End;
|
||||
var vector = pointB - pointA;
|
||||
|
||||
var halfway = vector / 2f;
|
||||
|
||||
// Finding the point
|
||||
var adj = halfway.Length();
|
||||
|
||||
// Should we even subdivide.
|
||||
if (adj <= args.Distance)
|
||||
{
|
||||
// Just check the next entry no double skip.
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
subdivided = true;
|
||||
var opposite = args.MaxRatio * adj;
|
||||
var hypotenuse = MathF.Sqrt(MathF.Pow(adj, 2) + MathF.Pow(opposite, 2));
|
||||
|
||||
// Okay so essentially we have 2 points and no poly
|
||||
// We add 2 other points to form a diamond and want some point halfway between randomly offset.
|
||||
var angle = new Angle(MathF.Atan(opposite / adj));
|
||||
var pointAPerp = pointA + angle.RotateVec(halfway).Normalized() * hypotenuse;
|
||||
var pointBPerp = pointA + (-angle).RotateVec(halfway).Normalized() * hypotenuse;
|
||||
|
||||
var perpLine = pointBPerp - pointAPerp;
|
||||
var perpHalfway = perpLine.Length() / 2f;
|
||||
|
||||
var splinePoint = (pointAPerp + perpLine.Normalized() * random.NextFloat(-args.MaxRatio, args.MaxRatio) * perpHalfway).Floored();
|
||||
|
||||
// We essentially take (A, B) and turn it into (A, C) & (C, B)
|
||||
pairs[i] = (pointA, splinePoint);
|
||||
pairs.Insert(i + 1, (splinePoint, pointB));
|
||||
|
||||
i+= 2;
|
||||
}
|
||||
}
|
||||
|
||||
var spline = new ValueList<Vector2i>(pairs.Count - 1)
|
||||
{
|
||||
start
|
||||
};
|
||||
|
||||
foreach (var pair in pairs)
|
||||
{
|
||||
spline.Add(pair.End);
|
||||
}
|
||||
|
||||
// Now we need to pathfind between each node on the spline.
|
||||
|
||||
// TODO: Add rotation version or straight-line version for pathfinder config
|
||||
// Move the worm pathfinder to here I think.
|
||||
var cameFrom = new Dictionary<Vector2i, Vector2i>();
|
||||
|
||||
// TODO: Need to get rid of the branch bullshit.
|
||||
var points = new List<Vector2i>();
|
||||
|
||||
for (var i = 0; i < spline.Count - 1; i++)
|
||||
{
|
||||
var point = spline[i];
|
||||
var target = spline[i + 1];
|
||||
points.Add(point);
|
||||
var aStarArgs = args.Args with { Start = point, End = target };
|
||||
|
||||
var aStarResult = GetPath(aStarArgs);
|
||||
|
||||
if (aStarResult == SimplePathResult.NoPath)
|
||||
return SplinePathResult.NoPath;
|
||||
|
||||
path.AddRange(aStarResult.Path[0..]);
|
||||
|
||||
foreach (var a in aStarResult.CameFrom)
|
||||
{
|
||||
cameFrom[a.Key] = a.Value;
|
||||
}
|
||||
}
|
||||
|
||||
points.Add(spline[^1]);
|
||||
|
||||
var simple = SimplifyPath(new SimplifyPathArgs()
|
||||
{
|
||||
Start = args.Args.Start,
|
||||
End = args.Args.End,
|
||||
Path = path,
|
||||
});
|
||||
|
||||
return new SplinePathResult()
|
||||
{
|
||||
Path = simple,
|
||||
CameFrom = cameFrom,
|
||||
Points = points,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does a simpler pathfinder over the nodes to prune unnecessary branches.
|
||||
/// </summary>
|
||||
public List<Vector2i> SimplifyPath(SimplifyPathArgs args)
|
||||
{
|
||||
var nodes = new HashSet<Vector2i>(args.Path);
|
||||
|
||||
var result = GetBreadthPath(new BreadthPathArgs()
|
||||
{
|
||||
Start = args.Start,
|
||||
Ends = new List<Vector2i>()
|
||||
{
|
||||
args.End,
|
||||
},
|
||||
TileCost = node =>
|
||||
{
|
||||
if (!nodes.Contains(node))
|
||||
return 0f;
|
||||
|
||||
return 1f;
|
||||
}
|
||||
});
|
||||
|
||||
return result.Path;
|
||||
}
|
||||
}
|
||||
89
Content.Server/NPC/Pathfinding/PathfindingSystem.Widen.cs
Normal file
89
Content.Server/NPC/Pathfinding/PathfindingSystem.Widen.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.NPC.Pathfinding;
|
||||
|
||||
public sealed partial class PathfindingSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Widens the path by the specified amount.
|
||||
/// </summary>
|
||||
public HashSet<Vector2i> GetWiden(WidenArgs args, Random random)
|
||||
{
|
||||
var tiles = new HashSet<Vector2i>(args.Path.Count * 2);
|
||||
var variance = (args.MaxWiden - args.MinWiden) / 2f + args.MinWiden;
|
||||
var counter = 0;
|
||||
|
||||
foreach (var tile in args.Path)
|
||||
{
|
||||
counter++;
|
||||
|
||||
if (counter != args.TileSkip)
|
||||
continue;
|
||||
|
||||
counter = 0;
|
||||
|
||||
var center = new Vector2(tile.X + 0.5f, tile.Y + 0.5f);
|
||||
|
||||
if (args.Square)
|
||||
{
|
||||
for (var x = -variance; x <= variance; x++)
|
||||
{
|
||||
for (var y = -variance; y <= variance; y++)
|
||||
{
|
||||
var neighbor = center + new Vector2(x, y);
|
||||
|
||||
tiles.Add(neighbor.Floored());
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var x = -variance; x <= variance; x++)
|
||||
{
|
||||
for (var y = -variance; y <= variance; y++)
|
||||
{
|
||||
var offset = new Vector2(x, y);
|
||||
|
||||
if (offset.Length() > variance)
|
||||
continue;
|
||||
|
||||
var neighbor = center + offset;
|
||||
|
||||
tiles.Add(neighbor.Floored());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variance += random.NextFloat(-args.Variance * args.TileSkip, args.Variance * args.TileSkip);
|
||||
variance = Math.Clamp(variance, args.MinWiden, args.MaxWiden);
|
||||
}
|
||||
|
||||
return tiles;
|
||||
}
|
||||
|
||||
public record struct WidenArgs()
|
||||
{
|
||||
public bool Square = false;
|
||||
|
||||
/// <summary>
|
||||
/// How many tiles to skip between iterations., 1-in-n
|
||||
/// </summary>
|
||||
public int TileSkip = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum amount to vary per tile.
|
||||
/// </summary>
|
||||
public float Variance = 0.25f;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum width.
|
||||
/// </summary>
|
||||
public float MinWiden = 2f;
|
||||
|
||||
|
||||
public float MaxWiden = 7f;
|
||||
|
||||
public List<Vector2i> Path;
|
||||
}
|
||||
}
|
||||
@@ -142,6 +142,13 @@ public sealed partial class NPCSteeringSystem
|
||||
|
||||
// Grab the target position, either the next path node or our end goal..
|
||||
var targetCoordinates = GetTargetCoordinates(steering);
|
||||
|
||||
if (!targetCoordinates.IsValid(EntityManager))
|
||||
{
|
||||
steering.Status = SteeringStatus.NoPath;
|
||||
return false;
|
||||
}
|
||||
|
||||
var needsPath = false;
|
||||
|
||||
// If the next node is invalid then get new ones
|
||||
@@ -243,6 +250,14 @@ public sealed partial class NPCSteeringSystem
|
||||
// Alright just adjust slightly and grab the next node so we don't stop moving for a tick.
|
||||
// TODO: If it's the last node just grab the target instead.
|
||||
targetCoordinates = GetTargetCoordinates(steering);
|
||||
|
||||
if (!targetCoordinates.IsValid(EntityManager))
|
||||
{
|
||||
SetDirection(mover, steering, Vector2.Zero);
|
||||
steering.Status = SteeringStatus.NoPath;
|
||||
return false;
|
||||
}
|
||||
|
||||
targetMap = targetCoordinates.ToMap(EntityManager, _transform);
|
||||
|
||||
// Can't make it again.
|
||||
|
||||
Reference in New Issue
Block a user