From f456ad911e62fe8b20a90f82cd16b3c634fb8391 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Fri, 30 Sep 2022 14:39:48 +1000 Subject: [PATCH] Pathfinder rework (#11452) --- Content.Client/Commands/DebugAiCommand.cs | 61 -- .../Commands/DebugPathfindingCommand.cs | 81 +- Content.Client/NPC/ClientAiDebugSystem.cs | 197 ---- .../NPC/ClientPathfindingDebugSystem.cs | 520 ---------- Content.Client/NPC/NPCWindow.xaml | 6 +- Content.Client/NPC/NPCWindow.xaml.cs | 26 +- Content.Client/NPC/PathfindingSystem.cs | 526 ++++++++++ .../JobQueues/Queues/AiActionJobQueue.cs | 4 - Content.Server/Doors/Systems/DoorSystem.cs | 4 +- .../NPC/Components/NPCMeleeCombatComponent.cs | 5 + .../NPC/Components/NPCSteeringComponent.cs | 25 +- Content.Server/NPC/HTN/HTNComponent.cs | 2 + Content.Server/NPC/HTN/HTNPlanJob.cs | 2 +- Content.Server/NPC/HTN/HTNSystem.cs | 3 +- .../NPC/HTN/PrimitiveTasks/HTNOperator.cs | 8 +- .../Operators/Melee/MeleeOperator.cs | 6 +- .../Melee/PickMeleeTargetOperator.cs | 20 +- .../Operators/MoveToOperator.cs | 50 +- .../Operators/NPCCombatOperator.cs | 68 +- .../PickAccessibleComponentOperator.cs | 46 +- .../Operators/PickAccessibleOperator.cs | 47 +- .../Operators/PickRandomRotationOperator.cs | 4 +- .../Operators/RandomOperator.cs | 4 +- .../Ranged/PickRangedTargetOperator.cs | 25 +- .../Operators/Ranged/RangedOperator.cs | 4 +- .../Operators/SetFloatOperator.cs | 4 +- .../Specific/PickNearbyInjectableOperator.cs | 4 +- .../Test/PickPathfindPointOperator.cs | 4 +- Content.Server/NPC/NPCBlackboard.cs | 10 + .../Accessible/AiReachableSystem.cs | 789 --------------- .../Pathfinding/Accessible/BFSPathfinder.cs | 67 -- .../Accessible/PathfindingRegion.cs | 138 --- .../Pathfinding/Accessible/ReachableArgs.cs | 39 - .../NPC/Pathfinding/GridPathfindingChunk.cs | 33 + .../Pathfinding/GridPathfindingComponent.cs | 22 +- Content.Server/NPC/Pathfinding/PathFlags.cs | 22 + Content.Server/NPC/Pathfinding/PathPoly.cs | 64 ++ Content.Server/NPC/Pathfinding/PathPortal.cs | 30 + Content.Server/NPC/Pathfinding/PathRequest.cs | 110 +++ Content.Server/NPC/Pathfinding/PathResult.cs | 9 + .../Pathfinders/AStarPathfindingJob.cs | 172 ---- .../Pathfinders/JpsPathfindingJob.cs | 512 ---------- .../Pathfinders/PathfindingArgs.cs | 43 - .../Pathfinders/PathfindingComparer.cs | 10 - .../NPC/Pathfinding/PathfindingChunk.cs | 185 ---- .../NPC/Pathfinding/PathfindingHelpers.cs | 350 ------- .../NPC/Pathfinding/PathfindingNode.cs | 317 ------ .../Pathfinding/PathfindingRequestEvent.cs | 11 + .../Pathfinding/PathfindingSystem.AStar.cs | 143 +++ .../NPC/Pathfinding/PathfindingSystem.BFS.cs | 121 +++ .../Pathfinding/PathfindingSystem.Common.cs | 151 +++ .../Pathfinding/PathfindingSystem.Distance.cs | 46 + .../NPC/Pathfinding/PathfindingSystem.Grid.cs | 928 +++++++++++++----- .../PathfindingSystem.Simplifier.cs | 53 - .../NPC/Pathfinding/PathfindingSystem.cs | 696 ++++++++++++- .../ServerPathfindingDebugSystem.cs | 92 -- .../NPC/Systems/NPCCombatSystem.Melee.cs | 31 +- Content.Server/NPC/Systems/NPCCombatSystem.cs | 2 +- .../Systems/NPCSteeringSystem.Avoidance.cs | 6 - .../Systems/NPCSteeringSystem.Obstacles.cs | 157 +++ .../NPC/Systems/NPCSteeringSystem.cs | 344 ++++--- .../Shuttles/Components/DockingComponent.cs | 3 + .../Shuttles/Systems/DockingSystem.cs | 9 + Content.Shared/AI/SharedAiDebug.cs | 181 ---- Content.Shared/CCVar/CCVars.cs | 2 +- Content.Shared/NPC/{ => Events}/HTNMessage.cs | 0 .../NPC/Events/PathBreadcrumbsMessage.cs | 23 + .../NPC/Events/PathPolysRefreshMessage.cs | 15 + Content.Shared/NPC/Events/PathRouteMessage.cs | 19 + .../Events/RequestPathfindingDebugMessage.cs | 9 + Content.Shared/NPC/PathPoly.cs | 32 + Content.Shared/NPC/PathfindingBoundary.cs | 23 + Content.Shared/NPC/PathfindingBreadcrumb.cs | 118 +++ Content.Shared/NPC/PathfindingDebugMode.cs | 46 + Content.Shared/NPC/SharedPathfindingSystem.cs | 22 + .../Weapons/Melee/SharedMeleeWeaponSystem.cs | 1 + Resources/Locale/en-US/doors/door.ftl | 1 + .../Prototypes/Entities/Mobs/NPCs/xeno.yml | 13 +- .../Doors/Windoors/base_structurewindoors.yml | 2 +- .../Entities/Structures/Windows/window.yml | 2 +- 80 files changed, 3606 insertions(+), 4374 deletions(-) delete mode 100644 Content.Client/Commands/DebugAiCommand.cs delete mode 100644 Content.Client/NPC/ClientAiDebugSystem.cs delete mode 100644 Content.Client/NPC/ClientPathfindingDebugSystem.cs create mode 100644 Content.Client/NPC/PathfindingSystem.cs delete mode 100644 Content.Server/CPUJob/JobQueues/Queues/AiActionJobQueue.cs delete mode 100644 Content.Server/NPC/Pathfinding/Accessible/AiReachableSystem.cs delete mode 100644 Content.Server/NPC/Pathfinding/Accessible/BFSPathfinder.cs delete mode 100644 Content.Server/NPC/Pathfinding/Accessible/PathfindingRegion.cs delete mode 100644 Content.Server/NPC/Pathfinding/Accessible/ReachableArgs.cs create mode 100644 Content.Server/NPC/Pathfinding/GridPathfindingChunk.cs create mode 100644 Content.Server/NPC/Pathfinding/PathFlags.cs create mode 100644 Content.Server/NPC/Pathfinding/PathPoly.cs create mode 100644 Content.Server/NPC/Pathfinding/PathPortal.cs create mode 100644 Content.Server/NPC/Pathfinding/PathRequest.cs create mode 100644 Content.Server/NPC/Pathfinding/PathResult.cs delete mode 100644 Content.Server/NPC/Pathfinding/Pathfinders/AStarPathfindingJob.cs delete mode 100644 Content.Server/NPC/Pathfinding/Pathfinders/JpsPathfindingJob.cs delete mode 100644 Content.Server/NPC/Pathfinding/Pathfinders/PathfindingArgs.cs delete mode 100644 Content.Server/NPC/Pathfinding/Pathfinders/PathfindingComparer.cs delete mode 100644 Content.Server/NPC/Pathfinding/PathfindingChunk.cs delete mode 100644 Content.Server/NPC/Pathfinding/PathfindingHelpers.cs delete mode 100644 Content.Server/NPC/Pathfinding/PathfindingNode.cs create mode 100644 Content.Server/NPC/Pathfinding/PathfindingRequestEvent.cs create mode 100644 Content.Server/NPC/Pathfinding/PathfindingSystem.AStar.cs create mode 100644 Content.Server/NPC/Pathfinding/PathfindingSystem.BFS.cs create mode 100644 Content.Server/NPC/Pathfinding/PathfindingSystem.Common.cs create mode 100644 Content.Server/NPC/Pathfinding/PathfindingSystem.Distance.cs delete mode 100644 Content.Server/NPC/Pathfinding/PathfindingSystem.Simplifier.cs delete mode 100644 Content.Server/NPC/Pathfinding/ServerPathfindingDebugSystem.cs create mode 100644 Content.Server/NPC/Systems/NPCSteeringSystem.Obstacles.cs delete mode 100644 Content.Shared/AI/SharedAiDebug.cs rename Content.Shared/NPC/{ => Events}/HTNMessage.cs (100%) create mode 100644 Content.Shared/NPC/Events/PathBreadcrumbsMessage.cs create mode 100644 Content.Shared/NPC/Events/PathPolysRefreshMessage.cs create mode 100644 Content.Shared/NPC/Events/PathRouteMessage.cs create mode 100644 Content.Shared/NPC/Events/RequestPathfindingDebugMessage.cs create mode 100644 Content.Shared/NPC/PathPoly.cs create mode 100644 Content.Shared/NPC/PathfindingBoundary.cs create mode 100644 Content.Shared/NPC/PathfindingBreadcrumb.cs create mode 100644 Content.Shared/NPC/PathfindingDebugMode.cs create mode 100644 Content.Shared/NPC/SharedPathfindingSystem.cs create mode 100644 Resources/Locale/en-US/doors/door.ftl diff --git a/Content.Client/Commands/DebugAiCommand.cs b/Content.Client/Commands/DebugAiCommand.cs deleted file mode 100644 index e35dd21387..0000000000 --- a/Content.Client/Commands/DebugAiCommand.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Content.Client.NPC; -using JetBrains.Annotations; -using Robust.Shared.Console; -using Robust.Shared.GameObjects; - -namespace Content.Client.Commands -{ - /// - /// This is used to handle the tooltips above AI mobs - /// - [UsedImplicitly] - internal sealed class DebugAiCommand : IConsoleCommand - { - // ReSharper disable once StringLiteralTypo - public string Command => "debugai"; - public string Description => "Handles all tooltip debugging above AI mobs"; - public string Help => "debugai [hide/paths/thonk]"; - - public void Execute(IConsoleShell shell, string argStr, string[] args) - { -#if DEBUG - if (args.Length < 1) - { - shell.RemoteExecuteCommand(argStr); - return; - } - - var anyAction = false; - var debugSystem = EntitySystem.Get(); - - foreach (var arg in args) - { - switch (arg) - { - case "hide": - debugSystem.Disable(); - anyAction = true; - break; - // This will show the pathfinding numbers above the mob's head - case "paths": - debugSystem.ToggleTooltip(AiDebugMode.Paths); - anyAction = true; - break; - // Shows stats on what the AI was thinking. - case "thonk": - debugSystem.ToggleTooltip(AiDebugMode.Thonk); - anyAction = true; - break; - default: - continue; - } - } - - if(!anyAction) - shell.RemoteExecuteCommand(argStr); -#else - shell.RemoteExecuteCommand(argStr); -#endif - } - } -} diff --git a/Content.Client/Commands/DebugPathfindingCommand.cs b/Content.Client/Commands/DebugPathfindingCommand.cs index 69738559df..9071ea40a7 100644 --- a/Content.Client/Commands/DebugPathfindingCommand.cs +++ b/Content.Client/Commands/DebugPathfindingCommand.cs @@ -1,74 +1,61 @@ +using System.Linq; using Content.Client.NPC; +using Content.Shared.NPC; using JetBrains.Annotations; using Robust.Shared.Console; -using Robust.Shared.GameObjects; namespace Content.Client.Commands { [UsedImplicitly] - internal sealed class DebugPathfindingCommand : IConsoleCommand + public sealed class DebugPathfindingCommand : IConsoleCommand { // ReSharper disable once StringLiteralTypo public string Command => "pathfinder"; public string Description => "Toggles visibility of pathfinding debuggers."; - public string Help => "pathfinder [hide/nodes/routes/graph/regioncache/regions]"; + public string Help => "pathfinder [options]"; public void Execute(IConsoleShell shell, string argStr, string[] args) { -#if DEBUG - if (args.Length < 1) + var system = IoCManager.Resolve().GetEntitySystem(); + + if (args.Length == 0) { - shell.RemoteExecuteCommand(argStr); + system.Modes = PathfindingDebugMode.None; return; } - var anyAction = false; - var debugSystem = EntitySystem.Get(); - foreach (var arg in args) { - switch (arg) + if (!Enum.TryParse(arg, out var mode)) { - case "hide": - debugSystem.Disable(); - anyAction = true; - break; - // Shows all nodes on the closed list - case "nodes": - debugSystem.ToggleTooltip(PathfindingDebugMode.Nodes); - anyAction = true; - break; - // Will show just the constructed route - case "routes": - debugSystem.ToggleTooltip(PathfindingDebugMode.Route); - anyAction = true; - break; - // Shows all of the pathfinding chunks - case "graph": - debugSystem.ToggleTooltip(PathfindingDebugMode.Graph); - anyAction = true; - break; - // Shows every time the cached reachable regions are hit (whether cached already or not) - case "regioncache": - debugSystem.ToggleTooltip(PathfindingDebugMode.CachedRegions); - anyAction = true; - break; - // Shows all of the regions in each chunk - case "regions": - debugSystem.ToggleTooltip(PathfindingDebugMode.Regions); - anyAction = true; - break; - - default: - continue; + shell.WriteError($"Unrecognised pathfinder args {arg}"); + continue; } + + system.Modes ^= mode; + shell.WriteLine($"Toggled {arg} to {(system.Modes & mode) != 0x0}"); + } + } + + public CompletionResult GetCompletion(IConsoleShell shell, string[] args) + { + if (args.Length > 1) + { + return CompletionResult.Empty; } - if(!anyAction) - shell.RemoteExecuteCommand(argStr); -#else - shell.RemoteExecuteCommand(argStr); -#endif + var values = Enum.GetValues().ToList(); + var options = new List(); + + foreach (var val in values) + { + if (val == PathfindingDebugMode.None) + continue; + + options.Add(new CompletionOption(val.ToString())); + } + + return CompletionResult.FromOptions(options); } } } diff --git a/Content.Client/NPC/ClientAiDebugSystem.cs b/Content.Client/NPC/ClientAiDebugSystem.cs deleted file mode 100644 index 5f807d1398..0000000000 --- a/Content.Client/NPC/ClientAiDebugSystem.cs +++ /dev/null @@ -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 _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(0); - foreach (var (entity, panel) in _aiBoxes) - { - if (Deleted(entity)) - { - deletedEntities.Add(entity); - continue; - } - - if (!_eyeManager.GetWorldViewport().Contains(EntityManager.GetComponent(entity).WorldPosition)) - { - panel.Visible = false; - continue; - } - - var (x, y) = _eyeManager.CoordinatesToScreen(EntityManager.GetComponent(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(HandleUtilityAiDebugMessage); - SubscribeNetworkEvent(HandleAStarRouteMessage); - SubscribeNetworkEvent(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(); - - 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, - } -} diff --git a/Content.Client/NPC/ClientPathfindingDebugSystem.cs b/Content.Client/NPC/ClientPathfindingDebugSystem.cs deleted file mode 100644 index 7c1de6105e..0000000000 --- a/Content.Client/NPC/ClientPathfindingDebugSystem.cs +++ /dev/null @@ -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(HandleAStarRouteMessage); - SubscribeNetworkEvent(HandleJpsRouteMessage); - SubscribeNetworkEvent(HandleGraphMessage); - SubscribeNetworkEvent(HandleRegionsMessage); - SubscribeNetworkEvent(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(); - _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(); - 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> Graph = new(); - private readonly Dictionary _graphColors = new(); - - // Cached regions - public readonly Dictionary>> CachedRegions = - new(); - - private readonly Dictionary> _cachedRegionColors = - new(); - - // Regions - public readonly Dictionary>>> Regions = - new(); - - private readonly Dictionary>> _regionColors = - new(); - - // Route debugging - // As each pathfinder is very different you'll likely want to draw them completely different - public readonly List AStarRoutes = new(); - public readonly List JpsRoutes = new(); - - public DebugPathfindingOverlay(IEntityManager entities, IEyeManager eyeManager, IPlayerManager playerManager, IPrototypeManager prototypeManager) - { - _entities = entities; - _eyeManager = eyeManager; - _playerManager = playerManager; - _shader = prototypeManager.Index("unshaded").Instance(); - } - - #region Graph - public void UpdateGraph(Dictionary> graph) - { - Graph.Clear(); - _graphColors.Clear(); - var robustRandom = IoCManager.Resolve(); - 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> messageRegions, bool cached) - { - if (!CachedRegions.ContainsKey(gridId)) - { - CachedRegions.Add(gridId, new Dictionary>()); - _cachedRegionColors.Add(gridId, new Dictionary()); - } - - 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(_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>> messageRegions) - { - if (!Regions.ContainsKey(gridId)) - { - Regions.Add(gridId, new Dictionary>>()); - _regionColors.Add(gridId, new Dictionary>()); - } - - var robustRandom = IoCManager.Resolve(); - foreach (var (chunk, regions) in messageRegions) - { - Regions[gridId][chunk] = new Dictionary>(); - _regionColors[gridId][chunk] = new Dictionary(); - - 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, - } -} diff --git a/Content.Client/NPC/NPCWindow.xaml b/Content.Client/NPC/NPCWindow.xaml index 29a2a75737..664f15600c 100644 --- a/Content.Client/NPC/NPCWindow.xaml +++ b/Content.Client/NPC/NPCWindow.xaml @@ -15,9 +15,11 @@