Pathfinder rework (#11452)

This commit is contained in:
metalgearsloth
2022-09-30 14:39:48 +10:00
committed by GitHub
parent fd3b29fb03
commit f456ad911e
80 changed files with 3606 additions and 4374 deletions

View File

@@ -0,0 +1,23 @@
using Robust.Shared.Serialization;
namespace Content.Shared.NPC;
[Serializable, NetSerializable]
public sealed class PathBreadcrumbsMessage : EntityEventArgs
{
public Dictionary<EntityUid, Dictionary<Vector2i, List<PathfindingBreadcrumb>>> Breadcrumbs = new();
}
[Serializable, NetSerializable]
public sealed class PathBreadcrumbsRefreshMessage : EntityEventArgs
{
public EntityUid GridUid;
public Vector2i Origin;
public List<PathfindingBreadcrumb> Data = new();
}
[Serializable, NetSerializable]
public sealed class PathPolysMessage : EntityEventArgs
{
public Dictionary<EntityUid, Dictionary<Vector2i, Dictionary<Vector2i, List<DebugPathPoly>>>> Polys = new();
}

View File

@@ -0,0 +1,15 @@
using Robust.Shared.Serialization;
namespace Content.Shared.NPC;
[Serializable, NetSerializable]
public sealed class PathPolysRefreshMessage : EntityEventArgs
{
public EntityUid GridUid;
public Vector2i Origin;
/// <summary>
/// Multi-dimension arrays aren't supported so
/// </summary>
public Dictionary<Vector2i, List<DebugPathPoly>> Polys = new();
}

View File

@@ -0,0 +1,19 @@
using Robust.Shared.Serialization;
namespace Content.Shared.NPC;
/// <summary>
/// Debug message containing a pathfinding route.
/// </summary>
[Serializable, NetSerializable]
public sealed class PathRouteMessage : EntityEventArgs
{
public List<DebugPathPoly> Path;
public Dictionary<DebugPathPoly, float> Costs;
public PathRouteMessage(List<DebugPathPoly> path, Dictionary<DebugPathPoly, float> costs)
{
Path = path;
Costs = costs;
}
}

View File

@@ -0,0 +1,9 @@
using Robust.Shared.Serialization;
namespace Content.Shared.NPC;
[Serializable, NetSerializable]
public sealed class RequestPathfindingDebugMessage : EntityEventArgs
{
public PathfindingDebugMode Mode;
}

View File

@@ -0,0 +1,32 @@
using Robust.Shared.Map;
using Robust.Shared.Serialization;
namespace Content.Shared.NPC;
/*
* I bikeshedded a lot on how to do this and I'm still not entirely happy.
* The main thing is you need a weak ref to the poly because it may be invalidated due to graph updates.
* I had a struct version but you still need to store the neighbors somewhere, maybe on the chunk itself?
* Future dev work required.
*/
/// <summary>
/// A path poly to be used for networked debug purposes.
/// </summary>
[Serializable, NetSerializable]
public sealed class DebugPathPoly
{
public EntityUid GraphUid;
public Vector2i ChunkOrigin;
public byte TileIndex;
public Box2 Box;
public PathfindingData Data;
public List<EntityCoordinates> Neighbors = default!;
}
[Serializable, NetSerializable]
public sealed class DebugPathPolyNeighbor
{
public EntityCoordinates Coordinates;
}

View File

@@ -0,0 +1,23 @@
using Robust.Shared.Serialization;
namespace Content.Shared.NPC;
/// <summary>
/// Boundary around a navigation region.
/// </summary>
[Serializable, NetSerializable]
public struct PathfindingBoundary
{
public List<PathfindingBreadcrumb> Breadcrumbs;
/// <summary>
/// Is it a closed loop or is it a special-case chain (e.g. thindows).
/// </summary>
public bool Closed;
public PathfindingBoundary(bool closed, List<PathfindingBreadcrumb> crumbs)
{
Closed = closed;
Breadcrumbs = crumbs;
}
}

View File

@@ -0,0 +1,118 @@
using Robust.Shared.Serialization;
namespace Content.Shared.NPC;
[Serializable, NetSerializable]
public struct PathfindingBreadcrumb : IEquatable<PathfindingBreadcrumb>
{
/// <summary>
/// The X and Y index in the point grid.
/// The actual coordinates require using <see cref="SharedPathfindingSystem.ChunkSize"/> and <see cref="SharedPathfindingSystem.SubStep"/>
/// </summary>
public Vector2i Coordinates;
public PathfindingData Data;
public static readonly PathfindingBreadcrumb Invalid = new()
{
Data = new PathfindingData(PathfindingBreadcrumbFlag.None, -1, -1, 0f),
};
public PathfindingBreadcrumb(Vector2i coordinates, int layer, int mask, float damage, PathfindingBreadcrumbFlag flags = PathfindingBreadcrumbFlag.None)
{
Coordinates = coordinates;
Data = new PathfindingData(flags, layer, mask, damage);
}
/// <summary>
/// Is this crumb equal for pathfinding region purposes.
/// </summary>
public bool Equivalent(PathfindingBreadcrumb other)
{
return Data.Equals(other.Data);
}
public bool Equals(PathfindingBreadcrumb other)
{
return Coordinates.Equals(other.Coordinates) && Data.Equals(other.Data);
}
public override bool Equals(object? obj)
{
return obj is PathfindingBreadcrumb other && Equals(other);
}
public override int GetHashCode()
{
return HashCode.Combine(Coordinates, Data);
}
}
/// <summary>
/// The data relevant for pathfinding.
/// </summary>
[Serializable, NetSerializable]
public struct PathfindingData : IEquatable<PathfindingData>
{
public PathfindingBreadcrumbFlag Flags;
public int CollisionLayer;
public int CollisionMask;
public float Damage;
public bool IsFreeSpace => (Flags == PathfindingBreadcrumbFlag.None && Damage.Equals(0f));
public PathfindingData(PathfindingBreadcrumbFlag flag, int layer, int mask, float damage)
{
Flags = flag;
CollisionLayer = layer;
CollisionMask = mask;
Damage = damage;
}
public bool IsEquivalent(PathfindingData other)
{
return CollisionLayer.Equals(other.CollisionLayer) &&
CollisionMask.Equals(other.CollisionMask) &&
Flags.Equals(other.Flags);
}
public bool Equals(PathfindingData other)
{
return CollisionLayer.Equals(other.CollisionLayer) &&
CollisionMask.Equals(other.CollisionMask) &&
Flags.Equals(other.Flags) &&
Damage.Equals(other.Damage);
}
public override bool Equals(object? obj)
{
return obj is PathfindingData other && Equals(other);
}
public override int GetHashCode()
{
return HashCode.Combine((int) Flags, CollisionLayer, CollisionMask);
}
}
[Flags]
public enum PathfindingBreadcrumbFlag : ushort
{
None = 0,
/// <summary>
/// Has this poly been replaced and is it no longer valid.
/// </summary>
Invalid = 1 << 0,
Space = 1 << 1,
/// <summary>
/// Is there a door that is potentially pryable
/// </summary>
Door = 1 << 2,
/// <summary>
/// Is there access required
/// </summary>
Access = 1 << 3,
}

View File

@@ -0,0 +1,46 @@
namespace Content.Shared.NPC;
[Flags]
public enum PathfindingDebugMode : ushort
{
None = 0,
/// <summary>
/// Show the individual pathfinding breadcrumbs.
/// </summary>
Breadcrumbs = 1 << 0,
/// <summary>
/// Show the pathfinding chunk edges.
/// </summary>
Chunks = 1 << 1,
/// <summary>
/// Shows the stats nearest crumb to the mouse cursor.
/// </summary>
Crumb = 1 << 2,
/// <summary>
/// Shows all of the pathfinding polys.
/// </summary>
Polys = 1 << 6,
/// <summary>
/// Shows the edges between pathfinding polys.
/// </summary>
PolyNeighbors = 1 << 7,
/// <summary>
/// Shows the nearest poly to the mouse cursor.
/// </summary>
Poly = 1 << 8,
/// <summary>
/// Gets a path from the current attached entity to the mouse cursor.
/// </summary>
Path = 1 << 9,
Routes = 1 << 10,
RouteCosts = 1 << 11,
}

View File

@@ -0,0 +1,22 @@
namespace Content.Shared.NPC;
public abstract class SharedPathfindingSystem : EntitySystem
{
/// <summary>
/// This is equivalent to agent radii for navmeshes. In our case it's preferable that things are cleanly
/// divisible per tile so we'll make sure it works as a discrete number.
/// </summary>
public const byte SubStep = 4;
public const byte ChunkSize = 8;
/// <summary>
/// We won't do points on edges so we'll offset them slightly.
/// </summary>
protected const float StepOffset = 1f / SubStep / 2f;
public Vector2 GetCoordinate(Vector2i chunk, Vector2i index)
{
return new Vector2(index.X, index.Y) / SubStep+ (chunk) * ChunkSize + StepOffset;
}
}