NPC refactor (#10122)

Co-authored-by: metalgearsloth <metalgearsloth@gmail.com>
This commit is contained in:
metalgearsloth
2022-09-06 00:28:23 +10:00
committed by GitHub
parent 138e328c04
commit 0286b88388
290 changed files with 13842 additions and 5939 deletions

View File

@@ -0,0 +1,197 @@
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,
}
}

View File

@@ -0,0 +1,520 @@
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,
}
}

View File

@@ -0,0 +1,7 @@
namespace Content.Client.NPC.HTN;
[RegisterComponent]
public sealed class HTNComponent : NPCComponent
{
public string DebugText = string.Empty;
}

View File

@@ -0,0 +1,41 @@
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;
namespace Content.Client.NPC.HTN;
public sealed class HTNOverlay : Overlay
{
private readonly IEntityManager _entManager = default!;
private readonly Font _font = default!;
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
public HTNOverlay(IEntityManager entManager, IResourceCache resourceCache)
{
_entManager = entManager;
_font = new VectorFont(resourceCache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
}
protected override void Draw(in OverlayDrawArgs args)
{
if (args.ViewportControl == null)
return;
var handle = args.ScreenHandle;
foreach (var (comp, xform) in _entManager.EntityQuery<HTNComponent, TransformComponent>(true))
{
if (string.IsNullOrEmpty(comp.DebugText) || xform.MapID != args.MapId)
continue;
var worldPos = xform.WorldPosition;
if (!args.WorldAABB.Contains(worldPos))
continue;
var screenPos = args.ViewportControl.WorldToScreen(worldPos);
handle.DrawString(_font, screenPos + new Vector2(0, 10f), comp.DebugText);
}
}
}

View File

@@ -0,0 +1,54 @@
using Content.Shared.NPC;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
namespace Content.Client.NPC.HTN;
public sealed class HTNSystem : EntitySystem
{
/*
* Mainly handles clientside debugging for HTN NPCs.
*/
public bool EnableOverlay
{
get => _enableOverlay;
set
{
var overlayManager = IoCManager.Resolve<IOverlayManager>();
_enableOverlay = value;
if (_enableOverlay)
{
overlayManager.AddOverlay(new HTNOverlay(EntityManager, IoCManager.Resolve<IResourceCache>()));
RaiseNetworkEvent(new RequestHTNMessage()
{
Enabled = true,
});
}
else
{
overlayManager.RemoveOverlay<HTNOverlay>();
RaiseNetworkEvent(new RequestHTNMessage()
{
Enabled = false,
});
}
}
}
private bool _enableOverlay;
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<HTNMessage>(OnHTNMessage);
}
private void OnHTNMessage(HTNMessage ev)
{
if (!TryComp<HTNComponent>(ev.Uid, out var htn))
return;
htn.DebugText = ev.Text;
}
}

View File

@@ -0,0 +1,8 @@
using Content.Shared.NPC;
namespace Content.Client.NPC;
public abstract class NPCComponent : SharedNPCComponent
{
}

View File

@@ -0,0 +1,22 @@
using Content.Client.Eui;
namespace Content.Client.NPC;
public sealed class NPCEui : BaseEui
{
private NPCWindow? _window = new();
public override void Opened()
{
base.Opened();
_window = new NPCWindow();
_window.OpenCentered();
}
public override void Closed()
{
base.Closed();
_window?.Close();
_window = null;
}
}

View File

@@ -0,0 +1,24 @@
<userInterface:FancyWindow xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="clr-namespace:Content.Client.HUD.UI"
xmlns:userInterface="clr-namespace:Content.Client.UserInterface"
Title="NPC debug"
MinSize="200 200">
<BoxContainer Name="Options" Orientation="Vertical">
<ui:StripeBack>
<Label Text="NPC" HorizontalAlignment="Center"/>
</ui:StripeBack>
<BoxContainer Name="NPCBox" Orientation="Vertical">
<CheckBox Name="NPCPath" Text="Path"/>
<CheckBox Name="NPCThonk" Text="Thonk"/>
</BoxContainer>
<ui:StripeBack>
<Label Text="Pathfinder" HorizontalAlignment="Center"/>
</ui:StripeBack>
<BoxContainer Name="PathfinderBox" Orientation="Vertical">
<CheckBox Name="PathNodes" Text="Nodes"/>
<CheckBox Name="PathRoutes" Text="Routes"/>
<CheckBox Name="PathRegions" Text="Regions"/>
</BoxContainer>
</BoxContainer>
</userInterface:FancyWindow>

View File

@@ -0,0 +1,33 @@
using Content.Client.UserInterface;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Console;
namespace Content.Client.NPC;
[GenerateTypedNameReferences]
public sealed partial class NPCWindow : FancyWindow
{
public NPCWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
var sysManager = IoCManager.Resolve<IEntitySystemManager>();
var debugSys = sysManager.GetEntitySystem<ClientAiDebugSystem>();
var path = sysManager.GetEntitySystem<ClientPathfindingDebugSystem>();
NPCPath.Pressed = (debugSys.Tooltips & AiDebugMode.Paths) != 0x0;
NPCThonk.Pressed = (debugSys.Tooltips & AiDebugMode.Thonk) != 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);
}
}

View File

@@ -0,0 +1,16 @@
using Content.Client.NPC.HTN;
using Robust.Shared.Console;
namespace Content.Client.NPC;
public sealed class ShowHTNCommand : IConsoleCommand
{
public string Command => "showhtn";
public string Description => "Shows the current status for HTN NPCs";
public string Help => $"{Command}";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var npcs = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<HTNSystem>();
npcs.EnableOverlay ^= true;
}
}