mirror of
https://github.com/space-wizards/space-station-14.git
synced 2026-02-14 19:29:53 +01:00
* revert of the revert * tests * changes * more fun * test * ccvvvar * works but bad * now its better * more fixes * more cleanup * cleaning * last fixes before move to glasses activ * x * glasses only * working * fix toolbox * cleanup * ThermalByte added * small fix * small optimalisations * float bux fix * comments add * more comments * more comments * last fix * revert cvar delete * wrong blue shades * cvar refactor * Update Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com> * Update Content.Client/Atmos/Overlays/GasTileDangerousTemperatureOverlay.cs Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com> * tweak to TryGetTemperature comment * Factors are now const * renames * Interface for ThermalByte * tile color vaccum and more comments * saving yeeted * integration test * rename and cleanup * fix * cleanup * switch * UT fix (hopefully) * small bug+ rename * vaccum limit + space is now invalid * typo * typo * fix * cleanup --------- Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com>
258 lines
8.9 KiB
C#
258 lines
8.9 KiB
C#
using Content.Shared.Atmos.Components;
|
|
using Robust.Shared.Configuration;
|
|
using Robust.Shared.GameStates;
|
|
using Robust.Shared.Prototypes;
|
|
using Robust.Shared.Serialization;
|
|
|
|
namespace Content.Shared.Atmos.EntitySystems;
|
|
|
|
public abstract class SharedGasTileOverlaySystem : EntitySystem
|
|
{
|
|
public const byte ChunkSize = 8;
|
|
protected float AccumulatedFrameTime;
|
|
protected bool PvsEnabled;
|
|
|
|
[Dependency] protected readonly IPrototypeManager ProtoMan = default!;
|
|
[Dependency] protected readonly IConfigurationManager ConfMan = default!;
|
|
[Dependency] private readonly SharedAtmosphereSystem _atmosphere = default!;
|
|
|
|
/// <summary>
|
|
/// array of the ids of all visible gases.
|
|
/// </summary>
|
|
public int[] VisibleGasId = default!;
|
|
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
SubscribeLocalEvent<GasTileOverlayComponent, ComponentGetState>(OnGetState);
|
|
|
|
List<int> visibleGases = new();
|
|
|
|
for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
|
|
{
|
|
var gasPrototype = _atmosphere.GetGas(i);
|
|
if (!string.IsNullOrEmpty(gasPrototype.GasOverlayTexture) ||
|
|
(!string.IsNullOrEmpty(gasPrototype.GasOverlaySprite) && !string.IsNullOrEmpty(gasPrototype.GasOverlayState)))
|
|
visibleGases.Add(i);
|
|
}
|
|
VisibleGasId = visibleGases.ToArray();
|
|
}
|
|
|
|
private void OnGetState(EntityUid uid, GasTileOverlayComponent component, ref ComponentGetState args)
|
|
{
|
|
if (PvsEnabled && !args.ReplayState)
|
|
return;
|
|
|
|
// Should this be a full component state or a delta-state?
|
|
if (args.FromTick <= component.CreationTick || args.FromTick <= component.ForceTick)
|
|
{
|
|
args.State = new GasTileOverlayState(component.Chunks);
|
|
return;
|
|
}
|
|
|
|
var data = new Dictionary<Vector2i, GasOverlayChunk>();
|
|
foreach (var (index, chunk) in component.Chunks)
|
|
{
|
|
if (chunk.LastUpdate >= args.FromTick)
|
|
data[index] = chunk;
|
|
}
|
|
|
|
args.State = new GasTileOverlayDeltaState(data, new(component.Chunks.Keys));
|
|
}
|
|
|
|
public static Vector2i GetGasChunkIndices(Vector2i indices)
|
|
{
|
|
return new Vector2i((int)MathF.Floor((float)indices.X / ChunkSize), (int)MathF.Floor((float)indices.Y / ChunkSize));
|
|
}
|
|
|
|
[Serializable, NetSerializable]
|
|
public readonly struct GasOverlayData : IEquatable<GasOverlayData>
|
|
{
|
|
[ViewVariables] public readonly byte FireState;
|
|
[ViewVariables] public readonly byte[] Opacity;
|
|
// TODO change fire color based on ByteTemp
|
|
|
|
/// <summary>
|
|
/// Network-synced air temperature, compressed to a single byte per tile for bandwidth optimization.
|
|
/// Note: Values are approximate and may deviate even ~10°C from the precise server side only temperature.
|
|
/// </summary>
|
|
[ViewVariables]
|
|
public readonly ThermalByte ByteGasTemperature;
|
|
|
|
|
|
public GasOverlayData(byte fireState, byte[] opacity, ThermalByte byteTemp)
|
|
{
|
|
FireState = fireState;
|
|
Opacity = opacity;
|
|
ByteGasTemperature = byteTemp;
|
|
}
|
|
|
|
public bool Equals(GasOverlayData other)
|
|
{
|
|
if (FireState != other.FireState)
|
|
return false;
|
|
|
|
if (Opacity?.Length != other.Opacity?.Length)
|
|
return false;
|
|
|
|
if (Opacity != null && other.Opacity != null)
|
|
{
|
|
for (var i = 0; i < Opacity.Length; i++)
|
|
{
|
|
if (Opacity[i] != other.Opacity[i])
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (ByteGasTemperature != other.ByteGasTemperature)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
[Serializable, NetSerializable]
|
|
public sealed class GasOverlayUpdateEvent : EntityEventArgs
|
|
{
|
|
public Dictionary<NetEntity, List<GasOverlayChunk>> UpdatedChunks = new();
|
|
public Dictionary<NetEntity, HashSet<Vector2i>> RemovedChunks = new();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Struct for networking gas temperatures to all clients using a single struct(byte) per tile.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// This struct compresses the gas temperature into a 1-byte value (0-255).
|
|
/// It clamps the temperature to a maximum of 1000K and divides it by 4, creating a range of 0-250.
|
|
/// This provides a resolution of 4 degrees Kelvin.
|
|
/// </para>
|
|
/// <para>
|
|
/// The remaining bytes are used as special flags:
|
|
/// <list type="bullet">
|
|
/// <item><description><b>255</b>: Represents a Wall (block cannot hold atmosphere).</description></item>
|
|
/// <item><description><b>254</b>: Represents a Vacuum.</description></item>
|
|
/// <item><description><b>251-253</b>: Reserved for future use.</description></item>
|
|
/// </list>
|
|
/// </para>
|
|
/// <para>
|
|
/// <b>Dirtying Logic:</b> The value is only dirtied and networked if the difference between the
|
|
/// networked byte and the real atmosphere byte is greater than 1. This prevents network spam
|
|
/// from minor temperature fluctuations (e.g., heating from 1K to 8K will not trigger an update,
|
|
/// but hitting 9K moves the byte index enough to sync).
|
|
/// </para>
|
|
/// <para>
|
|
/// Currently, the conversion is linear. Future improvements might involve a quadratic scale
|
|
/// or pre-defined resolution points to offer higher precision at room temperatures
|
|
/// and lower precision at extreme temperatures (1000K).
|
|
/// </para>
|
|
/// </remarks>
|
|
[Serializable, NetSerializable]
|
|
public struct ThermalByte : IEquatable<ThermalByte>
|
|
{
|
|
public const float TempMinimum = 0f;
|
|
public const float TempMaximum = 1000f;
|
|
public const int TempResolution = 250;
|
|
|
|
public const byte ReservedFuture0 = 251;
|
|
public const byte ReservedFuture1 = 252;
|
|
public const byte ReservedFuture2 = 253;
|
|
public const byte StateVacuum = 254;
|
|
public const byte AtmosImpossible = 255;
|
|
|
|
public const float TempDegreeResolution = (TempMaximum - TempMinimum) / TempResolution;
|
|
public const float TempToByteFactor = TempResolution / (TempMaximum - TempMinimum);
|
|
|
|
private byte _coreValue;
|
|
|
|
public ThermalByte(float temperatureKelvin)
|
|
{
|
|
SetTemperature(temperatureKelvin);
|
|
}
|
|
|
|
public ThermalByte()
|
|
{
|
|
_coreValue = AtmosImpossible;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set temperature of air in this in Kelvin.
|
|
/// </summary>
|
|
public void SetTemperature(float temperatureKelvin)
|
|
{
|
|
var clampedTemp = Math.Clamp(temperatureKelvin, TempMinimum, TempMaximum);
|
|
_coreValue = (byte)((clampedTemp - TempMinimum) * TempResolution / (TempMaximum - TempMinimum));
|
|
}
|
|
|
|
public void SetAtmosIsImpossible()
|
|
{
|
|
_coreValue = AtmosImpossible;
|
|
}
|
|
|
|
public void SetVacuum()
|
|
{
|
|
_coreValue = StateVacuum;
|
|
}
|
|
|
|
public bool IsAtmosImpossible => _coreValue == AtmosImpossible; // Cold space, solid walls
|
|
public bool IsVacuum => _coreValue == StateVacuum;
|
|
public byte Value => _coreValue;
|
|
|
|
/// <summary>
|
|
/// Attempts to get the air temperature in Kelvin.
|
|
/// </summary>
|
|
/// <param name="temperature">The temperature in Kelvin, if the tile has a valid temperature.</param>
|
|
/// <param name="onVacuumReturnTcmb">
|
|
/// If true and the tile is a vacuum, <paramref name="temperature"/> will be set to <see cref="Atmospherics.TCMB"/>
|
|
/// and the method will return <see langword="true"/>.
|
|
/// </param>
|
|
/// <returns>
|
|
/// <see langword="true"/> if the tile contains a valid temperature (including vacuum if <paramref name="onVacuumReturnTcmb"/> is set);
|
|
/// otherwise <see langword="false"/> (e.g., walls).
|
|
/// </returns>
|
|
public readonly bool TryGetTemperature(out float temperature, bool onVacuumReturnTcmb = true)
|
|
{
|
|
switch (_coreValue)
|
|
{
|
|
case AtmosImpossible:
|
|
temperature = 0f;
|
|
return false;
|
|
case StateVacuum when onVacuumReturnTcmb:
|
|
temperature = Atmospherics.TCMB;
|
|
return true;
|
|
case StateVacuum:
|
|
temperature = 0f;
|
|
return false;
|
|
default:
|
|
temperature = (_coreValue * TempDegreeResolution) + TempMinimum;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public bool Equals(ThermalByte other)
|
|
{
|
|
return _coreValue == other._coreValue;
|
|
}
|
|
|
|
public static bool operator ==(ThermalByte left, ThermalByte right)
|
|
{
|
|
return left.Equals(right);
|
|
}
|
|
|
|
public static bool operator !=(ThermalByte left, ThermalByte right)
|
|
{
|
|
return !left.Equals(right);
|
|
}
|
|
|
|
public override bool Equals(object? obj)
|
|
{
|
|
return obj is ThermalByte other && Equals(other);
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
return _coreValue.GetHashCode();
|
|
}
|
|
}
|