using System.Numerics;
using Content.Server.Atmos.Components;
using Content.Shared.Atmos;
using Content.Shared.Atmos.EntitySystems;
using Content.Shared.CCVar;
using JetBrains.Annotations;
using Robust.Server.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.Map;
namespace Content.Server.Atmos.EntitySystems
{
[UsedImplicitly]
public sealed class AtmosDebugOverlaySystem : SharedAtmosDebugOverlaySystem
{
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
///
/// Players allowed to see the atmos debug overlay.
/// To modify it see and
/// .
///
private readonly HashSet _playerObservers = new();
///
/// Overlay update ticks per second.
///
private float _updateCooldown;
public override void Initialize()
{
base.Initialize();
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
}
public override void Shutdown()
{
base.Shutdown();
_playerManager.PlayerStatusChanged -= OnPlayerStatusChanged;
}
public bool AddObserver(IPlayerSession observer)
{
return _playerObservers.Add(observer);
}
public bool HasObserver(IPlayerSession observer)
{
return _playerObservers.Contains(observer);
}
public bool RemoveObserver(IPlayerSession observer)
{
if (!_playerObservers.Remove(observer))
{
return false;
}
var message = new AtmosDebugOverlayDisableMessage();
RaiseNetworkEvent(message, observer.ConnectedClient);
return true;
}
///
/// Adds the given observer if it doesn't exist, removes it otherwise.
///
/// The observer to toggle.
/// true if added, false if removed.
public bool ToggleObserver(IPlayerSession observer)
{
if (HasObserver(observer))
{
RemoveObserver(observer);
return false;
}
else
{
AddObserver(observer);
return true;
}
}
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
{
if (e.NewStatus != SessionStatus.InGame)
{
RemoveObserver(e.Session);
}
}
private AtmosDebugOverlayData ConvertTileToData(TileAtmosphere? tile, bool mapIsSpace)
{
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);
}
}
public override void Update(float frameTime)
{
AccumulatedFrameTime += frameTime;
_updateCooldown = 1 / _configManager.GetCVar(CCVars.NetAtmosDebugOverlayTickRate);
if (AccumulatedFrameTime < _updateCooldown)
{
return;
}
// This is the timer from GasTileOverlaySystem
AccumulatedFrameTime -= _updateCooldown;
// 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 is not {Valid: true} entity)
continue;
var transform = EntityManager.GetComponent(entity);
var mapUid = transform.MapUid;
var mapIsSpace = _atmosphereSystem.IsTileSpace(null, mapUid, Vector2i.Zero);
var worldBounds = Box2.CenteredAround(transform.WorldPosition,
new Vector2(LocalViewRange, LocalViewRange));
foreach (var grid in _mapManager.FindGridsIntersecting(transform.MapID, worldBounds))
{
var uid = grid.Owner;
if (!Exists(uid))
continue;
if (!TryComp(uid, out GridAtmosphereComponent? gridAtmos))
continue;
var entityTile = grid.GetTileRef(transform.Coordinates).GridIndices;
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);
}
}
RaiseNetworkEvent(new AtmosDebugOverlayMessage(grid.Owner, baseTile, debugOverlayContent), session.ConnectedClient);
}
}
}
}
}