Pathfinder rework (#11452)
This commit is contained in:
@@ -1,197 +0,0 @@
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.AI;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Content.Client.NPC
|
||||
{
|
||||
public sealed class ClientAiDebugSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
|
||||
public AiDebugMode Tooltips { get; private set; } = AiDebugMode.None;
|
||||
private readonly Dictionary<EntityUid, PanelContainer> _aiBoxes = new();
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
if (Tooltips == 0)
|
||||
{
|
||||
if (_aiBoxes.Count > 0)
|
||||
{
|
||||
foreach (var (_, panel) in _aiBoxes)
|
||||
{
|
||||
panel.Dispose();
|
||||
}
|
||||
|
||||
_aiBoxes.Clear();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var deletedEntities = new List<EntityUid>(0);
|
||||
foreach (var (entity, panel) in _aiBoxes)
|
||||
{
|
||||
if (Deleted(entity))
|
||||
{
|
||||
deletedEntities.Add(entity);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_eyeManager.GetWorldViewport().Contains(EntityManager.GetComponent<TransformComponent>(entity).WorldPosition))
|
||||
{
|
||||
panel.Visible = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
var (x, y) = _eyeManager.CoordinatesToScreen(EntityManager.GetComponent<TransformComponent>(entity).Coordinates).Position;
|
||||
var offsetPosition = new Vector2(x - panel.Width / 2, y - panel.Height - 50f);
|
||||
panel.Visible = true;
|
||||
|
||||
LayoutContainer.SetPosition(panel, offsetPosition);
|
||||
}
|
||||
|
||||
foreach (var entity in deletedEntities)
|
||||
{
|
||||
_aiBoxes.Remove(entity);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
UpdatesOutsidePrediction = true;
|
||||
SubscribeNetworkEvent<SharedAiDebug.UtilityAiDebugMessage>(HandleUtilityAiDebugMessage);
|
||||
SubscribeNetworkEvent<SharedAiDebug.AStarRouteMessage>(HandleAStarRouteMessage);
|
||||
SubscribeNetworkEvent<SharedAiDebug.JpsRouteMessage>(HandleJpsRouteMessage);
|
||||
}
|
||||
|
||||
private void HandleUtilityAiDebugMessage(SharedAiDebug.UtilityAiDebugMessage message)
|
||||
{
|
||||
if ((Tooltips & AiDebugMode.Thonk) != 0)
|
||||
{
|
||||
// I guess if it's out of range we don't know about it?
|
||||
var entity = message.EntityUid;
|
||||
TryCreatePanel(entity);
|
||||
|
||||
// Probably shouldn't access by index but it's a debugging tool so eh
|
||||
var label = (Label) _aiBoxes[entity].GetChild(0).GetChild(0);
|
||||
label.Text = $"Current Task: {message.FoundTask}\n" +
|
||||
$"Task score: {message.ActionScore}\n" +
|
||||
$"Planning time (ms): {message.PlanningTime * 1000:0.0000}\n" +
|
||||
$"Considered {message.ConsideredTaskCount} tasks";
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleAStarRouteMessage(SharedAiDebug.AStarRouteMessage message)
|
||||
{
|
||||
if ((Tooltips & AiDebugMode.Paths) != 0)
|
||||
{
|
||||
var entity = message.EntityUid;
|
||||
TryCreatePanel(entity);
|
||||
|
||||
var label = (Label) _aiBoxes[entity].GetChild(0).GetChild(1);
|
||||
label.Text = $"Pathfinding time (ms): {message.TimeTaken * 1000:0.0000}\n" +
|
||||
$"Nodes traversed: {message.CameFrom.Count}\n" +
|
||||
$"Nodes per ms: {message.CameFrom.Count / (message.TimeTaken * 1000)}";
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleJpsRouteMessage(SharedAiDebug.JpsRouteMessage message)
|
||||
{
|
||||
if ((Tooltips & AiDebugMode.Paths) != 0)
|
||||
{
|
||||
var entity = message.EntityUid;
|
||||
TryCreatePanel(entity);
|
||||
|
||||
var label = (Label) _aiBoxes[entity].GetChild(0).GetChild(1);
|
||||
label.Text = $"Pathfinding time (ms): {message.TimeTaken * 1000:0.0000}\n" +
|
||||
$"Jump Nodes: {message.JumpNodes.Count}\n" +
|
||||
$"Jump Nodes per ms: {message.JumpNodes.Count / (message.TimeTaken * 1000)}";
|
||||
}
|
||||
}
|
||||
|
||||
public void Disable()
|
||||
{
|
||||
foreach (var tooltip in _aiBoxes.Values)
|
||||
{
|
||||
tooltip.Dispose();
|
||||
}
|
||||
_aiBoxes.Clear();
|
||||
Tooltips = AiDebugMode.None;
|
||||
}
|
||||
|
||||
|
||||
public void EnableTooltip(AiDebugMode tooltip)
|
||||
{
|
||||
Tooltips |= tooltip;
|
||||
}
|
||||
|
||||
public void DisableTooltip(AiDebugMode tooltip)
|
||||
{
|
||||
Tooltips &= ~tooltip;
|
||||
}
|
||||
|
||||
public void ToggleTooltip(AiDebugMode tooltip)
|
||||
{
|
||||
if ((Tooltips & tooltip) != 0)
|
||||
{
|
||||
DisableTooltip(tooltip);
|
||||
}
|
||||
else
|
||||
{
|
||||
EnableTooltip(tooltip);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryCreatePanel(EntityUid entity)
|
||||
{
|
||||
if (!_aiBoxes.ContainsKey(entity))
|
||||
{
|
||||
var userInterfaceManager = IoCManager.Resolve<IUserInterfaceManager>();
|
||||
|
||||
var actionLabel = new Label
|
||||
{
|
||||
MouseFilter = Control.MouseFilterMode.Ignore,
|
||||
};
|
||||
|
||||
var pathfindingLabel = new Label
|
||||
{
|
||||
MouseFilter = Control.MouseFilterMode.Ignore,
|
||||
};
|
||||
|
||||
var vBox = new BoxContainer()
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
SeparationOverride = 15,
|
||||
Children = {actionLabel, pathfindingLabel},
|
||||
};
|
||||
|
||||
var panel = new PanelContainer
|
||||
{
|
||||
StyleClasses = { StyleNano.StyleClassTooltipPanel },
|
||||
Children = {vBox},
|
||||
MouseFilter = Control.MouseFilterMode.Ignore,
|
||||
ModulateSelfOverride = Color.White.WithAlpha(0.75f),
|
||||
};
|
||||
|
||||
userInterfaceManager.StateRoot.AddChild(panel);
|
||||
|
||||
_aiBoxes[entity] = panel;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum AiDebugMode : byte
|
||||
{
|
||||
None = 0,
|
||||
Paths = 1 << 1,
|
||||
Thonk = 1 << 2,
|
||||
}
|
||||
}
|
||||
@@ -1,520 +0,0 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.AI;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.NPC
|
||||
{
|
||||
public sealed class ClientPathfindingDebugSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
public PathfindingDebugMode Modes { get; private set; } = PathfindingDebugMode.None;
|
||||
private float _routeDuration = 4.0f; // How long before we remove a route from the overlay
|
||||
private DebugPathfindingOverlay? _overlay;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeNetworkEvent<SharedAiDebug.AStarRouteMessage>(HandleAStarRouteMessage);
|
||||
SubscribeNetworkEvent<SharedAiDebug.JpsRouteMessage>(HandleJpsRouteMessage);
|
||||
SubscribeNetworkEvent<SharedAiDebug.PathfindingGraphMessage>(HandleGraphMessage);
|
||||
SubscribeNetworkEvent<SharedAiDebug.ReachableChunkRegionsDebugMessage>(HandleRegionsMessage);
|
||||
SubscribeNetworkEvent<SharedAiDebug.ReachableCacheDebugMessage>(HandleCachedRegionsMessage);
|
||||
// I'm lazy
|
||||
EnableOverlay();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
DisableOverlay();
|
||||
}
|
||||
|
||||
private void HandleAStarRouteMessage(SharedAiDebug.AStarRouteMessage message)
|
||||
{
|
||||
if ((Modes & PathfindingDebugMode.Nodes) != 0 ||
|
||||
(Modes & PathfindingDebugMode.Route) != 0)
|
||||
{
|
||||
_overlay?.AStarRoutes.Add(message);
|
||||
Timer.Spawn(TimeSpan.FromSeconds(_routeDuration), () =>
|
||||
{
|
||||
if (_overlay == null) return;
|
||||
_overlay.AStarRoutes.Remove(message);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleJpsRouteMessage(SharedAiDebug.JpsRouteMessage message)
|
||||
{
|
||||
if ((Modes & PathfindingDebugMode.Nodes) != 0 ||
|
||||
(Modes & PathfindingDebugMode.Route) != 0)
|
||||
{
|
||||
_overlay?.JpsRoutes.Add(message);
|
||||
Timer.Spawn(TimeSpan.FromSeconds(_routeDuration), () =>
|
||||
{
|
||||
if (_overlay == null) return;
|
||||
_overlay.JpsRoutes.Remove(message);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleGraphMessage(SharedAiDebug.PathfindingGraphMessage message)
|
||||
{
|
||||
EnableOverlay().UpdateGraph(message.Graph);
|
||||
}
|
||||
|
||||
private void HandleRegionsMessage(SharedAiDebug.ReachableChunkRegionsDebugMessage message)
|
||||
{
|
||||
EnableOverlay().UpdateRegions(message.GridId, message.Regions);
|
||||
}
|
||||
|
||||
private void HandleCachedRegionsMessage(SharedAiDebug.ReachableCacheDebugMessage message)
|
||||
{
|
||||
EnableOverlay().UpdateCachedRegions(message.GridId, message.Regions, message.Cached);
|
||||
}
|
||||
|
||||
private DebugPathfindingOverlay EnableOverlay()
|
||||
{
|
||||
if (_overlay != null)
|
||||
{
|
||||
return _overlay;
|
||||
}
|
||||
|
||||
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
||||
_overlay = new DebugPathfindingOverlay(EntityManager, _eyeManager, _playerManager, _prototypeManager) {Modes = Modes};
|
||||
overlayManager.AddOverlay(_overlay);
|
||||
|
||||
return _overlay;
|
||||
}
|
||||
|
||||
private void DisableOverlay()
|
||||
{
|
||||
if (_overlay == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_overlay.Modes = 0;
|
||||
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
||||
overlayManager.RemoveOverlay(_overlay);
|
||||
_overlay = null;
|
||||
}
|
||||
|
||||
public void Disable()
|
||||
{
|
||||
Modes = PathfindingDebugMode.None;
|
||||
DisableOverlay();
|
||||
}
|
||||
|
||||
|
||||
public void EnableMode(PathfindingDebugMode tooltip)
|
||||
{
|
||||
Modes |= tooltip;
|
||||
if (Modes != 0)
|
||||
{
|
||||
EnableOverlay();
|
||||
}
|
||||
|
||||
if (_overlay != null)
|
||||
{
|
||||
_overlay.Modes = Modes;
|
||||
}
|
||||
|
||||
if (tooltip == PathfindingDebugMode.Graph)
|
||||
{
|
||||
RaiseNetworkEvent(new SharedAiDebug.RequestPathfindingGraphMessage());
|
||||
}
|
||||
|
||||
if (tooltip == PathfindingDebugMode.Regions)
|
||||
{
|
||||
RaiseNetworkEvent(new SharedAiDebug.SubscribeReachableMessage());
|
||||
}
|
||||
|
||||
// TODO: Request region graph, although the client system messages didn't seem to be going through anymore
|
||||
// So need further investigation.
|
||||
}
|
||||
|
||||
public void DisableMode(PathfindingDebugMode mode)
|
||||
{
|
||||
if (mode == PathfindingDebugMode.Regions && (Modes & PathfindingDebugMode.Regions) != 0)
|
||||
{
|
||||
RaiseNetworkEvent(new SharedAiDebug.UnsubscribeReachableMessage());
|
||||
}
|
||||
|
||||
Modes &= ~mode;
|
||||
if (Modes == 0)
|
||||
{
|
||||
DisableOverlay();
|
||||
}
|
||||
else if (_overlay != null)
|
||||
{
|
||||
_overlay.Modes = Modes;
|
||||
}
|
||||
}
|
||||
|
||||
public void ToggleTooltip(PathfindingDebugMode mode)
|
||||
{
|
||||
if ((Modes & mode) != 0)
|
||||
{
|
||||
DisableMode(mode);
|
||||
}
|
||||
else
|
||||
{
|
||||
EnableMode(mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class DebugPathfindingOverlay : Overlay
|
||||
{
|
||||
private readonly IEyeManager _eyeManager;
|
||||
private readonly IPlayerManager _playerManager;
|
||||
private readonly IEntityManager _entities;
|
||||
|
||||
// TODO: Add a box like the debug one and show the most recent path stuff
|
||||
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
|
||||
private readonly ShaderInstance _shader;
|
||||
|
||||
public PathfindingDebugMode Modes { get; set; } = PathfindingDebugMode.None;
|
||||
|
||||
// Graph debugging
|
||||
public readonly Dictionary<int, List<Vector2>> Graph = new();
|
||||
private readonly Dictionary<int, Color> _graphColors = new();
|
||||
|
||||
// Cached regions
|
||||
public readonly Dictionary<EntityUid, Dictionary<int, List<Vector2>>> CachedRegions =
|
||||
new();
|
||||
|
||||
private readonly Dictionary<EntityUid, Dictionary<int, Color>> _cachedRegionColors =
|
||||
new();
|
||||
|
||||
// Regions
|
||||
public readonly Dictionary<EntityUid, Dictionary<int, Dictionary<int, List<Vector2>>>> Regions =
|
||||
new();
|
||||
|
||||
private readonly Dictionary<EntityUid, Dictionary<int, Dictionary<int, Color>>> _regionColors =
|
||||
new();
|
||||
|
||||
// Route debugging
|
||||
// As each pathfinder is very different you'll likely want to draw them completely different
|
||||
public readonly List<SharedAiDebug.AStarRouteMessage> AStarRoutes = new();
|
||||
public readonly List<SharedAiDebug.JpsRouteMessage> JpsRoutes = new();
|
||||
|
||||
public DebugPathfindingOverlay(IEntityManager entities, IEyeManager eyeManager, IPlayerManager playerManager, IPrototypeManager prototypeManager)
|
||||
{
|
||||
_entities = entities;
|
||||
_eyeManager = eyeManager;
|
||||
_playerManager = playerManager;
|
||||
_shader = prototypeManager.Index<ShaderPrototype>("unshaded").Instance();
|
||||
}
|
||||
|
||||
#region Graph
|
||||
public void UpdateGraph(Dictionary<int, List<Vector2>> graph)
|
||||
{
|
||||
Graph.Clear();
|
||||
_graphColors.Clear();
|
||||
var robustRandom = IoCManager.Resolve<IRobustRandom>();
|
||||
foreach (var (chunk, nodes) in graph)
|
||||
{
|
||||
Graph[chunk] = nodes;
|
||||
_graphColors[chunk] = new Color(robustRandom.NextFloat(), robustRandom.NextFloat(),
|
||||
robustRandom.NextFloat(), 0.3f);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawGraph(DrawingHandleScreen screenHandle, Box2 viewport)
|
||||
{
|
||||
foreach (var (chunk, nodes) in Graph)
|
||||
{
|
||||
foreach (var tile in nodes)
|
||||
{
|
||||
if (!viewport.Contains(tile)) continue;
|
||||
|
||||
var screenTile = _eyeManager.WorldToScreen(tile);
|
||||
var box = new UIBox2(
|
||||
screenTile.X - 15.0f,
|
||||
screenTile.Y - 15.0f,
|
||||
screenTile.X + 15.0f,
|
||||
screenTile.Y + 15.0f);
|
||||
|
||||
screenHandle.DrawRect(box, _graphColors[chunk]);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Regions
|
||||
//Server side debugger should increment every region
|
||||
public void UpdateCachedRegions(EntityUid gridId, Dictionary<int, List<Vector2>> messageRegions, bool cached)
|
||||
{
|
||||
if (!CachedRegions.ContainsKey(gridId))
|
||||
{
|
||||
CachedRegions.Add(gridId, new Dictionary<int, List<Vector2>>());
|
||||
_cachedRegionColors.Add(gridId, new Dictionary<int, Color>());
|
||||
}
|
||||
|
||||
foreach (var (region, nodes) in messageRegions)
|
||||
{
|
||||
CachedRegions[gridId][region] = nodes;
|
||||
if (cached)
|
||||
{
|
||||
_cachedRegionColors[gridId][region] = Color.Blue.WithAlpha(0.3f);
|
||||
}
|
||||
else
|
||||
{
|
||||
_cachedRegionColors[gridId][region] = Color.LimeGreen.WithAlpha(0.3f);
|
||||
}
|
||||
|
||||
Timer.Spawn(3000, () =>
|
||||
{
|
||||
if (CachedRegions[gridId].ContainsKey(region))
|
||||
{
|
||||
CachedRegions[gridId].Remove(region);
|
||||
_cachedRegionColors[gridId].Remove(region);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawCachedRegions(DrawingHandleScreen screenHandle, Box2 viewport)
|
||||
{
|
||||
var transform = _entities.GetComponentOrNull<TransformComponent>(_playerManager.LocalPlayer?.ControlledEntity);
|
||||
if (transform == null || transform.GridUid == null || !CachedRegions.TryGetValue(transform.GridUid.Value, out var entityRegions))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var (region, nodes) in entityRegions)
|
||||
{
|
||||
foreach (var tile in nodes)
|
||||
{
|
||||
if (!viewport.Contains(tile)) continue;
|
||||
|
||||
var screenTile = _eyeManager.WorldToScreen(tile);
|
||||
var box = new UIBox2(
|
||||
screenTile.X - 15.0f,
|
||||
screenTile.Y - 15.0f,
|
||||
screenTile.X + 15.0f,
|
||||
screenTile.Y + 15.0f);
|
||||
|
||||
screenHandle.DrawRect(box, _cachedRegionColors[transform.GridUid.Value][region]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateRegions(EntityUid gridId, Dictionary<int, Dictionary<int, List<Vector2>>> messageRegions)
|
||||
{
|
||||
if (!Regions.ContainsKey(gridId))
|
||||
{
|
||||
Regions.Add(gridId, new Dictionary<int, Dictionary<int, List<Vector2>>>());
|
||||
_regionColors.Add(gridId, new Dictionary<int, Dictionary<int, Color>>());
|
||||
}
|
||||
|
||||
var robustRandom = IoCManager.Resolve<IRobustRandom>();
|
||||
foreach (var (chunk, regions) in messageRegions)
|
||||
{
|
||||
Regions[gridId][chunk] = new Dictionary<int, List<Vector2>>();
|
||||
_regionColors[gridId][chunk] = new Dictionary<int, Color>();
|
||||
|
||||
foreach (var (region, nodes) in regions)
|
||||
{
|
||||
Regions[gridId][chunk].Add(region, nodes);
|
||||
_regionColors[gridId][chunk][region] = new Color(robustRandom.NextFloat(), robustRandom.NextFloat(),
|
||||
robustRandom.NextFloat(), 0.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawRegions(DrawingHandleScreen screenHandle, Box2 viewport)
|
||||
{
|
||||
var attachedEntity = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
if (!_entities.TryGetComponent(attachedEntity, out TransformComponent? transform) ||
|
||||
transform.GridUid == null ||
|
||||
!Regions.TryGetValue(transform.GridUid.Value, out var entityRegions))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var (chunk, regions) in entityRegions)
|
||||
{
|
||||
foreach (var (region, nodes) in regions)
|
||||
{
|
||||
foreach (var tile in nodes)
|
||||
{
|
||||
if (!viewport.Contains(tile)) continue;
|
||||
|
||||
var screenTile = _eyeManager.WorldToScreen(tile);
|
||||
var box = new UIBox2(
|
||||
screenTile.X - 15.0f,
|
||||
screenTile.Y - 15.0f,
|
||||
screenTile.X + 15.0f,
|
||||
screenTile.Y + 15.0f);
|
||||
|
||||
screenHandle.DrawRect(box, _regionColors[transform.GridUid.Value][chunk][region]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Pathfinder
|
||||
private void DrawAStarRoutes(DrawingHandleScreen screenHandle, Box2 viewport)
|
||||
{
|
||||
foreach (var route in AStarRoutes)
|
||||
{
|
||||
// Draw box on each tile of route
|
||||
foreach (var position in route.Route)
|
||||
{
|
||||
if (!viewport.Contains(position)) continue;
|
||||
var screenTile = _eyeManager.WorldToScreen(position);
|
||||
// worldHandle.DrawLine(position, nextWorld.Value, Color.Blue);
|
||||
var box = new UIBox2(
|
||||
screenTile.X - 15.0f,
|
||||
screenTile.Y - 15.0f,
|
||||
screenTile.X + 15.0f,
|
||||
screenTile.Y + 15.0f);
|
||||
screenHandle.DrawRect(box, Color.Orange.WithAlpha(0.25f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawAStarNodes(DrawingHandleScreen screenHandle, Box2 viewport)
|
||||
{
|
||||
foreach (var route in AStarRoutes)
|
||||
{
|
||||
var highestGScore = route.GScores.Values.Max();
|
||||
|
||||
foreach (var (tile, score) in route.GScores)
|
||||
{
|
||||
if ((route.Route.Contains(tile) && (Modes & PathfindingDebugMode.Route) != 0) ||
|
||||
!viewport.Contains(tile))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var screenTile = _eyeManager.WorldToScreen(tile);
|
||||
var box = new UIBox2(
|
||||
screenTile.X - 15.0f,
|
||||
screenTile.Y - 15.0f,
|
||||
screenTile.X + 15.0f,
|
||||
screenTile.Y + 15.0f);
|
||||
|
||||
screenHandle.DrawRect(box, new Color(
|
||||
0.0f,
|
||||
score / highestGScore,
|
||||
1.0f - (score / highestGScore),
|
||||
0.1f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawJpsRoutes(DrawingHandleScreen screenHandle, Box2 viewport)
|
||||
{
|
||||
foreach (var route in JpsRoutes)
|
||||
{
|
||||
// Draw box on each tile of route
|
||||
foreach (var position in route.Route)
|
||||
{
|
||||
if (!viewport.Contains(position)) continue;
|
||||
var screenTile = _eyeManager.WorldToScreen(position);
|
||||
// worldHandle.DrawLine(position, nextWorld.Value, Color.Blue);
|
||||
var box = new UIBox2(
|
||||
screenTile.X - 15.0f,
|
||||
screenTile.Y - 15.0f,
|
||||
screenTile.X + 15.0f,
|
||||
screenTile.Y + 15.0f);
|
||||
screenHandle.DrawRect(box, Color.Orange.WithAlpha(0.25f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawJpsNodes(DrawingHandleScreen screenHandle, Box2 viewport)
|
||||
{
|
||||
foreach (var route in JpsRoutes)
|
||||
{
|
||||
foreach (var tile in route.JumpNodes)
|
||||
{
|
||||
if ((route.Route.Contains(tile) && (Modes & PathfindingDebugMode.Route) != 0) ||
|
||||
!viewport.Contains(tile))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var screenTile = _eyeManager.WorldToScreen(tile);
|
||||
var box = new UIBox2(
|
||||
screenTile.X - 15.0f,
|
||||
screenTile.Y - 15.0f,
|
||||
screenTile.X + 15.0f,
|
||||
screenTile.Y + 15.0f);
|
||||
|
||||
screenHandle.DrawRect(box, new Color(
|
||||
0.0f,
|
||||
1.0f,
|
||||
0.0f,
|
||||
0.2f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (Modes == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var screenHandle = args.ScreenHandle;
|
||||
screenHandle.UseShader(_shader);
|
||||
var viewport = args.WorldAABB;
|
||||
|
||||
if ((Modes & PathfindingDebugMode.Route) != 0)
|
||||
{
|
||||
DrawAStarRoutes(screenHandle, viewport);
|
||||
DrawJpsRoutes(screenHandle, viewport);
|
||||
}
|
||||
|
||||
if ((Modes & PathfindingDebugMode.Nodes) != 0)
|
||||
{
|
||||
DrawAStarNodes(screenHandle, viewport);
|
||||
DrawJpsNodes(screenHandle, viewport);
|
||||
}
|
||||
|
||||
if ((Modes & PathfindingDebugMode.Graph) != 0)
|
||||
{
|
||||
DrawGraph(screenHandle, viewport);
|
||||
}
|
||||
|
||||
if ((Modes & PathfindingDebugMode.CachedRegions) != 0)
|
||||
{
|
||||
DrawCachedRegions(screenHandle, viewport);
|
||||
}
|
||||
|
||||
if ((Modes & PathfindingDebugMode.Regions) != 0)
|
||||
{
|
||||
DrawRegions(screenHandle, viewport);
|
||||
}
|
||||
|
||||
screenHandle.UseShader(null);
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum PathfindingDebugMode : byte
|
||||
{
|
||||
None = 0,
|
||||
Route = 1 << 0,
|
||||
Graph = 1 << 1,
|
||||
Nodes = 1 << 2,
|
||||
CachedRegions = 1 << 3,
|
||||
Regions = 1 << 4,
|
||||
}
|
||||
}
|
||||
@@ -15,9 +15,11 @@
|
||||
<Label Text="Pathfinder" HorizontalAlignment="Center"/>
|
||||
</controls:StripeBack>
|
||||
<BoxContainer Name="PathfinderBox" Orientation="Vertical">
|
||||
<CheckBox Name="PathNodes" Text="Nodes"/>
|
||||
<CheckBox Name="PathCrumbs" Text="Breadcrumbs"/>
|
||||
<CheckBox Name="PathPolys" Text="Polygons"/>
|
||||
<CheckBox Name="PathNeighbors" Text="Neighbors"/>
|
||||
<CheckBox Name="PathRouteCosts" Text="Route costs"/>
|
||||
<CheckBox Name="PathRoutes" Text="Routes"/>
|
||||
<CheckBox Name="PathRegions" Text="Regions"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.NPC;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
@@ -12,21 +13,18 @@ public sealed partial class NPCWindow : FancyWindow
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
var sysManager = IoCManager.Resolve<IEntitySystemManager>();
|
||||
var debugSys = sysManager.GetEntitySystem<ClientAiDebugSystem>();
|
||||
var path = sysManager.GetEntitySystem<ClientPathfindingDebugSystem>();
|
||||
var path = sysManager.GetEntitySystem<PathfindingSystem>();
|
||||
|
||||
NPCPath.Pressed = (debugSys.Tooltips & AiDebugMode.Paths) != 0x0;
|
||||
NPCThonk.Pressed = (debugSys.Tooltips & AiDebugMode.Thonk) != 0x0;
|
||||
PathCrumbs.Pressed = (path.Modes & PathfindingDebugMode.Breadcrumbs) != 0x0;
|
||||
PathPolys.Pressed = (path.Modes & PathfindingDebugMode.Polys) != 0x0;
|
||||
PathNeighbors.Pressed = (path.Modes & PathfindingDebugMode.PolyNeighbors) != 0x0;
|
||||
PathRouteCosts.Pressed = (path.Modes & PathfindingDebugMode.RouteCosts) != 0x0;
|
||||
PathRoutes.Pressed = (path.Modes & PathfindingDebugMode.Routes) != 0x0;
|
||||
|
||||
NPCPath.OnToggled += args => debugSys.ToggleTooltip(AiDebugMode.Paths);
|
||||
NPCThonk.OnToggled += args => debugSys.ToggleTooltip(AiDebugMode.Thonk);
|
||||
|
||||
PathNodes.Pressed = (path.Modes & PathfindingDebugMode.Nodes) != 0x0;
|
||||
PathRegions.Pressed = (path.Modes & PathfindingDebugMode.Regions) != 0x0;
|
||||
PathRoutes.Pressed = (path.Modes & PathfindingDebugMode.Route) != 0x0;
|
||||
|
||||
PathNodes.OnToggled += args => path.ToggleTooltip(PathfindingDebugMode.Nodes);
|
||||
PathRegions.OnToggled += args => path.ToggleTooltip(PathfindingDebugMode.Regions);
|
||||
PathRoutes.OnToggled += args => path.ToggleTooltip(PathfindingDebugMode.Route);
|
||||
PathCrumbs.OnToggled += args => path.Modes ^= PathfindingDebugMode.Breadcrumbs;
|
||||
PathPolys.OnToggled += args => path.Modes ^= PathfindingDebugMode.Polys;
|
||||
PathNeighbors.OnToggled += args => path.Modes ^= PathfindingDebugMode.PolyNeighbors;
|
||||
PathRouteCosts.OnToggled += args => path.Modes ^= PathfindingDebugMode.RouteCosts;
|
||||
PathRoutes.OnToggled += args => path.Modes ^= PathfindingDebugMode.Routes;
|
||||
}
|
||||
}
|
||||
|
||||
526
Content.Client/NPC/PathfindingSystem.cs
Normal file
526
Content.Client/NPC/PathfindingSystem.cs
Normal file
@@ -0,0 +1,526 @@
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Content.Shared.NPC;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.NPC
|
||||
{
|
||||
public sealed class PathfindingSystem : SharedPathfindingSystem
|
||||
{
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IResourceCache _cache = default!;
|
||||
|
||||
public PathfindingDebugMode Modes
|
||||
{
|
||||
get => _modes;
|
||||
set
|
||||
{
|
||||
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
||||
|
||||
if (value == PathfindingDebugMode.None)
|
||||
{
|
||||
Breadcrumbs.Clear();
|
||||
Polys.Clear();
|
||||
overlayManager.RemoveOverlay<PathfindingOverlay>();
|
||||
}
|
||||
else if (!overlayManager.HasOverlay<PathfindingOverlay>())
|
||||
{
|
||||
overlayManager.AddOverlay(new PathfindingOverlay(EntityManager, _eyeManager, _inputManager, _mapManager, _cache, this));
|
||||
}
|
||||
|
||||
_modes = value;
|
||||
|
||||
RaiseNetworkEvent(new RequestPathfindingDebugMessage()
|
||||
{
|
||||
Mode = _modes,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private PathfindingDebugMode _modes = PathfindingDebugMode.None;
|
||||
|
||||
// It's debug data IDC if it doesn't support snapshots I just want something fast.
|
||||
public Dictionary<EntityUid, Dictionary<Vector2i, List<PathfindingBreadcrumb>>> Breadcrumbs = new();
|
||||
public Dictionary<EntityUid, Dictionary<Vector2i, Dictionary<Vector2i, List<DebugPathPoly>>>> Polys = new();
|
||||
public readonly List<(TimeSpan Time, PathRouteMessage Message)> Routes = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeNetworkEvent<PathBreadcrumbsMessage>(OnBreadcrumbs);
|
||||
SubscribeNetworkEvent<PathBreadcrumbsRefreshMessage>(OnBreadcrumbsRefresh);
|
||||
SubscribeNetworkEvent<PathPolysMessage>(OnPolys);
|
||||
SubscribeNetworkEvent<PathPolysRefreshMessage>(OnPolysRefresh);
|
||||
SubscribeNetworkEvent<PathRouteMessage>(OnRoute);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
if (!_timing.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
for (var i = 0; i < Routes.Count; i++)
|
||||
{
|
||||
var route = Routes[i];
|
||||
|
||||
if (_timing.RealTime < route.Time)
|
||||
break;
|
||||
|
||||
Routes.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRoute(PathRouteMessage ev)
|
||||
{
|
||||
Routes.Add((_timing.RealTime + TimeSpan.FromSeconds(0.5), ev));
|
||||
}
|
||||
|
||||
private void OnPolys(PathPolysMessage ev)
|
||||
{
|
||||
Polys = ev.Polys;
|
||||
}
|
||||
|
||||
private void OnPolysRefresh(PathPolysRefreshMessage ev)
|
||||
{
|
||||
var chunks = Polys.GetOrNew(ev.GridUid);
|
||||
chunks[ev.Origin] = ev.Polys;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
// Don't send any messages to server, just shut down quietly.
|
||||
_modes = PathfindingDebugMode.None;
|
||||
}
|
||||
|
||||
private void OnBreadcrumbs(PathBreadcrumbsMessage ev)
|
||||
{
|
||||
Breadcrumbs = ev.Breadcrumbs;
|
||||
}
|
||||
|
||||
private void OnBreadcrumbsRefresh(PathBreadcrumbsRefreshMessage ev)
|
||||
{
|
||||
if (!Breadcrumbs.TryGetValue(ev.GridUid, out var chunks))
|
||||
return;
|
||||
|
||||
chunks[ev.Origin] = ev.Data;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class PathfindingOverlay : Overlay
|
||||
{
|
||||
private readonly IEntityManager _entManager;
|
||||
private readonly IEyeManager _eyeManager;
|
||||
private readonly IInputManager _inputManager;
|
||||
private readonly IMapManager _mapManager;
|
||||
private readonly PathfindingSystem _system;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.ScreenSpace | OverlaySpace.WorldSpace;
|
||||
|
||||
private readonly Font _font;
|
||||
|
||||
public PathfindingOverlay(
|
||||
IEntityManager entManager,
|
||||
IEyeManager eyeManager,
|
||||
IInputManager inputManager,
|
||||
IMapManager mapManager,
|
||||
IResourceCache cache,
|
||||
PathfindingSystem system)
|
||||
{
|
||||
_entManager = entManager;
|
||||
_eyeManager = eyeManager;
|
||||
_inputManager = inputManager;
|
||||
_mapManager = mapManager;
|
||||
_system = system;
|
||||
_font = new VectorFont(cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
switch (args.DrawingHandle)
|
||||
{
|
||||
case DrawingHandleScreen screenHandle:
|
||||
DrawScreen(args, screenHandle);
|
||||
break;
|
||||
case DrawingHandleWorld worldHandle:
|
||||
DrawWorld(args, worldHandle);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawScreen(OverlayDrawArgs args, DrawingHandleScreen screenHandle)
|
||||
{
|
||||
var mousePos = _inputManager.MouseScreenPosition;
|
||||
var mouseWorldPos = _eyeManager.ScreenToMap(mousePos);
|
||||
var aabb = new Box2(mouseWorldPos.Position - SharedPathfindingSystem.ChunkSize, mouseWorldPos.Position + SharedPathfindingSystem.ChunkSize);
|
||||
|
||||
if ((_system.Modes & PathfindingDebugMode.Crumb) != 0x0 &&
|
||||
mouseWorldPos.MapId == args.MapId)
|
||||
{
|
||||
var found = false;
|
||||
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mouseWorldPos.MapId, aabb))
|
||||
{
|
||||
if (found || !_system.Breadcrumbs.TryGetValue(grid.GridEntityId, out var crumbs))
|
||||
continue;
|
||||
|
||||
var localAABB = grid.InvWorldMatrix.TransformBox(aabb.Enlarged(float.Epsilon - SharedPathfindingSystem.ChunkSize));
|
||||
var worldMatrix = grid.WorldMatrix;
|
||||
|
||||
foreach (var chunk in crumbs)
|
||||
{
|
||||
if (found)
|
||||
continue;
|
||||
|
||||
var origin = chunk.Key * SharedPathfindingSystem.ChunkSize;
|
||||
|
||||
var chunkAABB = new Box2(origin, origin + SharedPathfindingSystem.ChunkSize);
|
||||
|
||||
if (!chunkAABB.Intersects(localAABB))
|
||||
continue;
|
||||
|
||||
PathfindingBreadcrumb? nearest = null;
|
||||
var nearestDistance = float.MaxValue;
|
||||
|
||||
foreach (var crumb in chunk.Value)
|
||||
{
|
||||
var crumbMapPos = worldMatrix.Transform(_system.GetCoordinate(chunk.Key, crumb.Coordinates));
|
||||
var distance = (crumbMapPos - mouseWorldPos.Position).Length;
|
||||
|
||||
if (distance < nearestDistance)
|
||||
{
|
||||
nearestDistance = distance;
|
||||
nearest = crumb;
|
||||
}
|
||||
}
|
||||
|
||||
if (nearest != null)
|
||||
{
|
||||
var text = new StringBuilder();
|
||||
|
||||
// Sandbox moment
|
||||
var coords = $"Point coordinates: {nearest.Value.Coordinates.ToString()}";
|
||||
var gridCoords =
|
||||
$"Grid coordinates: {_system.GetCoordinate(chunk.Key, nearest.Value.Coordinates).ToString()}";
|
||||
var layer = $"Layer: {nearest.Value.Data.CollisionLayer.ToString()}";
|
||||
var mask = $"Mask: {nearest.Value.Data.CollisionMask.ToString()}";
|
||||
|
||||
text.AppendLine(coords);
|
||||
text.AppendLine(gridCoords);
|
||||
text.AppendLine(layer);
|
||||
text.AppendLine(mask);
|
||||
text.AppendLine($"Flags:");
|
||||
|
||||
foreach (var flag in Enum.GetValues<PathfindingBreadcrumbFlag>())
|
||||
{
|
||||
if ((flag & nearest.Value.Data.Flags) == 0x0)
|
||||
continue;
|
||||
|
||||
var flagStr = $"- {flag.ToString()}";
|
||||
text.AppendLine(flagStr);
|
||||
}
|
||||
|
||||
screenHandle.DrawString(_font, mousePos.Position, text.ToString());
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((_system.Modes & PathfindingDebugMode.Poly) != 0x0 &&
|
||||
mouseWorldPos.MapId == args.MapId)
|
||||
{
|
||||
if (!_mapManager.TryFindGridAt(mouseWorldPos, out var grid))
|
||||
return;
|
||||
|
||||
var found = false;
|
||||
|
||||
if (!_system.Polys.TryGetValue(grid.GridEntityId, out var data))
|
||||
return;
|
||||
|
||||
var tileRef = grid.GetTileRef(mouseWorldPos);
|
||||
var localPos = tileRef.GridIndices;
|
||||
var chunkOrigin = localPos / SharedPathfindingSystem.ChunkSize;
|
||||
|
||||
if (!data.TryGetValue(chunkOrigin, out var chunk) ||
|
||||
!chunk.TryGetValue(new Vector2i(localPos.X % SharedPathfindingSystem.ChunkSize,
|
||||
localPos.Y % SharedPathfindingSystem.ChunkSize), out var tile))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var invGridMatrix = grid.InvWorldMatrix;
|
||||
DebugPathPoly? nearest = null;
|
||||
var nearestDistance = float.MaxValue;
|
||||
|
||||
foreach (var poly in tile)
|
||||
{
|
||||
if (poly.Box.Contains(invGridMatrix.Transform(mouseWorldPos.Position)))
|
||||
{
|
||||
nearest = poly;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (nearest != null)
|
||||
{
|
||||
var text = new StringBuilder();
|
||||
/*
|
||||
|
||||
// Sandbox moment
|
||||
var coords = $"Point coordinates: {nearest.Value.Coordinates.ToString()}";
|
||||
var gridCoords =
|
||||
$"Grid coordinates: {_system.GetCoordinate(chunk.Key, nearest.Value.Coordinates).ToString()}";
|
||||
var layer = $"Layer: {nearest.Value.Data.CollisionLayer.ToString()}";
|
||||
var mask = $"Mask: {nearest.Value.Data.CollisionMask.ToString()}";
|
||||
|
||||
text.AppendLine(coords);
|
||||
text.AppendLine(gridCoords);
|
||||
text.AppendLine(layer);
|
||||
text.AppendLine(mask);
|
||||
text.AppendLine($"Flags:");
|
||||
|
||||
foreach (var flag in Enum.GetValues<PathfindingBreadcrumbFlag>())
|
||||
{
|
||||
if ((flag & nearest.Value.Data.Flags) == 0x0)
|
||||
continue;
|
||||
|
||||
var flagStr = $"- {flag.ToString()}";
|
||||
text.AppendLine(flagStr);
|
||||
}
|
||||
|
||||
foreach (var neighbor in )
|
||||
|
||||
screenHandle.DrawString(_font, mousePos.Position, text.ToString());
|
||||
found = true;
|
||||
break;
|
||||
*/
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawWorld(OverlayDrawArgs args, DrawingHandleWorld worldHandle)
|
||||
{
|
||||
var mousePos = _inputManager.MouseScreenPosition;
|
||||
var mouseWorldPos = _eyeManager.ScreenToMap(mousePos);
|
||||
var aabb = new Box2(mouseWorldPos.Position - Vector2.One / 4f, mouseWorldPos.Position + Vector2.One / 4f);
|
||||
|
||||
if ((_system.Modes & PathfindingDebugMode.Breadcrumbs) != 0x0 &&
|
||||
mouseWorldPos.MapId == args.MapId)
|
||||
{
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mouseWorldPos.MapId, aabb))
|
||||
{
|
||||
if (!_system.Breadcrumbs.TryGetValue(grid.GridEntityId, out var crumbs))
|
||||
continue;
|
||||
|
||||
worldHandle.SetTransform(grid.WorldMatrix);
|
||||
var localAABB = grid.InvWorldMatrix.TransformBox(aabb);
|
||||
|
||||
foreach (var chunk in crumbs)
|
||||
{
|
||||
var origin = chunk.Key * SharedPathfindingSystem.ChunkSize;
|
||||
|
||||
var chunkAABB = new Box2(origin, origin + SharedPathfindingSystem.ChunkSize);
|
||||
|
||||
if (!chunkAABB.Intersects(localAABB))
|
||||
continue;
|
||||
|
||||
foreach (var crumb in chunk.Value)
|
||||
{
|
||||
if (crumb.Equals(PathfindingBreadcrumb.Invalid))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const float edge = 1f / SharedPathfindingSystem.SubStep / 4f;
|
||||
|
||||
var masked = crumb.Data.CollisionMask != 0 || crumb.Data.CollisionLayer != 0;
|
||||
Color color;
|
||||
|
||||
if ((crumb.Data.Flags & PathfindingBreadcrumbFlag.Space) != 0x0)
|
||||
{
|
||||
color = Color.Green;
|
||||
}
|
||||
else if (masked)
|
||||
{
|
||||
color = Color.Blue;
|
||||
}
|
||||
else
|
||||
{
|
||||
color = Color.Orange;
|
||||
}
|
||||
|
||||
var coordinate = _system.GetCoordinate(chunk.Key, crumb.Coordinates);
|
||||
worldHandle.DrawRect(new Box2(coordinate - edge, coordinate + edge), color.WithAlpha(0.25f));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((_system.Modes & PathfindingDebugMode.Polys) != 0x0 &&
|
||||
mouseWorldPos.MapId == args.MapId)
|
||||
{
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(args.MapId, aabb))
|
||||
{
|
||||
if (!_system.Polys.TryGetValue(grid.GridEntityId, out var data))
|
||||
continue;
|
||||
|
||||
worldHandle.SetTransform(grid.WorldMatrix);
|
||||
var localAABB = grid.InvWorldMatrix.TransformBox(aabb);
|
||||
|
||||
foreach (var chunk in data)
|
||||
{
|
||||
var origin = chunk.Key * SharedPathfindingSystem.ChunkSize;
|
||||
|
||||
var chunkAABB = new Box2(origin, origin + SharedPathfindingSystem.ChunkSize);
|
||||
|
||||
if (!chunkAABB.Intersects(localAABB))
|
||||
continue;
|
||||
|
||||
foreach (var tile in chunk.Value)
|
||||
{
|
||||
foreach (var poly in tile.Value)
|
||||
{
|
||||
worldHandle.DrawRect(poly.Box, Color.Green.WithAlpha(0.25f));
|
||||
worldHandle.DrawRect(poly.Box, Color.Red, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((_system.Modes & PathfindingDebugMode.PolyNeighbors) != 0x0 &&
|
||||
mouseWorldPos.MapId == args.MapId)
|
||||
{
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(args.MapId, aabb))
|
||||
{
|
||||
if (!_system.Polys.TryGetValue(grid.GridEntityId, out var data) ||
|
||||
!_entManager.TryGetComponent<TransformComponent>(grid.GridEntityId, out var gridXform))
|
||||
continue;
|
||||
|
||||
var (_, _, worldMatrix, invMatrix) = gridXform.GetWorldPositionRotationMatrixWithInv();
|
||||
worldHandle.SetTransform(worldMatrix);
|
||||
var localAABB = invMatrix.TransformBox(aabb);
|
||||
|
||||
foreach (var chunk in data)
|
||||
{
|
||||
var origin = chunk.Key * SharedPathfindingSystem.ChunkSize;
|
||||
|
||||
var chunkAABB = new Box2(origin, origin + SharedPathfindingSystem.ChunkSize);
|
||||
|
||||
if (!chunkAABB.Intersects(localAABB))
|
||||
continue;
|
||||
|
||||
foreach (var tile in chunk.Value)
|
||||
{
|
||||
foreach (var poly in tile.Value)
|
||||
{
|
||||
foreach (var neighborPoly in poly.Neighbors)
|
||||
{
|
||||
Color color;
|
||||
Vector2 neighborPos;
|
||||
|
||||
if (neighborPoly.EntityId != poly.GraphUid)
|
||||
{
|
||||
color = Color.Green;
|
||||
var neighborMap = neighborPoly.ToMap(_entManager);
|
||||
|
||||
if (neighborMap.MapId != args.MapId)
|
||||
continue;
|
||||
|
||||
neighborPos = invMatrix.Transform(neighborMap.Position);
|
||||
}
|
||||
else
|
||||
{
|
||||
color = Color.Blue;
|
||||
neighborPos = neighborPoly.Position;
|
||||
}
|
||||
|
||||
worldHandle.DrawLine(poly.Box.Center, neighborPos, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((_system.Modes & PathfindingDebugMode.Chunks) != 0x0)
|
||||
{
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(args.MapId, args.WorldBounds))
|
||||
{
|
||||
if (!_system.Breadcrumbs.TryGetValue(grid.GridEntityId, out var crumbs))
|
||||
continue;
|
||||
|
||||
worldHandle.SetTransform(grid.WorldMatrix);
|
||||
var localAABB = grid.InvWorldMatrix.TransformBox(args.WorldBounds);
|
||||
|
||||
foreach (var chunk in crumbs)
|
||||
{
|
||||
var origin = chunk.Key * SharedPathfindingSystem.ChunkSize;
|
||||
|
||||
var chunkAABB = new Box2(origin, origin + SharedPathfindingSystem.ChunkSize);
|
||||
|
||||
if (!chunkAABB.Intersects(localAABB))
|
||||
continue;
|
||||
|
||||
worldHandle.DrawRect(chunkAABB, Color.Red, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((_system.Modes & PathfindingDebugMode.Routes) != 0x0)
|
||||
{
|
||||
foreach (var route in _system.Routes)
|
||||
{
|
||||
foreach (var node in route.Message.Path)
|
||||
{
|
||||
if (!_entManager.TryGetComponent<TransformComponent>(node.GraphUid, out var graphXform))
|
||||
continue;
|
||||
|
||||
worldHandle.SetTransform(graphXform.WorldMatrix);
|
||||
worldHandle.DrawRect(node.Box, Color.Orange.WithAlpha(0.10f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((_system.Modes & PathfindingDebugMode.RouteCosts) != 0x0)
|
||||
{
|
||||
var matrix = EntityUid.Invalid;
|
||||
|
||||
foreach (var route in _system.Routes)
|
||||
{
|
||||
var highestGScore = route.Message.Costs.Values.Max();
|
||||
|
||||
foreach (var (node, cost) in route.Message.Costs)
|
||||
{
|
||||
if (matrix != node.GraphUid)
|
||||
{
|
||||
if (!_entManager.TryGetComponent<TransformComponent>(node.GraphUid, out var graphXform))
|
||||
continue;
|
||||
|
||||
matrix = node.GraphUid;
|
||||
worldHandle.SetTransform(graphXform.WorldMatrix);
|
||||
}
|
||||
|
||||
worldHandle.DrawRect(node.Box, new Color(0f, cost / highestGScore, 1f - (cost / highestGScore), 0.10f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
worldHandle.SetTransform(Matrix3.Identity);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user