Atmospheric network monitor (#32294)

* Updated to latest master version

* Added gas pipe analyzer

* Completed prototype

* Playing with UI display

* Refinement of the main UI

* Renamed gas pipe analyzer to gas pipe sensor

* Added focus network highlighting and map icons for gas pipe sensors

* Added construction graph for gas pipe sensor

* Improved efficiency of atmos pipe and focus pipe network data storage

* Added gas pipe sensor variants

* Fixed gas pipe sensor nav map icon not highlighting on focus

* Rendered pipe lines now get merged together

* Set up appearance handling for the gas pipe sensor, but setting the layers is bugged

* Gas pipe sensor lights turn off when the device is unpowered

* Renamed console

* The gas pipe sensor is now a pipe. Redistributed components between it and its assembly

* AtmosMonitors can now optionally monitor their internal pipe network instead of the surrounding atmosphere

* Massive code clean up

* Added delta states to handle pipe net updates, fixed entity deletion handling

* Nav map blip data has been replaced with prototypes

* Nav map blip fixes

* Nav map colors are now set by the console component

* Made the nav map more responsive to changes in focus

* Updated nav map icons

* Reverted unnecessary namespace changes

* Code tidy up

* Updated sprites and construction graph for gas pipe sensor

* Updated localization files

* Misc bug fixes

* Added missing comment

* Fixed issue with the circuit board for the monitor

* Embellished the background of the console network entries

* Updated console to account for PR #32273

* Removed gas pipe sensor

* Fixing merge conflict

* Update

* Addressing reviews part 1

* Addressing review part 2

* Addressing reviews part 3

* Removed unnecessary references

* Side panel values will be grayed out if there is no gas present in the pipe network

* Declaring colors at the start of some files

* Added a colored stripe to the side of the atmos network entries

* Fixed an issue with pipe sensor blip coloration

* Fixed delay that occurs when toggling gas sensors on/off
This commit is contained in:
chromiumboy
2024-12-16 21:53:17 -06:00
committed by GitHub
parent f4765260cb
commit 27e59d35fb
47 changed files with 2486 additions and 66 deletions

View File

@@ -145,6 +145,22 @@ namespace Content.Shared.Atmos
/// </summary>
public const float SpaceHeatCapacity = 7000f;
/// <summary>
/// Dictionary of chemical abbreviations for <see cref="Gas"/>
/// </summary>
public static Dictionary<Gas, string> GasAbbreviations = new Dictionary<Gas, string>()
{
[Gas.Ammonia] = Loc.GetString("gas-ammonia-abbreviation"),
[Gas.CarbonDioxide] = Loc.GetString("gas-carbon-dioxide-abbreviation"),
[Gas.Frezon] = Loc.GetString("gas-frezon-abbreviation"),
[Gas.Nitrogen] = Loc.GetString("gas-nitrogen-abbreviation"),
[Gas.NitrousOxide] = Loc.GetString("gas-nitrous-oxide-abbreviation"),
[Gas.Oxygen] = Loc.GetString("gas-oxygen-abbreviation"),
[Gas.Plasma] = Loc.GetString("gas-plasma-abbreviation"),
[Gas.Tritium] = Loc.GetString("gas-tritium-abbreviation"),
[Gas.WaterVapor] = Loc.GetString("gas-water-vapor-abbreviation"),
};
#region Excited Groups
/// <summary>

View File

@@ -0,0 +1,10 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Atmos.Components;
/// <summary>
/// Entities with component will be queried against for their
/// atmos monitoring data on atmos monitoring consoles
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class GasPipeSensorComponent : Component;

View File

@@ -0,0 +1,235 @@
using Content.Shared.Atmos.Consoles;
using Content.Shared.Pinpointer;
using Content.Shared.Prototypes;
using Robust.Shared.GameStates;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Timing;
namespace Content.Shared.Atmos.Components;
/// <summary>
/// Entities capable of opening the atmos monitoring console UI
/// require this component to function correctly
/// </summary>
[RegisterComponent, NetworkedComponent]
[Access(typeof(SharedAtmosMonitoringConsoleSystem))]
public sealed partial class AtmosMonitoringConsoleComponent : Component
{
/*
* Don't need DataFields as this can be reconstructed
*/
/// <summary>
/// A dictionary of the all the nav map chunks that contain anchored atmos pipes
/// </summary>
[ViewVariables]
public Dictionary<Vector2i, AtmosPipeChunk> AtmosPipeChunks = new();
/// <summary>
/// A list of all the atmos devices that will be used to populate the nav map
/// </summary>
[ViewVariables]
public Dictionary<NetEntity, AtmosDeviceNavMapData> AtmosDevices = new();
/// <summary>
/// Color of the floor tiles on the nav map screen
/// </summary>
[DataField, ViewVariables]
public Color NavMapTileColor;
/// <summary>
/// Color of the wall lines on the nav map screen
/// </summary>
[DataField, ViewVariables]
public Color NavMapWallColor;
/// <summary>
/// The next time this component is dirtied, it will force the full state
/// to be sent to the client, instead of just the delta state
/// </summary>
[ViewVariables]
public bool ForceFullUpdate = false;
}
[Serializable, NetSerializable]
public struct AtmosPipeChunk(Vector2i origin)
{
/// <summary>
/// Chunk position
/// </summary>
[ViewVariables]
public readonly Vector2i Origin = origin;
/// <summary>
/// Bitmask look up for atmos pipes, 1 for occupied and 0 for empty.
/// Indexed by the color hexcode of the pipe
/// </summary>
[ViewVariables]
public Dictionary<(int, string), ulong> AtmosPipeData = new();
/// <summary>
/// The last game tick that the chunk was updated
/// </summary>
[NonSerialized]
public GameTick LastUpdate;
}
[Serializable, NetSerializable]
public struct AtmosDeviceNavMapData
{
/// <summary>
/// The entity in question
/// </summary>
public NetEntity NetEntity;
/// <summary>
/// Location of the entity
/// </summary>
public NetCoordinates NetCoordinates;
/// <summary>
/// The associated pipe network ID
/// </summary>
public int NetId = -1;
/// <summary>
/// Prototype ID for the nav map blip
/// </summary>
public ProtoId<NavMapBlipPrototype> NavMapBlip;
/// <summary>
/// Direction of the entity
/// </summary>
public Direction Direction;
/// <summary>
/// Color of the attached pipe
/// </summary>
public Color PipeColor;
/// <summary>
/// Populate the atmos monitoring console nav map with a single entity
/// </summary>
public AtmosDeviceNavMapData(NetEntity netEntity, NetCoordinates netCoordinates, int netId, ProtoId<NavMapBlipPrototype> navMapBlip, Direction direction, Color pipeColor)
{
NetEntity = netEntity;
NetCoordinates = netCoordinates;
NetId = netId;
NavMapBlip = navMapBlip;
Direction = direction;
PipeColor = pipeColor;
}
}
[Serializable, NetSerializable]
public sealed class AtmosMonitoringConsoleBoundInterfaceState : BoundUserInterfaceState
{
/// <summary>
/// A list of all entries to populate the UI with
/// </summary>
public AtmosMonitoringConsoleEntry[] AtmosNetworks;
/// <summary>
/// Sends data from the server to the client to populate the atmos monitoring console UI
/// </summary>
public AtmosMonitoringConsoleBoundInterfaceState(AtmosMonitoringConsoleEntry[] atmosNetworks)
{
AtmosNetworks = atmosNetworks;
}
}
[Serializable, NetSerializable]
public struct AtmosMonitoringConsoleEntry
{
/// <summary>
/// The entity in question
/// </summary>
public NetEntity NetEntity;
/// <summary>
/// Location of the entity
/// </summary>
public NetCoordinates Coordinates;
/// <summary>
/// The associated pipe network ID
/// </summary>
public int NetId = -1;
/// <summary>
/// Localised device name
/// </summary>
public string EntityName;
/// <summary>
/// Device network address
/// </summary>
public string Address;
/// <summary>
/// Temperature (K)
/// </summary>
public float TemperatureData;
/// <summary>
/// Pressure (kPA)
/// </summary>
public float PressureData;
/// <summary>
/// Total number of mols of gas
/// </summary>
public float TotalMolData;
/// <summary>
/// Mol and percentage for all detected gases
/// </summary>
public Dictionary<Gas, float> GasData = new();
/// <summary>
/// The color to be associated with the pipe network
/// </summary>
public Color Color;
/// <summary>
/// Indicates whether the entity is powered
/// </summary>
public bool IsPowered = true;
/// <summary>
/// Used to populate the atmos monitoring console UI with data from a single air alarm
/// </summary>
public AtmosMonitoringConsoleEntry
(NetEntity entity,
NetCoordinates coordinates,
int netId,
string entityName,
string address)
{
NetEntity = entity;
Coordinates = coordinates;
NetId = netId;
EntityName = entityName;
Address = address;
}
}
public enum AtmosPipeChunkDataFacing : byte
{
// Values represent bit shift offsets when retrieving data in the tile array.
North = 0,
South = SharedNavMapSystem.ArraySize,
East = SharedNavMapSystem.ArraySize * 2,
West = SharedNavMapSystem.ArraySize * 3,
}
/// <summary>
/// UI key associated with the atmos monitoring console
/// </summary>
[Serializable, NetSerializable]
public enum AtmosMonitoringConsoleUiKey
{
Key
}

View File

@@ -0,0 +1,21 @@
using Content.Shared.Prototypes;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared.Atmos.Components;
/// <summary>
/// Entities with this component appear on the
/// nav maps of atmos monitoring consoles
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class AtmosMonitoringConsoleDeviceComponent : Component
{
/// <summary>
/// Prototype ID for the blip used to represent this
/// entity on the atmos monitoring console nav map.
/// If null, no blip is drawn (i.e., null for pipes)
/// </summary>
[DataField, ViewVariables]
public ProtoId<NavMapBlipPrototype>? NavMapBlip = null;
}

View File

@@ -0,0 +1,115 @@
using Content.Shared.Atmos.Components;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
namespace Content.Shared.Atmos.Consoles;
public abstract class SharedAtmosMonitoringConsoleSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AtmosMonitoringConsoleComponent, ComponentGetState>(OnGetState);
}
private void OnGetState(EntityUid uid, AtmosMonitoringConsoleComponent component, ref ComponentGetState args)
{
Dictionary<Vector2i, Dictionary<(int, string), ulong>> chunks;
// Should this be a full component state or a delta-state?
if (args.FromTick <= component.CreationTick || component.ForceFullUpdate)
{
component.ForceFullUpdate = false;
// Full state
chunks = new(component.AtmosPipeChunks.Count);
foreach (var (origin, chunk) in component.AtmosPipeChunks)
{
chunks.Add(origin, chunk.AtmosPipeData);
}
args.State = new AtmosMonitoringConsoleState(chunks, component.AtmosDevices);
return;
}
chunks = new();
foreach (var (origin, chunk) in component.AtmosPipeChunks)
{
if (chunk.LastUpdate < args.FromTick)
continue;
chunks.Add(origin, chunk.AtmosPipeData);
}
args.State = new AtmosMonitoringConsoleDeltaState(chunks, component.AtmosDevices, new(component.AtmosPipeChunks.Keys));
}
#region: System messages
[Serializable, NetSerializable]
protected sealed class AtmosMonitoringConsoleState(
Dictionary<Vector2i, Dictionary<(int, string), ulong>> chunks,
Dictionary<NetEntity, AtmosDeviceNavMapData> atmosDevices)
: ComponentState
{
public Dictionary<Vector2i, Dictionary<(int, string), ulong>> Chunks = chunks;
public Dictionary<NetEntity, AtmosDeviceNavMapData> AtmosDevices = atmosDevices;
}
[Serializable, NetSerializable]
protected sealed class AtmosMonitoringConsoleDeltaState(
Dictionary<Vector2i, Dictionary<(int, string), ulong>> modifiedChunks,
Dictionary<NetEntity, AtmosDeviceNavMapData> atmosDevices,
HashSet<Vector2i> allChunks)
: ComponentState, IComponentDeltaState<AtmosMonitoringConsoleState>
{
public Dictionary<Vector2i, Dictionary<(int, string), ulong>> ModifiedChunks = modifiedChunks;
public Dictionary<NetEntity, AtmosDeviceNavMapData> AtmosDevices = atmosDevices;
public HashSet<Vector2i> AllChunks = allChunks;
public void ApplyToFullState(AtmosMonitoringConsoleState state)
{
foreach (var key in state.Chunks.Keys)
{
if (!AllChunks!.Contains(key))
state.Chunks.Remove(key);
}
foreach (var (index, data) in ModifiedChunks)
{
state.Chunks[index] = new Dictionary<(int, string), ulong>(data);
}
state.AtmosDevices.Clear();
foreach (var (nuid, atmosDevice) in AtmosDevices)
{
state.AtmosDevices.Add(nuid, atmosDevice);
}
}
public AtmosMonitoringConsoleState CreateNewFullState(AtmosMonitoringConsoleState state)
{
var chunks = new Dictionary<Vector2i, Dictionary<(int, string), ulong>>(state.Chunks.Count);
foreach (var (index, data) in state.Chunks)
{
if (!AllChunks!.Contains(index))
continue;
if (ModifiedChunks.ContainsKey(index))
chunks[index] = new Dictionary<(int, string), ulong>(ModifiedChunks[index]);
else
chunks[index] = new Dictionary<(int, string), ulong>(state.Chunks[index]);
}
return new AtmosMonitoringConsoleState(chunks, new(AtmosDevices));
}
}
#endregion
}

View File

@@ -0,0 +1,42 @@
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Shared.Prototypes;
[Prototype("navMapBlip")]
public sealed partial class NavMapBlipPrototype : IPrototype
{
[ViewVariables]
[IdDataField]
public string ID { get; private set; } = default!;
/// <summary>
/// Sets whether the associated entity can be selected when the blip is clicked
/// </summary>
[DataField]
public bool Selectable = false;
/// <summary>
/// Sets whether the blips is always blinking
/// </summary>
[DataField]
public bool Blinks = false;
/// <summary>
/// Sets the color of the blip
/// </summary>
[DataField]
public Color Color { get; private set; } = Color.LightGray;
/// <summary>
/// Texture paths associated with the blip
/// </summary>
[DataField]
public ResPath[]? TexturePaths { get; private set; }
/// <summary>
/// Sets the UI scaling of the blip
/// </summary>
[DataField]
public float Scale { get; private set; } = 1f;
}