+using System.Linq;
using System.Numerics;
using Content.Client.Atmos.EntitySystems;
+using Content.Client.Resources;
using Content.Shared.Atmos;
-using Content.Shared.Atmos.EntitySystems;
using Robust.Client.Graphics;
+using Robust.Client.Input;
+using Robust.Client.ResourceManagement;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Enums;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
+using AtmosDebugOverlayData = Content.Shared.Atmos.EntitySystems.SharedAtmosDebugOverlaySystem.AtmosDebugOverlayData;
+using DebugMessage = Content.Shared.Atmos.EntitySystems.SharedAtmosDebugOverlaySystem.AtmosDebugOverlayMessage;
-namespace Content.Client.Atmos.Overlays
+namespace Content.Client.Atmos.Overlays;
+
+
+public sealed class AtmosDebugOverlay : Overlay
{
- public sealed class AtmosDebugOverlay : Overlay
+ [Dependency] private readonly IEntityManager _entManager = default!;
+ [Dependency] private readonly IMapManager _mapManager = default!;
+ [Dependency] private readonly IInputManager _input = default!;
+ [Dependency] private readonly IUserInterfaceManager _ui = default!;
+ [Dependency] private readonly IResourceCache _cache = default!;
+ private readonly SharedTransformSystem _transform;
+ private readonly AtmosDebugOverlaySystem _system;
+ private readonly SharedMapSystem _map;
+ private readonly Font _font;
+ private List<(Entity<MapGridComponent>, DebugMessage)> _grids = new();
+
+ public override OverlaySpace Space => OverlaySpace.WorldSpace | OverlaySpace.ScreenSpace;
+
+ internal AtmosDebugOverlay(AtmosDebugOverlaySystem system)
{
- private readonly AtmosDebugOverlaySystem _atmosDebugOverlaySystem;
+ IoCManager.InjectDependencies(this);
+
+ _system = system;
+ _transform = _entManager.System<SharedTransformSystem>();
+ _map = _entManager.System<SharedMapSystem>();
+ _font = _cache.GetFont("/Fonts/NotoSans/NotoSans-Regular.ttf", 12);
+ }
+
+ protected override void Draw(in OverlayDrawArgs args)
+ {
+ if (args.Space == OverlaySpace.ScreenSpace)
+ {
+ DrawTooltip(args);
+ return;
+ }
- [Dependency] private readonly IEntityManager _entManager = default!;
- [Dependency] private readonly IMapManager _mapManager = default!;
+ var handle = args.WorldHandle;
+ GetGrids(args.MapId, args.WorldBounds);
- public override OverlaySpace Space => OverlaySpace.WorldSpace;
- private List<Entity<MapGridComponent>> _grids = new();
+ // IF YOU ARE ABOUT TO INTRODUCE CHUNKING OR SOME OTHER OPTIMIZATION INTO THIS CODE:
+ // -- THINK! --
+ // 1. "Is this going to make a critical atmos debugging tool harder to debug itself?"
+ // 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?"
+ // 3. "Is this going to make it harder for atmos programmers to add data that may not be chunk-friendly into the atmos debugger?"
+ // Nanotrasen needs YOU! to avoid premature optimization in critical debugging tools - 20kdc
- internal AtmosDebugOverlay(AtmosDebugOverlaySystem system)
+ foreach (var (grid, msg) in _grids)
{
- IoCManager.InjectDependencies(this);
+ handle.SetTransform(_transform.GetWorldMatrix(grid));
+ DrawData(msg, handle);
+ }
- _atmosDebugOverlaySystem = system;
+ handle.SetTransform(Matrix3.Identity);
+ }
+
+ private void DrawData(DebugMessage msg,
+ DrawingHandleWorld handle)
+ {
+ foreach (var data in msg.OverlayData)
+ {
+ if (data != null)
+ DrawGridTile(data.Value, handle);
}
+ }
+
+ private void DrawGridTile(AtmosDebugOverlayData data,
+ DrawingHandleWorld handle)
+ {
+ DrawFill(data, handle);
+ DrawBlocked(data, handle);
+ }
+
+ private void DrawFill(AtmosDebugOverlayData data, DrawingHandleWorld handle)
+ {
+ var tile = data.Indices;
+ var fill = GetFillData(data);
+ var interp = (fill - _system.CfgBase) / _system.CfgScale;
- protected override void Draw(in OverlayDrawArgs args)
+ Color res;
+ if (_system.CfgCBM)
+ {
+ // Greyscale interpolation
+ res = Color.InterpolateBetween(Color.Black, Color.White, interp);
+ }
+ else
{
- var drawHandle = args.WorldHandle;
+ // Red-Green-Blue interpolation
+ if (interp < 0.5f)
+ {
+ res = Color.InterpolateBetween(Color.Red, Color.LimeGreen, interp * 2);
+ }
+ else
+ {
+ res = Color.InterpolateBetween(Color.LimeGreen, Color.Blue, (interp - 0.5f) * 2);
+ }
+ }
- var mapId = args.Viewport.Eye!.Position.MapId;
- var worldBounds = args.WorldBounds;
+ res = res.WithAlpha(0.75f);
+ handle.DrawRect(Box2.FromDimensions(new Vector2(tile.X, tile.Y), new Vector2(1, 1)), res);
+ }
- // IF YOU ARE ABOUT TO INTRODUCE CHUNKING OR SOME OTHER OPTIMIZATION INTO THIS CODE:
- // -- THINK! --
- // 1. "Is this going to make a critical atmos debugging tool harder to debug itself?"
- // 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?"
- // 3. "Is this going to make it harder for atmos programmers to add data that may not be chunk-friendly into the atmos debugger?"
- // Nanotrasen needs YOU! to avoid premature optimization in critical debugging tools - 20kdc
+ private float GetFillData(AtmosDebugOverlayData data)
+ {
+ if (data.Moles == null)
+ return 0;
- _grids.Clear();
+ switch (_system.CfgMode)
+ {
+ case AtmosDebugOverlayMode.TotalMoles:
+ var total = 0f;
+ foreach (var f in data.Moles)
+ {
+ total += f;
+ }
- _mapManager.FindGridsIntersecting(mapId, worldBounds, ref _grids, (EntityUid uid, MapGridComponent grid,
- ref List<Entity<MapGridComponent>> state) =>
- {
- state.Add((uid, grid));
- return true;
- });
+ return total;
+ case AtmosDebugOverlayMode.GasMoles:
+ return data.Moles[_system.CfgSpecificGas];
+ default:
+ return data.Temperature;
+ }
+ }
- foreach (var (uid, mapGrid) in _grids)
- {
- if (!_atmosDebugOverlaySystem.HasData(uid) ||
- !_entManager.TryGetComponent<TransformComponent>(uid, out var xform))
- continue;
+ private void DrawBlocked(AtmosDebugOverlayData data, DrawingHandleWorld handle)
+ {
+ var tile = data.Indices;
+ var tileCentre = tile + 0.5f * Vector2.One;
+ CheckAndShowBlockDir(data, handle, AtmosDirection.North, tileCentre);
+ CheckAndShowBlockDir(data, handle, AtmosDirection.South, tileCentre);
+ CheckAndShowBlockDir(data, handle, AtmosDirection.East, tileCentre);
+ CheckAndShowBlockDir(data, handle, AtmosDirection.West, tileCentre);
+
+ // -- Pressure Direction --
+ if (data.PressureDirection != AtmosDirection.Invalid)
+ {
+ DrawPressureDirection(handle, data.PressureDirection, tileCentre, Color.Blue);
+ }
+ else if (data.LastPressureDirection != AtmosDirection.Invalid)
+ {
+ DrawPressureDirection(handle, data.LastPressureDirection, tileCentre, Color.LightGray);
+ }
+
+ // -- Excited Groups --
+ if (data.InExcitedGroup is {} grp)
+ {
+ var basisA = tile;
+ var basisB = tile + new Vector2(1.0f, 1.0f);
+ var basisC = tile + new Vector2(0.0f, 1.0f);
+ var basisD = tile + new Vector2(1.0f, 0.0f);
+ var color = Color.White // Use first three nibbles for an unique color... Good enough?
+ .WithRed(grp & 0x000F)
+ .WithGreen((grp & 0x00F0) >> 4)
+ .WithBlue((grp & 0x0F00) >> 8);
+ handle.DrawLine(basisA, basisB, color);
+ handle.DrawLine(basisC, basisD, color);
+ }
+
+ if (data.IsSpace)
+ handle.DrawCircle(tileCentre, 0.15f, Color.Yellow);
+
+ if (data.MapAtmosphere)
+ handle.DrawCircle(tileCentre, 0.1f, Color.Orange);
+
+ if (data.NoGrid)
+ handle.DrawCircle(tileCentre, 0.05f, Color.Black);
+ }
+
+ private void CheckAndShowBlockDir(AtmosDebugOverlayData data, DrawingHandleWorld handle, AtmosDirection dir,
+ Vector2 tileCentre)
+ {
+ if (!data.BlockDirection.HasFlag(dir))
+ return;
+
+ // Account for South being 0.
+ var atmosAngle = dir.ToAngle() - Angle.FromDegrees(90);
+ var atmosAngleOfs = atmosAngle.ToVec() * 0.45f;
+ var atmosAngleOfsR90 = new Vector2(atmosAngleOfs.Y, -atmosAngleOfs.X);
+ var basisA = tileCentre + atmosAngleOfs - atmosAngleOfsR90;
+ var basisB = tileCentre + atmosAngleOfs + atmosAngleOfsR90;
+ handle.DrawLine(basisA, basisB, Color.Azure);
+ }
- drawHandle.SetTransform(xform.WorldMatrix);
+ private void DrawPressureDirection(
+ DrawingHandleWorld handle,
+ AtmosDirection d,
+ Vector2 center,
+ Color color)
+ {
+ // Account for South being 0.
+ var atmosAngle = d.ToAngle() - Angle.FromDegrees(90);
+ var atmosAngleOfs = atmosAngle.ToVec() * 0.4f;
+ handle.DrawLine(center, center + atmosAngleOfs, color);
+ }
+
+ private void DrawTooltip(in OverlayDrawArgs args)
+ {
+ var handle = args.ScreenHandle;
+ var mousePos = _input.MouseScreenPosition;
+ if (!mousePos.IsValid)
+ return;
+
+ if (_ui.MouseGetControl(mousePos) is not IViewportControl viewport)
+ return;
- for (var pass = 0; pass < 2; pass++)
+ var coords= viewport.PixelToMap(mousePos.Position);
+ var box = Box2.CenteredAround(coords.Position, 3 * Vector2.One);
+ GetGrids(coords.MapId, new Box2Rotated(box));
+
+ foreach (var (grid, msg) in _grids)
+ {
+ var index = _map.WorldToTile(grid, grid, coords.Position);
+ foreach (var data in msg.OverlayData)
+ {
+ if (data?.Indices == index)
{
- foreach (var tile in mapGrid.GetTilesIntersecting(worldBounds))
- {
- var dataMaybeNull = _atmosDebugOverlaySystem.GetData(uid, tile.GridIndices);
- if (dataMaybeNull != null)
- {
- var data = (SharedAtmosDebugOverlaySystem.AtmosDebugOverlayData) dataMaybeNull;
- if (pass == 0)
- {
- // -- Mole Count --
- float total = 0;
- switch (_atmosDebugOverlaySystem.CfgMode)
- {
- case AtmosDebugOverlayMode.TotalMoles:
- foreach (var f in data.Moles)
- {
- total += f;
- }
- break;
- case AtmosDebugOverlayMode.GasMoles:
- total = data.Moles[_atmosDebugOverlaySystem.CfgSpecificGas];
- break;
- case AtmosDebugOverlayMode.Temperature:
- total = data.Temperature;
- break;
- }
- var interp = (total - _atmosDebugOverlaySystem.CfgBase) / _atmosDebugOverlaySystem.CfgScale;
- Color res;
- if (_atmosDebugOverlaySystem.CfgCBM)
- {
- // Greyscale interpolation
- res = Color.InterpolateBetween(Color.Black, Color.White, interp);
- }
- else
- {
- // Red-Green-Blue interpolation
- if (interp < 0.5f)
- {
- res = Color.InterpolateBetween(Color.Red, Color.LimeGreen, interp * 2);
- }
- else
- {
- res = Color.InterpolateBetween(Color.LimeGreen, Color.Blue, (interp - 0.5f) * 2);
- }
- }
- res = res.WithAlpha(0.75f);
- drawHandle.DrawRect(Box2.FromDimensions(new Vector2(tile.X, tile.Y), new Vector2(1, 1)), res);
- }
- else if (pass == 1)
- {
- // -- Blocked Directions --
- void CheckAndShowBlockDir(AtmosDirection dir)
- {
- if (data.BlockDirection.HasFlag(dir))
- {
- // Account for South being 0.
- var atmosAngle = dir.ToAngle() - Angle.FromDegrees(90);
- var atmosAngleOfs = atmosAngle.ToVec() * 0.45f;
- var atmosAngleOfsR90 = new Vector2(atmosAngleOfs.Y, -atmosAngleOfs.X);
- var tileCentre = new Vector2(tile.X + 0.5f, tile.Y + 0.5f);
- var basisA = tileCentre + atmosAngleOfs - atmosAngleOfsR90;
- var basisB = tileCentre + atmosAngleOfs + atmosAngleOfsR90;
- drawHandle.DrawLine(basisA, basisB, Color.Azure);
- }
- }
- CheckAndShowBlockDir(AtmosDirection.North);
- CheckAndShowBlockDir(AtmosDirection.South);
- CheckAndShowBlockDir(AtmosDirection.East);
- CheckAndShowBlockDir(AtmosDirection.West);
-
- void DrawPressureDirection(
- DrawingHandleWorld handle,
- AtmosDirection d,
- TileRef t,
- Color color)
- {
- // Account for South being 0.
- var atmosAngle = d.ToAngle() - Angle.FromDegrees(90);
- var atmosAngleOfs = atmosAngle.ToVec() * 0.4f;
- var tileCentre = new Vector2(t.X + 0.5f, t.Y + 0.5f);
- var basisA = tileCentre;
- var basisB = tileCentre + atmosAngleOfs;
- handle.DrawLine(basisA, basisB, color);
- }
-
- // -- Pressure Direction --
- if (data.PressureDirection != AtmosDirection.Invalid)
- {
- DrawPressureDirection(drawHandle, data.PressureDirection, tile, Color.Blue);
- }
- else if (data.LastPressureDirection != AtmosDirection.Invalid)
- {
- DrawPressureDirection(drawHandle, data.LastPressureDirection, tile, Color.LightGray);
- }
-
- var tilePos = new Vector2(tile.X, tile.Y);
-
- // -- Excited Groups --
- if (data.InExcitedGroup != 0)
- {
- var basisA = tilePos;
- var basisB = tilePos + new Vector2(1.0f, 1.0f);
- var basisC = tilePos + new Vector2(0.0f, 1.0f);
- var basisD = tilePos + new Vector2(1.0f, 0.0f);
- var color = Color.White // Use first three nibbles for an unique color... Good enough?
- .WithRed( data.InExcitedGroup & 0x000F)
- .WithGreen((data.InExcitedGroup & 0x00F0) >>4)
- .WithBlue( (data.InExcitedGroup & 0x0F00) >>8);
- drawHandle.DrawLine(basisA, basisB, color);
- drawHandle.DrawLine(basisC, basisD, color);
- }
-
- // -- Space Tiles --
- if (data.IsSpace)
- {
- drawHandle.DrawCircle(tilePos + Vector2.One/2, 0.125f, Color.Orange);
- }
- }
- }
- }
+ DrawTooltip(handle, mousePos.Position, data.Value);
+ return;
}
}
-
- drawHandle.SetTransform(Matrix3.Identity);
}
}
+
+ private void DrawTooltip(DrawingHandleScreen handle, Vector2 pos, AtmosDebugOverlayData data)
+ {
+ var lineHeight = _font.GetLineHeight(1f);
+ var offset = new Vector2(0, lineHeight);
+
+ var moles = data.Moles == null
+ ? "No Air"
+ : data.Moles.Sum().ToString();
+
+ handle.DrawString(_font, pos, $"Moles: {moles}");
+ pos += offset;
+ handle.DrawString(_font, pos, $"Temp: {data.Temperature}");
+ pos += offset;
+ handle.DrawString(_font, pos, $"Excited: {data.InExcitedGroup?.ToString() ?? "None"}");
+ pos += offset;
+ handle.DrawString(_font, pos, $"Space: {data.IsSpace}");
+ pos += offset;
+ handle.DrawString(_font, pos, $"Map: {data.MapAtmosphere}");
+ pos += offset;
+ handle.DrawString(_font, pos, $"NoGrid: {data.NoGrid}");
+ }
+
+ private void GetGrids(MapId mapId, Box2Rotated box)
+ {
+ _grids.Clear();
+ _mapManager.FindGridsIntersecting(mapId, box, ref _grids, (EntityUid uid, MapGridComponent grid,
+ ref List<(Entity<MapGridComponent>, DebugMessage)> state) =>
+ {
+ if (_system.TileData.TryGetValue(uid, out var data))
+ state.Add(((uid, grid), data));
+ return true;
+ });
+ }
}
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IConfigurationManager _configManager = default!;
- [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
+ [Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly MapSystem _mapSystem = default!;
/// <summary>
}
var message = new AtmosDebugOverlayDisableMessage();
- RaiseNetworkEvent(message, observer.ConnectedClient);
+ RaiseNetworkEvent(message, observer.Channel);
return true;
}
RemoveObserver(observer);
return false;
}
- else
- {
- AddObserver(observer);
- return true;
- }
+
+ AddObserver(observer);
+ return true;
}
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
}
}
- private AtmosDebugOverlayData ConvertTileToData(TileAtmosphere? tile, bool mapIsSpace)
+ private AtmosDebugOverlayData ConvertTileToData(TileAtmosphere? tile)
{
- var gases = new float[Atmospherics.AdjustedNumberOfGases];
-
- if (tile?.Air == null)
- {
- return new AtmosDebugOverlayData(Atmospherics.TCMB, gases, AtmosDirection.Invalid, tile?.LastPressureDirection ?? AtmosDirection.Invalid, 0, tile?.BlockedAirflow ?? AtmosDirection.Invalid, tile?.Space ?? mapIsSpace);
- }
- else
- {
- NumericsHelpers.Add(gases, tile.Air.Moles);
- return new AtmosDebugOverlayData(tile.Air.Temperature, gases, tile.PressureDirection, tile.LastPressureDirection, tile.ExcitedGroup?.GetHashCode() ?? 0, tile.BlockedAirflow, tile.Space);
- }
+ if (tile == null)
+ return default;
+
+ return new AtmosDebugOverlayData(
+ tile.GridIndices,
+ tile.Air?.Temperature ?? default,
+ tile.Air?.Moles,
+ tile.PressureDirection,
+ tile.LastPressureDirection,
+ tile.BlockedAirflow,
+ tile.ExcitedGroup?.GetHashCode(),
+ tile.Space,
+ false,
+ false);
}
public override void Update(float frameTime)
if (session.AttachedEntity is not {Valid: true} entity)
continue;
- var transform = EntityManager.GetComponent<TransformComponent>(entity);
- var mapUid = transform.MapUid;
-
- var mapIsSpace = _atmosphereSystem.IsTileSpace(null, mapUid, Vector2i.Zero);
-
- var worldBounds = Box2.CenteredAround(transform.WorldPosition,
+ var transform = Transform(entity);
+ var pos = _transform.GetWorldPosition(transform);
+ var worldBounds = Box2.CenteredAround(pos,
new Vector2(LocalViewRange, LocalViewRange));
_grids.Clear();
continue;
var entityTile = _mapSystem.GetTileRef(grid, grid, transform.Coordinates).GridIndices;
- var baseTile = new Vector2i(entityTile.X - (LocalViewRange / 2), entityTile.Y - (LocalViewRange / 2));
- var debugOverlayContent = new AtmosDebugOverlayData[LocalViewRange * LocalViewRange];
+ var baseTile = new Vector2i(entityTile.X - LocalViewRange / 2, entityTile.Y - LocalViewRange / 2);
+ var debugOverlayContent = new AtmosDebugOverlayData?[LocalViewRange * LocalViewRange];
var index = 0;
for (var y = 0; y < LocalViewRange; y++)
for (var x = 0; x < LocalViewRange; x++)
{
var vector = new Vector2i(baseTile.X + x, baseTile.Y + y);
- debugOverlayContent[index++] = ConvertTileToData(gridAtmos.Tiles.TryGetValue(vector, out var tile) ? tile : null, mapIsSpace);
+ gridAtmos.Tiles.TryGetValue(vector, out var tile);
+ debugOverlayContent[index++] = tile == null ? null : ConvertTileToData(tile);
}
}
- RaiseNetworkEvent(new AtmosDebugOverlayMessage(GetNetEntity(grid), baseTile, debugOverlayContent), session.ConnectedClient);
+ var msg = new AtmosDebugOverlayMessage(GetNetEntity(grid), baseTile, debugOverlayContent);
+ RaiseNetworkEvent(msg, session.Channel);
}
}
}