1
0

AtmosDebugOverlay.cs 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. using System.Globalization;
  2. using System.Linq;
  3. using System.Numerics;
  4. using Content.Client.Atmos.EntitySystems;
  5. using Content.Client.Resources;
  6. using Content.Shared.Atmos;
  7. using Robust.Client.Graphics;
  8. using Robust.Client.Input;
  9. using Robust.Client.ResourceManagement;
  10. using Robust.Client.UserInterface;
  11. using Robust.Client.UserInterface.CustomControls;
  12. using Robust.Shared.Enums;
  13. using Robust.Shared.Map;
  14. using Robust.Shared.Map.Components;
  15. using AtmosDebugOverlayData = Content.Shared.Atmos.EntitySystems.SharedAtmosDebugOverlaySystem.AtmosDebugOverlayData;
  16. using DebugMessage = Content.Shared.Atmos.EntitySystems.SharedAtmosDebugOverlaySystem.AtmosDebugOverlayMessage;
  17. namespace Content.Client.Atmos.Overlays;
  18. public sealed class AtmosDebugOverlay : Overlay
  19. {
  20. [Dependency] private readonly IEntityManager _entManager = default!;
  21. [Dependency] private readonly IMapManager _mapManager = default!;
  22. [Dependency] private readonly IInputManager _input = default!;
  23. [Dependency] private readonly IUserInterfaceManager _ui = default!;
  24. [Dependency] private readonly IResourceCache _cache = default!;
  25. private readonly SharedTransformSystem _transform;
  26. private readonly AtmosDebugOverlaySystem _system;
  27. private readonly SharedMapSystem _map;
  28. private readonly Font _font;
  29. private List<(Entity<MapGridComponent>, DebugMessage)> _grids = new();
  30. public override OverlaySpace Space => OverlaySpace.WorldSpace | OverlaySpace.ScreenSpace;
  31. internal AtmosDebugOverlay(AtmosDebugOverlaySystem system)
  32. {
  33. IoCManager.InjectDependencies(this);
  34. _system = system;
  35. _transform = _entManager.System<SharedTransformSystem>();
  36. _map = _entManager.System<SharedMapSystem>();
  37. _font = _cache.GetFont("/Fonts/NotoSans/NotoSans-Regular.ttf", 12);
  38. }
  39. protected override void Draw(in OverlayDrawArgs args)
  40. {
  41. if (args.Space == OverlaySpace.ScreenSpace)
  42. {
  43. DrawTooltip(args);
  44. return;
  45. }
  46. var handle = args.WorldHandle;
  47. GetGrids(args.MapId, args.WorldBounds);
  48. // IF YOU ARE ABOUT TO INTRODUCE CHUNKING OR SOME OTHER OPTIMIZATION INTO THIS CODE:
  49. // -- THINK! --
  50. // 1. "Is this going to make a critical atmos debugging tool harder to debug itself?"
  51. // 2. "Is this going to do anything that could cause the atmos debugging tool to use resources, server-side or client-side, when nobody's using it?"
  52. // 3. "Is this going to make it harder for atmos programmers to add data that may not be chunk-friendly into the atmos debugger?"
  53. // Nanotrasen needs YOU! to avoid premature optimization in critical debugging tools - 20kdc
  54. foreach (var (grid, msg) in _grids)
  55. {
  56. handle.SetTransform(_transform.GetWorldMatrix(grid));
  57. DrawData(msg, handle);
  58. }
  59. handle.SetTransform(Matrix3x2.Identity);
  60. }
  61. private void DrawData(DebugMessage msg,
  62. DrawingHandleWorld handle)
  63. {
  64. foreach (var data in msg.OverlayData)
  65. {
  66. if (data != null)
  67. DrawGridTile(data.Value, handle);
  68. }
  69. }
  70. private void DrawGridTile(AtmosDebugOverlayData data,
  71. DrawingHandleWorld handle)
  72. {
  73. DrawFill(data, handle);
  74. DrawBlocked(data, handle);
  75. }
  76. private void DrawFill(AtmosDebugOverlayData data, DrawingHandleWorld handle)
  77. {
  78. var tile = data.Indices;
  79. var fill = GetFillData(data);
  80. var interp = (fill - _system.CfgBase) / _system.CfgScale;
  81. Color res;
  82. if (_system.CfgCBM)
  83. {
  84. // Greyscale interpolation
  85. res = Color.InterpolateBetween(Color.Black, Color.White, interp);
  86. }
  87. else
  88. {
  89. // Red-Green-Blue interpolation
  90. res = interp < 0.5f
  91. ? Color.InterpolateBetween(Color.Red, Color.LimeGreen, interp * 2)
  92. : Color.InterpolateBetween(Color.LimeGreen, Color.Blue, (interp - 0.5f) * 2);
  93. }
  94. res = res.WithAlpha(0.75f);
  95. handle.DrawRect(Box2.FromDimensions(new Vector2(tile.X, tile.Y), new Vector2(1, 1)), res);
  96. }
  97. private float GetFillData(AtmosDebugOverlayData data)
  98. {
  99. if (data.Moles == null)
  100. return 0;
  101. switch (_system.CfgMode)
  102. {
  103. case AtmosDebugOverlayMode.TotalMoles:
  104. var total = 0f;
  105. foreach (var f in data.Moles)
  106. {
  107. total += f;
  108. }
  109. return total;
  110. case AtmosDebugOverlayMode.GasMoles:
  111. return data.Moles[_system.CfgSpecificGas];
  112. default:
  113. return data.Temperature;
  114. }
  115. }
  116. private void DrawBlocked(AtmosDebugOverlayData data, DrawingHandleWorld handle)
  117. {
  118. var tile = data.Indices;
  119. var tileCentre = tile + 0.5f * Vector2.One;
  120. CheckAndShowBlockDir(data, handle, AtmosDirection.North, tileCentre);
  121. CheckAndShowBlockDir(data, handle, AtmosDirection.South, tileCentre);
  122. CheckAndShowBlockDir(data, handle, AtmosDirection.East, tileCentre);
  123. CheckAndShowBlockDir(data, handle, AtmosDirection.West, tileCentre);
  124. // -- Pressure Direction --
  125. if (data.PressureDirection != AtmosDirection.Invalid)
  126. {
  127. DrawPressureDirection(handle, data.PressureDirection, tileCentre, Color.Blue);
  128. }
  129. else if (data.LastPressureDirection != AtmosDirection.Invalid)
  130. {
  131. DrawPressureDirection(handle, data.LastPressureDirection, tileCentre, Color.LightGray);
  132. }
  133. // -- Excited Groups --
  134. if (data.InExcitedGroup is {} grp)
  135. {
  136. var basisA = tile;
  137. var basisB = tile + new Vector2(1.0f, 1.0f);
  138. var basisC = tile + new Vector2(0.0f, 1.0f);
  139. var basisD = tile + new Vector2(1.0f, 0.0f);
  140. var color = Color.White // Use first three nibbles for an unique color... Good enough?
  141. .WithRed(grp & 0x000F)
  142. .WithGreen((grp & 0x00F0) >> 4)
  143. .WithBlue((grp & 0x0F00) >> 8);
  144. handle.DrawLine(basisA, basisB, color);
  145. handle.DrawLine(basisC, basisD, color);
  146. }
  147. if (data.IsSpace)
  148. handle.DrawCircle(tileCentre, 0.15f, Color.Yellow);
  149. if (data.MapAtmosphere)
  150. handle.DrawCircle(tileCentre, 0.1f, Color.Orange);
  151. if (data.NoGrid)
  152. handle.DrawCircle(tileCentre, 0.05f, Color.Black);
  153. }
  154. private void CheckAndShowBlockDir(
  155. AtmosDebugOverlayData data,
  156. DrawingHandleWorld handle,
  157. AtmosDirection dir,
  158. Vector2 tileCentre)
  159. {
  160. if (!data.BlockDirection.HasFlag(dir))
  161. return;
  162. // Account for South being 0.
  163. var atmosAngle = dir.ToAngle() - Angle.FromDegrees(90);
  164. var atmosAngleOfs = atmosAngle.ToVec() * 0.45f;
  165. var atmosAngleOfsR90 = new Vector2(atmosAngleOfs.Y, -atmosAngleOfs.X);
  166. var basisA = tileCentre + atmosAngleOfs - atmosAngleOfsR90;
  167. var basisB = tileCentre + atmosAngleOfs + atmosAngleOfsR90;
  168. handle.DrawLine(basisA, basisB, Color.Azure);
  169. }
  170. private void DrawPressureDirection(
  171. DrawingHandleWorld handle,
  172. AtmosDirection d,
  173. Vector2 center,
  174. Color color)
  175. {
  176. // Account for South being 0.
  177. var atmosAngle = d.ToAngle() - Angle.FromDegrees(90);
  178. var atmosAngleOfs = atmosAngle.ToVec() * 0.4f;
  179. handle.DrawLine(center, center + atmosAngleOfs, color);
  180. }
  181. private void DrawTooltip(in OverlayDrawArgs args)
  182. {
  183. var handle = args.ScreenHandle;
  184. var mousePos = _input.MouseScreenPosition;
  185. if (!mousePos.IsValid)
  186. return;
  187. if (_ui.MouseGetControl(mousePos) is not IViewportControl viewport)
  188. return;
  189. var coords= viewport.PixelToMap(mousePos.Position);
  190. var box = Box2.CenteredAround(coords.Position, 3 * Vector2.One);
  191. GetGrids(coords.MapId, new Box2Rotated(box));
  192. foreach (var (grid, msg) in _grids)
  193. {
  194. var index = _map.WorldToTile(grid, grid, coords.Position);
  195. foreach (var data in msg.OverlayData)
  196. {
  197. if (data?.Indices == index)
  198. {
  199. DrawTooltip(handle, mousePos.Position, data.Value);
  200. return;
  201. }
  202. }
  203. }
  204. }
  205. private void DrawTooltip(DrawingHandleScreen handle, Vector2 pos, AtmosDebugOverlayData data)
  206. {
  207. var lineHeight = _font.GetLineHeight(1f);
  208. var offset = new Vector2(0, lineHeight);
  209. var moles = data.Moles == null
  210. ? "No Air"
  211. : data.Moles.Sum().ToString(CultureInfo.InvariantCulture);
  212. handle.DrawString(_font, pos, $"Moles: {moles}");
  213. pos += offset;
  214. handle.DrawString(_font, pos, $"Temp: {data.Temperature}");
  215. pos += offset;
  216. handle.DrawString(_font, pos, $"Excited: {data.InExcitedGroup?.ToString() ?? "None"}");
  217. pos += offset;
  218. handle.DrawString(_font, pos, $"Space: {data.IsSpace}");
  219. pos += offset;
  220. handle.DrawString(_font, pos, $"Map: {data.MapAtmosphere}");
  221. pos += offset;
  222. handle.DrawString(_font, pos, $"NoGrid: {data.NoGrid}");
  223. pos += offset;
  224. handle.DrawString(_font, pos, $"Immutable: {data.Immutable}");
  225. }
  226. private void GetGrids(MapId mapId, Box2Rotated box)
  227. {
  228. _grids.Clear();
  229. _mapManager.FindGridsIntersecting(
  230. mapId,
  231. box,
  232. ref _grids,
  233. (EntityUid uid,
  234. MapGridComponent grid,
  235. ref List<(Entity<MapGridComponent>, DebugMessage)> state) =>
  236. {
  237. if (_system.TileData.TryGetValue(uid, out var data))
  238. state.Add(((uid, grid), data));
  239. return true;
  240. });
  241. }
  242. }