using System; using System.Collections.Generic; using Content.Shared.AI; using Robust.Client.Interfaces.Graphics.ClientEye; using Robust.Client.Interfaces.UserInterface; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Maths; namespace Content.Client.GameObjects.EntitySystems.AI { #if DEBUG public class ClientAiDebugSystem : EntitySystem { #pragma warning disable 649 [Dependency] private IEyeManager _eyeManager; #pragma restore disable 649 private AiDebugMode _tooltips = AiDebugMode.None; private readonly Dictionary _aiBoxes = new Dictionary(); 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 (entity.Deleted) { deletedEntities.Add(entity); continue; } if (!_eyeManager.GetWorldViewport().Contains(entity.Transform.WorldPosition)) { panel.Visible = false; continue; } var (x, y) = _eyeManager.WorldToScreen(entity.Transform.GridPosition).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(); 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 entityManager = IoCManager.Resolve(); var entity = entityManager.GetEntity(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 entityManager = IoCManager.Resolve(); var entity = entityManager.GetEntity(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 entityManager = IoCManager.Resolve(); var entity = entityManager.GetEntity(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; } private void EnableTooltip(AiDebugMode tooltip) { _tooltips |= tooltip; } private void DisableTooltip(AiDebugMode tooltip) { _tooltips &= ~tooltip; } public void ToggleTooltip(AiDebugMode tooltip) { if ((_tooltips & tooltip) != 0) { DisableTooltip(tooltip); } else { EnableTooltip(tooltip); } } private bool TryCreatePanel(IEntity 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 VBoxContainer() { SeparationOverride = 15, Children = {actionLabel, pathfindingLabel}, }; var panel = new PanelContainer { StyleClasses = {"tooltipBox"}, 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, } #endif }