Atmos debug helpers (#2108)
* Atmos debug overlay * Pressure direction information * Atmos debug overlay: show excited tiles
This commit is contained in:
107
Content.Client/Atmos/AtmosDebugOverlay.cs
Normal file
107
Content.Client/Atmos/AtmosDebugOverlay.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using Content.Client.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.EntitySystems.Atmos;
|
||||
using Content.Shared.Atmos;
|
||||
using Robust.Client.Graphics.ClientEye;
|
||||
using Robust.Client.Graphics.Drawing;
|
||||
using Robust.Client.Graphics.Overlays;
|
||||
using Robust.Client.Interfaces.Graphics;
|
||||
using Robust.Client.Interfaces.Graphics.ClientEye;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Content.Client.Atmos
|
||||
{
|
||||
public class AtmosDebugOverlay : Overlay
|
||||
{
|
||||
private readonly AtmosDebugOverlaySystem _atmosDebugOverlaySystem;
|
||||
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
public AtmosDebugOverlay() : base(nameof(AtmosDebugOverlay))
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_atmosDebugOverlaySystem = EntitySystem.Get<AtmosDebugOverlaySystem>();
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleBase handle, OverlaySpace overlay)
|
||||
{
|
||||
var drawHandle = (DrawingHandleWorld) handle;
|
||||
|
||||
var mapId = _eyeManager.CurrentMap;
|
||||
var eye = _eyeManager.CurrentEye;
|
||||
|
||||
var worldBounds = Box2.CenteredAround(eye.Position.Position,
|
||||
_clyde.ScreenSize / (float) EyeManager.PixelsPerMeter * eye.Zoom);
|
||||
|
||||
// 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
|
||||
|
||||
foreach (var mapGrid in _mapManager.FindGridsIntersecting(mapId, worldBounds))
|
||||
{
|
||||
if (!_atmosDebugOverlaySystem.HasData(mapGrid.Index))
|
||||
continue;
|
||||
|
||||
var gridBounds = new Box2(mapGrid.WorldToLocal(worldBounds.BottomLeft), mapGrid.WorldToLocal(worldBounds.TopRight));
|
||||
|
||||
for (var pass = 0; pass < 3; pass++)
|
||||
{
|
||||
foreach (var tile in mapGrid.GetTilesIntersecting(gridBounds))
|
||||
{
|
||||
var dataMaybeNull = _atmosDebugOverlaySystem.GetData(mapGrid.Index, tile.GridIndices);
|
||||
if (dataMaybeNull != null)
|
||||
{
|
||||
var data = (SharedAtmosDebugOverlaySystem.AtmosDebugOverlayData) dataMaybeNull!;
|
||||
if (pass == 0)
|
||||
{
|
||||
float total = 0;
|
||||
foreach (float f in data.Moles)
|
||||
{
|
||||
total += f;
|
||||
}
|
||||
var interp = total / (Atmospherics.MolesCellStandard * 2);
|
||||
var res = Color.InterpolateBetween(Color.Red, Color.Green, interp).WithAlpha(0.75f);
|
||||
drawHandle.DrawRect(Box2.FromDimensions(mapGrid.LocalToWorld(new Vector2(tile.X, tile.Y)), new Vector2(1, 1)), res);
|
||||
}
|
||||
else if (pass == 1)
|
||||
{
|
||||
if (data.PressureDirection != AtmosDirection.Invalid)
|
||||
{
|
||||
var atmosAngle = data.PressureDirection.ToAngle();
|
||||
var atmosAngleOfs = atmosAngle.ToVec() * 0.4f;
|
||||
var tileCentre = new Vector2(tile.X + 0.5f, tile.Y + 0.5f);
|
||||
var basisA = mapGrid.LocalToWorld(tileCentre);
|
||||
var basisB = mapGrid.LocalToWorld(tileCentre + atmosAngleOfs);
|
||||
drawHandle.DrawLine(basisA, basisB, Color.Blue);
|
||||
}
|
||||
}
|
||||
else if (pass == 2)
|
||||
{
|
||||
if (data.InExcitedGroup)
|
||||
{
|
||||
var tilePos = new Vector2(tile.X, tile.Y);
|
||||
var basisA = mapGrid.LocalToWorld(tilePos);
|
||||
var basisB = mapGrid.LocalToWorld(tilePos + new Vector2(1.0f, 1.0f));
|
||||
var basisC = mapGrid.LocalToWorld(tilePos + new Vector2(0.0f, 1.0f));
|
||||
var basisD = mapGrid.LocalToWorld(tilePos + new Vector2(1.0f, 0.0f));
|
||||
drawHandle.DrawLine(basisA, basisB, Color.Cyan);
|
||||
drawHandle.DrawLine(basisC, basisD, Color.Cyan);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Client.Atmos;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.GameObjects.EntitySystems.Atmos;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Interfaces.Graphics.Overlays;
|
||||
using Robust.Client.Interfaces.ResourceManagement;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
internal sealed class AtmosDebugOverlaySystem : SharedAtmosDebugOverlaySystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
|
||||
private Dictionary<GridId, AtmosDebugOverlayMessage> _tileData =
|
||||
new Dictionary<GridId, AtmosDebugOverlayMessage>();
|
||||
|
||||
private AtmosphereSystem _atmosphereSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeNetworkEvent<AtmosDebugOverlayMessage>(HandleAtmosDebugOverlayMessage);
|
||||
_mapManager.OnGridRemoved += OnGridRemoved;
|
||||
|
||||
_atmosphereSystem = Get<AtmosphereSystem>();
|
||||
|
||||
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
||||
if(!overlayManager.HasOverlay(nameof(AtmosDebugOverlay)))
|
||||
overlayManager.AddOverlay(new AtmosDebugOverlay());
|
||||
}
|
||||
|
||||
private void HandleAtmosDebugOverlayMessage(AtmosDebugOverlayMessage message)
|
||||
{
|
||||
_tileData[message.GridId] = message;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_mapManager.OnGridRemoved -= OnGridRemoved;
|
||||
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
||||
if(!overlayManager.HasOverlay(nameof(GasTileOverlay)))
|
||||
overlayManager.RemoveOverlay(nameof(GasTileOverlay));
|
||||
}
|
||||
|
||||
private void OnGridRemoved(GridId gridId)
|
||||
{
|
||||
if (_tileData.ContainsKey(gridId))
|
||||
{
|
||||
_tileData.Remove(gridId);
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasData(GridId gridId)
|
||||
{
|
||||
return _tileData.ContainsKey(gridId);
|
||||
}
|
||||
|
||||
public AtmosDebugOverlayData? GetData(GridId gridIndex, MapIndices indices)
|
||||
{
|
||||
if (!_tileData.TryGetValue(gridIndex, out var srcMsg))
|
||||
return null;
|
||||
|
||||
var relative = indices - srcMsg.BaseIdx;
|
||||
if (relative.X < 0 || relative.Y < 0 || relative.X >= LocalViewRange || relative.Y >= LocalViewRange)
|
||||
return null;
|
||||
|
||||
return srcMsg.OverlayData[relative.X + (relative.Y * LocalViewRange)];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using System;
|
||||
using Content.Server.GameObjects.Components.Atmos;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Server.GameObjects.EntitySystems.Atmos;
|
||||
using Content.Shared.Atmos;
|
||||
using Robust.Server.Interfaces.Console;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
@@ -621,4 +622,26 @@ namespace Content.Server.Atmos
|
||||
shell.SendText(player, $"Removed {moles} moles of gas {gas} from {tiles} tiles.");
|
||||
}
|
||||
}
|
||||
|
||||
public class ShowAtmos : IClientCommand
|
||||
{
|
||||
public string Command => "showatmos";
|
||||
public string Description => "Toggles seeing atmos debug overlay";
|
||||
public string Help => $"Usage: {Command}";
|
||||
|
||||
public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args)
|
||||
{
|
||||
if (player == null) return;
|
||||
var atmosDebug = EntitySystem.Get<AtmosDebugOverlaySystem>();
|
||||
if (atmosDebug.PlayerObservers.Contains(player))
|
||||
{
|
||||
atmosDebug.PlayerObservers.Remove(player);
|
||||
shell.SendText(player, $"Ok, disabled");
|
||||
}
|
||||
else
|
||||
{
|
||||
atmosDebug.PlayerObservers.Add(player);
|
||||
shell.SendText(player, $"Ok, enabled");
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -93,6 +93,9 @@ namespace Content.Server.Atmos
|
||||
|
||||
private AtmosDirection _pressureDirection;
|
||||
|
||||
// I'm assuming there's a good reason the original variable was made private, but this information is also important.
|
||||
public AtmosDirection PressureDirectionForDebugOverlay => _pressureDirection;
|
||||
|
||||
[ViewVariables, UsedImplicitly]
|
||||
private int PressureDirectionInt => (int)_pressureDirection;
|
||||
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Content.Server.GameObjects.Components.Atmos;
|
||||
using Content.Server.Atmos;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.GameObjects.EntitySystems.Atmos;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Interfaces.Configuration;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.Interfaces.Timing;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.GameObjects.EntitySystems.Atmos
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class AtmosDebugOverlaySystem : SharedAtmosDebugOverlaySystem
|
||||
{
|
||||
[Robust.Shared.IoC.Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Robust.Shared.IoC.Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Robust.Shared.IoC.Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Robust.Shared.IoC.Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Robust.Shared.IoC.Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Players allowed to see the atmos debug overlay
|
||||
/// </summary>
|
||||
public HashSet<IPlayerSession> PlayerObservers = new HashSet<IPlayerSession>();
|
||||
|
||||
/// <summary>
|
||||
/// Overlay update ticks per second.
|
||||
/// </summary>
|
||||
private float _updateCooldown;
|
||||
|
||||
private AtmosphereSystem _atmosphereSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_atmosphereSystem = Get<AtmosphereSystem>();
|
||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
_configManager.RegisterCVar("net.atmosdbgoverlaytickrate", 3.0f);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_playerManager.PlayerStatusChanged -= OnPlayerStatusChanged;
|
||||
}
|
||||
|
||||
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
||||
{
|
||||
if (e.NewStatus != SessionStatus.InGame)
|
||||
{
|
||||
if (PlayerObservers.Contains(e.Session))
|
||||
{
|
||||
PlayerObservers.Remove(e.Session);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private AtmosDebugOverlayData ConvertTileToData(TileAtmosphere? tile)
|
||||
{
|
||||
var gases = new float[Atmospherics.TotalNumberOfGases];
|
||||
if (tile?.Air == null)
|
||||
{
|
||||
return new AtmosDebugOverlayData(0, gases, AtmosDirection.Invalid, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
|
||||
{
|
||||
gases[i] = tile.Air.GetMoles(i);
|
||||
}
|
||||
return new AtmosDebugOverlayData(tile.Air.Temperature, gases, tile.PressureDirectionForDebugOverlay, tile.ExcitedGroup != null);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
AccumulatedFrameTime += frameTime;
|
||||
_updateCooldown = 1 / _configManager.GetCVar<float>("net.atmosdbgoverlaytickrate");
|
||||
|
||||
if (AccumulatedFrameTime < _updateCooldown)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// This is the timer from GasTileOverlaySystem
|
||||
AccumulatedFrameTime -= _updateCooldown;
|
||||
|
||||
var currentTick = _gameTiming.CurTick;
|
||||
|
||||
// Now we'll go through each player, then through each chunk in range of that player checking if the player is still in range
|
||||
// If they are, check if they need the new data to send (i.e. if there's an overlay for the gas).
|
||||
// Afterwards we reset all the chunk data for the next time we tick.
|
||||
foreach (var session in PlayerObservers)
|
||||
{
|
||||
if (session.AttachedEntity == null) continue;
|
||||
|
||||
var entity = session.AttachedEntity;
|
||||
|
||||
var worldBounds = Box2.CenteredAround(entity.Transform.WorldPosition,
|
||||
new Vector2(LocalViewRange, LocalViewRange));
|
||||
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(entity.Transform.MapID, worldBounds))
|
||||
{
|
||||
if (!_entityManager.TryGetEntity(grid.GridEntityId, out var gridEnt)) continue;
|
||||
|
||||
if (!gridEnt.TryGetComponent<GridAtmosphereComponent>(out var gam)) continue;
|
||||
|
||||
var entityTile = grid.GetTileRef(entity.Transform.Coordinates).GridIndices;
|
||||
var baseTile = new MapIndices(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 mapIndices = new MapIndices(baseTile.X + x, baseTile.Y + y);
|
||||
debugOverlayContent[index++] = ConvertTileToData(gam.GetTile(mapIndices));
|
||||
}
|
||||
}
|
||||
|
||||
RaiseNetworkEvent(new AtmosDebugOverlayMessage(grid.Index, baseTile, debugOverlayContent), session.ConnectedClient);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
using Content.Shared.Atmos;
|
||||
|
||||
namespace Content.Shared.GameObjects.EntitySystems.Atmos
|
||||
{
|
||||
public abstract class SharedAtmosDebugOverlaySystem : EntitySystem
|
||||
{
|
||||
// Keep in mind, this system is hilariously unoptimized. The goal here is to provide accurate debug data.
|
||||
public const int LocalViewRange = 16;
|
||||
protected float AccumulatedFrameTime;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public readonly struct AtmosDebugOverlayData
|
||||
{
|
||||
public readonly float Temperature;
|
||||
public readonly float[] Moles;
|
||||
public readonly AtmosDirection PressureDirection;
|
||||
public readonly bool InExcitedGroup;
|
||||
|
||||
public AtmosDebugOverlayData(float temperature, float[] moles, AtmosDirection pressureDirection, bool inExcited)
|
||||
{
|
||||
Temperature = temperature;
|
||||
Moles = moles;
|
||||
PressureDirection = pressureDirection;
|
||||
InExcitedGroup = inExcited;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid tiles for the gas overlay.
|
||||
/// No point re-sending every tile if only a subset might have been updated.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class AtmosDebugOverlayMessage : EntitySystemMessage
|
||||
{
|
||||
public GridId GridId { get; }
|
||||
|
||||
public MapIndices BaseIdx { get; }
|
||||
// LocalViewRange*LocalViewRange
|
||||
public AtmosDebugOverlayData[] OverlayData { get; }
|
||||
|
||||
public AtmosDebugOverlayMessage(GridId gridIndices, MapIndices baseIdx, AtmosDebugOverlayData[] overlayData)
|
||||
{
|
||||
GridId = gridIndices;
|
||||
BaseIdx = baseIdx;
|
||||
OverlayData = overlayData;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -195,6 +195,7 @@
|
||||
- settemp
|
||||
- setatmostemp
|
||||
- deletegas
|
||||
- showatmos
|
||||
- tilewalls
|
||||
- events
|
||||
- destroymechanism
|
||||
|
||||
Reference in New Issue
Block a user