Atmos pipe rework (#3833)
* Initial * Cleanup a bunch of things * some changes dunno * RequireAnchored * a * stuff * more work * Lots of progress * delete pipe visualizer * a * b * pipenet and pipenode cleanup * Fixes * Adds GasValve * Adds GasMiner * Fix stuff, maybe? * More fixes * Ignored components on the client * Adds thermomachine behavior, change a bunch of stuff * Remove Anchored * some work, but it's shitcode * significantly more ECS * ECS AtmosDevices * Cleanup * fix appearance * when the pipe direction is sus * Gas tanks and canisters * pipe anchoring and stuff * coding is my passion * Unsafe pipes take longer to unanchor * turns out we're no longer using eris canisters * Gas canister inserted tank appearance, improvements * Work on a bunch of appearances * Scrubber appearance * Reorganize AtmosphereSystem.Piping into a bunch of different systems * Appearance for vent/scrubber/pump turns off when leaving atmosphere * ThermoMachine appearance * Cleanup gas tanks * Remove passive gate unused imports * remove old canister UI functionality * PipeNode environment air, make everything use AssumeAir instead of merging manually * a * Reorganize atmos to follow new structure * ????? * Canister UI, restructure client * Restructure shared * Fix build tho * listen, at least the canister UI works entirely... * fix build : ) * Atmos device prototypes have names and descriptions * gas canister ui slider doesn't jitter * trinary prototypes * sprite for miners * ignore components * fix YAML * Fix port system doing useless thing * Fix build * fix thinking moment * fix build again because * canister direction * pipenode is a word * GasTank Air will throw on invalid states * fix build.... * Unhardcode volume pump thresholds * Volume pump and filter take time into account * Rename Join/Leave atmosphere events to AtmosDeviceEnabled/Disabled Event * Gas tank node volume is set by initial mixtuer * I love node container
This commit is contained in:
committed by
GitHub
parent
cfc3f2e7fc
commit
a2b737d945
72
Content.Client/Atmos/Components/GasAnalyzerComponent.cs
Normal file
72
Content.Client/Atmos/Components/GasAnalyzerComponent.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using Content.Client.Items.Components;
|
||||
using Content.Client.Message;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Client.Atmos.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
internal class GasAnalyzerComponent : SharedGasAnalyzerComponent, IItemStatus
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite)] private bool _uiUpdateNeeded;
|
||||
[ViewVariables] private GasAnalyzerDanger Danger { get; set; }
|
||||
|
||||
Control IItemStatus.MakeControl()
|
||||
{
|
||||
return new StatusControl(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
|
||||
{
|
||||
if (curState is not GasAnalyzerComponentState state)
|
||||
return;
|
||||
|
||||
Danger = state.Danger;
|
||||
_uiUpdateNeeded = true;
|
||||
}
|
||||
|
||||
private sealed class StatusControl : Control
|
||||
{
|
||||
private readonly GasAnalyzerComponent _parent;
|
||||
private readonly RichTextLabel _label;
|
||||
|
||||
public StatusControl(GasAnalyzerComponent parent)
|
||||
{
|
||||
_parent = parent;
|
||||
_label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } };
|
||||
AddChild(_label);
|
||||
|
||||
parent._uiUpdateNeeded = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
if (!_parent._uiUpdateNeeded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_parent._uiUpdateNeeded = false;
|
||||
|
||||
var color = _parent.Danger switch
|
||||
{
|
||||
GasAnalyzerDanger.Warning => "orange",
|
||||
GasAnalyzerDanger.Hazard => "red",
|
||||
_ => "green",
|
||||
};
|
||||
|
||||
_label.SetMarkup(Loc.GetString("itemstatus-pressure-warn", ("color", color), ("danger", _parent.Danger)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
106
Content.Client/Atmos/EntitySystems/AtmosDebugOverlaySystem.cs
Normal file
106
Content.Client/Atmos/EntitySystems/AtmosDebugOverlaySystem.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using Content.Client.Atmos.Overlays;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
using Content.Shared.GameTicking;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Content.Client.Atmos.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
internal sealed class AtmosDebugOverlaySystem : SharedAtmosDebugOverlaySystem, IResettingEntitySystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
private readonly Dictionary<GridId, AtmosDebugOverlayMessage> _tileData =
|
||||
new();
|
||||
|
||||
// Configuration set by debug commands and used by AtmosDebugOverlay {
|
||||
/// <summary>Value source for display</summary>
|
||||
public AtmosDebugOverlayMode CfgMode;
|
||||
/// <summary>This is subtracted from value (applied before CfgScale)</summary>
|
||||
public float CfgBase = 0;
|
||||
/// <summary>The value is divided by this (applied after CfgBase)</summary>
|
||||
public float CfgScale = Atmospherics.MolesCellStandard * 2;
|
||||
/// <summary>Gas ID used by GasMoles mode</summary>
|
||||
public int CfgSpecificGas = 0;
|
||||
/// <summary>Uses black-to-white interpolation (as opposed to red-green-blue) for colourblind users</summary>
|
||||
public bool CfgCBM = false;
|
||||
// }
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeNetworkEvent<AtmosDebugOverlayMessage>(HandleAtmosDebugOverlayMessage);
|
||||
SubscribeNetworkEvent<AtmosDebugOverlayDisableMessage>(HandleAtmosDebugOverlayDisableMessage);
|
||||
|
||||
_mapManager.OnGridRemoved += OnGridRemoved;
|
||||
|
||||
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
||||
if(!overlayManager.HasOverlay<AtmosDebugOverlay>())
|
||||
overlayManager.AddOverlay(new AtmosDebugOverlay());
|
||||
}
|
||||
|
||||
private void HandleAtmosDebugOverlayMessage(AtmosDebugOverlayMessage message)
|
||||
{
|
||||
_tileData[message.GridId] = message;
|
||||
}
|
||||
|
||||
private void HandleAtmosDebugOverlayDisableMessage(AtmosDebugOverlayDisableMessage ev)
|
||||
{
|
||||
_tileData.Clear();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_mapManager.OnGridRemoved -= OnGridRemoved;
|
||||
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
||||
if(!overlayManager.HasOverlay<GasTileOverlay>())
|
||||
overlayManager.RemoveOverlay<GasTileOverlay>();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_tileData.Clear();
|
||||
}
|
||||
|
||||
private void OnGridRemoved(MapId mapId, GridId gridId)
|
||||
{
|
||||
if (_tileData.ContainsKey(gridId))
|
||||
{
|
||||
_tileData.Remove(gridId);
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasData(GridId gridId)
|
||||
{
|
||||
return _tileData.ContainsKey(gridId);
|
||||
}
|
||||
|
||||
public AtmosDebugOverlayData? GetData(GridId gridIndex, Vector2i 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)];
|
||||
}
|
||||
}
|
||||
|
||||
internal enum AtmosDebugOverlayMode : byte
|
||||
{
|
||||
TotalMoles,
|
||||
GasMoles,
|
||||
Temperature
|
||||
}
|
||||
}
|
||||
10
Content.Client/Atmos/EntitySystems/AtmosphereSystem.cs
Normal file
10
Content.Client/Atmos/EntitySystems/AtmosphereSystem.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Client.Atmos.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class AtmosphereSystem : SharedAtmosphereSystem
|
||||
{
|
||||
}
|
||||
}
|
||||
206
Content.Client/Atmos/EntitySystems/GasTileOverlaySystem.cs
Normal file
206
Content.Client/Atmos/EntitySystems/GasTileOverlaySystem.cs
Normal file
@@ -0,0 +1,206 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Client.Atmos.Overlays;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Atmos.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
internal sealed class GasTileOverlaySystem : SharedGasTileOverlaySystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
|
||||
// Gas overlays
|
||||
private readonly float[] _timer = new float[Atmospherics.TotalNumberOfGases];
|
||||
private readonly float[][] _frameDelays = new float[Atmospherics.TotalNumberOfGases][];
|
||||
private readonly int[] _frameCounter = new int[Atmospherics.TotalNumberOfGases];
|
||||
private readonly Texture[][] _frames = new Texture[Atmospherics.TotalNumberOfGases][];
|
||||
|
||||
// Fire overlays
|
||||
private const int FireStates = 3;
|
||||
private const string FireRsiPath = "/Textures/Effects/fire.rsi";
|
||||
|
||||
private readonly float[] _fireTimer = new float[FireStates];
|
||||
private readonly float[][] _fireFrameDelays = new float[FireStates][];
|
||||
private readonly int[] _fireFrameCounter = new int[FireStates];
|
||||
private readonly Texture[][] _fireFrames = new Texture[FireStates][];
|
||||
|
||||
private readonly Dictionary<GridId, Dictionary<Vector2i, GasOverlayChunk>> _tileData =
|
||||
new();
|
||||
|
||||
private AtmosphereSystem _atmosphereSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeNetworkEvent<GasOverlayMessage>(HandleGasOverlayMessage);
|
||||
_mapManager.OnGridRemoved += OnGridRemoved;
|
||||
|
||||
_atmosphereSystem = Get<AtmosphereSystem>();
|
||||
|
||||
for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
|
||||
{
|
||||
var overlay = _atmosphereSystem.GetOverlay(i);
|
||||
switch (overlay)
|
||||
{
|
||||
case SpriteSpecifier.Rsi animated:
|
||||
var rsi = _resourceCache.GetResource<RSIResource>(animated.RsiPath).RSI;
|
||||
var stateId = animated.RsiState;
|
||||
|
||||
if(!rsi.TryGetState(stateId, out var state)) continue;
|
||||
|
||||
_frames[i] = state.GetFrames(RSI.State.Direction.South);
|
||||
_frameDelays[i] = state.GetDelays();
|
||||
_frameCounter[i] = 0;
|
||||
break;
|
||||
case SpriteSpecifier.Texture texture:
|
||||
_frames[i] = new[] {texture.Frame0()};
|
||||
_frameDelays[i] = Array.Empty<float>();
|
||||
break;
|
||||
case null:
|
||||
_frames[i] = Array.Empty<Texture>();
|
||||
_frameDelays[i] = Array.Empty<float>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var fire = _resourceCache.GetResource<RSIResource>(FireRsiPath).RSI;
|
||||
|
||||
for (var i = 0; i < FireStates; i++)
|
||||
{
|
||||
if (!fire.TryGetState((i+1).ToString(), out var state))
|
||||
throw new ArgumentOutOfRangeException($"Fire RSI doesn't have state \"{i}\"!");
|
||||
|
||||
_fireFrames[i] = state.GetFrames(RSI.State.Direction.South);
|
||||
_fireFrameDelays[i] = state.GetDelays();
|
||||
_fireFrameCounter[i] = 0;
|
||||
}
|
||||
|
||||
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
||||
if(!overlayManager.HasOverlay<GasTileOverlay>())
|
||||
overlayManager.AddOverlay(new GasTileOverlay());
|
||||
}
|
||||
|
||||
private void HandleGasOverlayMessage(GasOverlayMessage message)
|
||||
{
|
||||
foreach (var (indices, data) in message.OverlayData)
|
||||
{
|
||||
var chunk = GetOrCreateChunk(message.GridId, indices);
|
||||
chunk.Update(data, indices);
|
||||
}
|
||||
}
|
||||
|
||||
// Slightly different to the server-side system version
|
||||
private GasOverlayChunk GetOrCreateChunk(GridId gridId, Vector2i indices)
|
||||
{
|
||||
if (!_tileData.TryGetValue(gridId, out var chunks))
|
||||
{
|
||||
chunks = new Dictionary<Vector2i, GasOverlayChunk>();
|
||||
_tileData[gridId] = chunks;
|
||||
}
|
||||
|
||||
var chunkIndices = GetGasChunkIndices(indices);
|
||||
|
||||
if (!chunks.TryGetValue(chunkIndices, out var chunk))
|
||||
{
|
||||
chunk = new GasOverlayChunk(gridId, chunkIndices);
|
||||
chunks[chunkIndices] = chunk;
|
||||
}
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_mapManager.OnGridRemoved -= OnGridRemoved;
|
||||
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
||||
if(!overlayManager.HasOverlay<GasTileOverlay>())
|
||||
overlayManager.RemoveOverlay<GasTileOverlay>();
|
||||
}
|
||||
|
||||
private void OnGridRemoved(MapId mapId, GridId gridId)
|
||||
{
|
||||
if (_tileData.ContainsKey(gridId))
|
||||
{
|
||||
_tileData.Remove(gridId);
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasData(GridId gridId)
|
||||
{
|
||||
return _tileData.ContainsKey(gridId);
|
||||
}
|
||||
|
||||
public IEnumerable<(Texture, Color)> GetOverlays(GridId gridIndex, Vector2i indices)
|
||||
{
|
||||
if (!_tileData.TryGetValue(gridIndex, out var chunks))
|
||||
yield break;
|
||||
|
||||
var chunkIndex = GetGasChunkIndices(indices);
|
||||
if (!chunks.TryGetValue(chunkIndex, out var chunk))
|
||||
yield break;
|
||||
|
||||
var overlays = chunk.GetData(indices);
|
||||
|
||||
if (overlays.Gas == null)
|
||||
yield break;
|
||||
|
||||
var fire = overlays.FireState != 0;
|
||||
|
||||
foreach (var gasData in overlays.Gas)
|
||||
{
|
||||
var frames = _frames[gasData.Index];
|
||||
yield return (frames[_frameCounter[gasData.Index]], Color.White.WithAlpha(gasData.Opacity));
|
||||
}
|
||||
|
||||
if (fire)
|
||||
{
|
||||
var state = overlays.FireState - 1;
|
||||
var frames = _fireFrames[state];
|
||||
// TODO ATMOS Set color depending on temperature
|
||||
yield return (frames[_fireFrameCounter[state]], Color.White);
|
||||
}
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
|
||||
for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
|
||||
{
|
||||
var delays = _frameDelays[i];
|
||||
if (delays.Length == 0) continue;
|
||||
|
||||
var frameCount = _frameCounter[i];
|
||||
_timer[i] += frameTime;
|
||||
if (!(_timer[i] >= delays[frameCount])) continue;
|
||||
_timer[i] = 0f;
|
||||
_frameCounter[i] = (frameCount + 1) % _frames[i].Length;
|
||||
}
|
||||
|
||||
for (var i = 0; i < FireStates; i++)
|
||||
{
|
||||
var delays = _fireFrameDelays[i];
|
||||
if (delays.Length == 0) continue;
|
||||
|
||||
var frameCount = _fireFrameCounter[i];
|
||||
_fireTimer[i] += frameTime;
|
||||
if (!(_fireTimer[i] >= delays[frameCount])) continue;
|
||||
_fireTimer[i] = 0f;
|
||||
_fireFrameCounter[i] = (frameCount + 1) % _fireFrames[i].Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,14 @@
|
||||
using Content.Client.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.EntitySystems.Atmos;
|
||||
using Content.Client.Atmos.EntitySystems;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Enums;
|
||||
using System;
|
||||
|
||||
namespace Content.Client.Atmos
|
||||
namespace Content.Client.Atmos.Overlays
|
||||
{
|
||||
public class AtmosDebugOverlay : Overlay
|
||||
{
|
||||
@@ -1,12 +1,12 @@
|
||||
using Content.Client.GameObjects.EntitySystems;
|
||||
using Robust.Shared.Enums;
|
||||
using Content.Client.Atmos.EntitySystems;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Content.Client.Atmos
|
||||
namespace Content.Client.Atmos.Overlays
|
||||
{
|
||||
public class GasTileOverlay : Overlay
|
||||
{
|
||||
39
Content.Client/Atmos/Piping/EnabledAtmosDeviceVisualizer.cs
Normal file
39
Content.Client/Atmos/Piping/EnabledAtmosDeviceVisualizer.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Content.Client.Atmos.Piping
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public abstract class EnabledAtmosDeviceVisualizer : AppearanceVisualizer
|
||||
{
|
||||
[DataField("enabledState")]
|
||||
private string _enabledState = string.Empty;
|
||||
protected abstract object LayerMap { get; }
|
||||
protected abstract Enum DataKey { get; }
|
||||
|
||||
public override void InitializeEntity(IEntity entity)
|
||||
{
|
||||
base.InitializeEntity(entity);
|
||||
|
||||
if (!entity.TryGetComponent(out ISpriteComponent? sprite))
|
||||
return;
|
||||
|
||||
sprite.LayerMapSet(LayerMap, sprite.AddLayerState(_enabledState));
|
||||
sprite.LayerSetVisible(LayerMap, false);
|
||||
}
|
||||
|
||||
public override void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
base.OnChangeData(component);
|
||||
|
||||
if (!component.Owner.TryGetComponent(out ISpriteComponent? sprite))
|
||||
return;
|
||||
|
||||
if(component.TryGetData(DataKey, out bool enabled))
|
||||
sprite.LayerSetVisible(LayerMap, enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Content.Client/Atmos/Piping/OutletInjectorVisualizer.cs
Normal file
18
Content.Client/Atmos/Piping/OutletInjectorVisualizer.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using Content.Shared.Atmos.Piping;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Client.Atmos.Piping
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class OutletInjectorVisualizer : EnabledAtmosDeviceVisualizer
|
||||
{
|
||||
protected override object LayerMap => Layers.Enabled;
|
||||
protected override Enum DataKey => OutletInjectorVisuals.Enabled;
|
||||
|
||||
enum Layers
|
||||
{
|
||||
Enabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Content.Client/Atmos/Piping/PassiveVentVisualizer.cs
Normal file
18
Content.Client/Atmos/Piping/PassiveVentVisualizer.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using Content.Shared.Atmos.Piping;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Client.Atmos.Piping
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class PassiveVentVisualizer : EnabledAtmosDeviceVisualizer
|
||||
{
|
||||
protected override object LayerMap => Layers.Enabled;
|
||||
protected override Enum DataKey => PassiveVentVisuals.Enabled;
|
||||
|
||||
enum Layers
|
||||
{
|
||||
Enabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
86
Content.Client/Atmos/Piping/PipeConnectorVisualizer.cs
Normal file
86
Content.Client/Atmos/Piping/PipeConnectorVisualizer.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using Content.Shared.Atmos;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Content.Client.Atmos.Piping
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class PipeConnectorVisualizer : AppearanceVisualizer, ISerializationHooks
|
||||
{
|
||||
[DataField("rsi")]
|
||||
private string _rsi = "Constructible/Atmos/pipe.rsi";
|
||||
|
||||
[DataField("baseState")]
|
||||
private string _baseState = "pipeConnector";
|
||||
|
||||
private RSI? _connectorRsi;
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
var rsiString = SharedSpriteComponent.TextureRoot / _rsi;
|
||||
var resourceCache = IoCManager.Resolve<IResourceCache>();
|
||||
|
||||
if (resourceCache.TryGetResource(rsiString, out RSIResource? rsi))
|
||||
_connectorRsi = rsi.RSI;
|
||||
else
|
||||
Logger.Error($"{nameof(PipeConnectorVisualizer)} could not load to load RSI {rsiString}.");
|
||||
}
|
||||
|
||||
public override void InitializeEntity(IEntity entity)
|
||||
{
|
||||
base.InitializeEntity(entity);
|
||||
|
||||
if (!entity.TryGetComponent<ISpriteComponent>(out var sprite))
|
||||
return;
|
||||
|
||||
if (_connectorRsi == null)
|
||||
return;
|
||||
|
||||
foreach (Layer layerKey in Enum.GetValues(typeof(Layer)))
|
||||
{
|
||||
sprite.LayerMapReserveBlank(layerKey);
|
||||
var layer = sprite.LayerMapGet(layerKey);
|
||||
sprite.LayerSetRSI(layer, _connectorRsi);
|
||||
var layerState = _baseState + ((PipeDirection) layerKey).ToString();
|
||||
sprite.LayerSetState(layer, layerState);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
base.OnChangeData(component);
|
||||
|
||||
if (!component.Owner.TryGetComponent<ISpriteComponent>(out var sprite))
|
||||
return;
|
||||
|
||||
if (!component.TryGetData(PipeVisuals.VisualState, out PipeVisualState state))
|
||||
return;
|
||||
|
||||
foreach (Layer layerKey in Enum.GetValues(typeof(Layer)))
|
||||
{
|
||||
var dir = (PipeDirection) layerKey;
|
||||
var layerVisible = state.ConnectedDirections.HasDirection(dir);
|
||||
|
||||
var layer = sprite.LayerMapGet(layerKey);
|
||||
sprite.LayerSetVisible(layer, layerVisible);
|
||||
}
|
||||
}
|
||||
|
||||
private enum Layer : byte
|
||||
{
|
||||
NorthConnection = PipeDirection.North,
|
||||
SouthConnection = PipeDirection.South,
|
||||
EastConnection = PipeDirection.East,
|
||||
WestConnection = PipeDirection.West,
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Content.Client/Atmos/Piping/PressurePumpVisualizer.cs
Normal file
18
Content.Client/Atmos/Piping/PressurePumpVisualizer.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using Content.Shared.Atmos.Piping;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Client.Atmos.Piping
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class PressurePumpVisualizer : EnabledAtmosDeviceVisualizer
|
||||
{
|
||||
protected override object LayerMap => Layers.Enabled;
|
||||
protected override Enum DataKey => PressurePumpVisuals.Enabled;
|
||||
|
||||
enum Layers
|
||||
{
|
||||
Enabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
52
Content.Client/Atmos/Piping/ScrubberVisualizer.cs
Normal file
52
Content.Client/Atmos/Piping/ScrubberVisualizer.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using Content.Shared.Atmos.Piping.Unary.Visuals;
|
||||
using Content.Shared.Atmos.Visuals;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Atmos.Piping
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class ScrubberVisualizer : AppearanceVisualizer
|
||||
{
|
||||
private string _offState = "scrub_off";
|
||||
private string _scrubState = "scrub_on";
|
||||
private string _siphonState = "scrub_purge";
|
||||
private string _weldedState = "scrub_welded";
|
||||
private string _wideState = "scrub_wide";
|
||||
|
||||
public override void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
base.OnChangeData(component);
|
||||
|
||||
if (!component.Owner.TryGetComponent(out ISpriteComponent? sprite))
|
||||
return;
|
||||
|
||||
if (!component.TryGetData(ScrubberVisuals.State, out ScrubberState state))
|
||||
return;
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case ScrubberState.Off:
|
||||
sprite.LayerSetState(ScrubberVisualLayers.Scrubber, _offState);
|
||||
break;
|
||||
case ScrubberState.Scrub:
|
||||
sprite.LayerSetState(ScrubberVisualLayers.Scrubber, _scrubState);
|
||||
break;
|
||||
case ScrubberState.Siphon:
|
||||
sprite.LayerSetState(ScrubberVisualLayers.Scrubber, _siphonState);
|
||||
break;
|
||||
case ScrubberState.Welded:
|
||||
sprite.LayerSetState(ScrubberVisualLayers.Scrubber, _weldedState);
|
||||
break;
|
||||
case ScrubberState.WideScrub:
|
||||
sprite.LayerSetState(ScrubberVisualLayers.Scrubber, _wideState);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum ScrubberVisualLayers
|
||||
{
|
||||
Scrubber,
|
||||
}
|
||||
}
|
||||
18
Content.Client/Atmos/Piping/ThermoMachineVisualizer.cs
Normal file
18
Content.Client/Atmos/Piping/ThermoMachineVisualizer.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using Content.Shared.Atmos.Piping;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Client.Atmos.Piping
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class ThermoMachineVisualizer : EnabledAtmosDeviceVisualizer
|
||||
{
|
||||
protected override object LayerMap => Layers.Enabled;
|
||||
protected override Enum DataKey => ThermoMachineVisuals.Enabled;
|
||||
|
||||
enum Layers
|
||||
{
|
||||
Enabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
47
Content.Client/Atmos/Piping/VentPumpVisualizer.cs
Normal file
47
Content.Client/Atmos/Piping/VentPumpVisualizer.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using Content.Shared.Atmos.Visuals;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Atmos.Piping
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class VentPumpVisualizer : AppearanceVisualizer
|
||||
{
|
||||
private string _offState = "vent_off";
|
||||
private string _inState = "vent_in";
|
||||
private string _outState = "vent_out";
|
||||
private string _weldedState = "vent_welded";
|
||||
|
||||
public override void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
base.OnChangeData(component);
|
||||
|
||||
if (!component.Owner.TryGetComponent(out ISpriteComponent? sprite))
|
||||
return;
|
||||
|
||||
if (!component.TryGetData(VentPumpVisuals.State, out VentPumpState state))
|
||||
return;
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case VentPumpState.Off:
|
||||
sprite.LayerSetState(VentVisualLayers.Vent, _offState);
|
||||
break;
|
||||
case VentPumpState.In:
|
||||
sprite.LayerSetState(VentVisualLayers.Vent, _inState);
|
||||
break;
|
||||
case VentPumpState.Out:
|
||||
sprite.LayerSetState(VentVisualLayers.Vent, _outState);
|
||||
break;
|
||||
case VentPumpState.Welded:
|
||||
sprite.LayerSetState(VentVisualLayers.Vent, _weldedState);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum VentVisualLayers
|
||||
{
|
||||
Vent,
|
||||
}
|
||||
}
|
||||
43
Content.Client/Atmos/UI/GasAnalyzerBoundUserInterface.cs
Normal file
43
Content.Client/Atmos/UI/GasAnalyzerBoundUserInterface.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using static Content.Shared.Atmos.Components.SharedGasAnalyzerComponent;
|
||||
|
||||
namespace Content.Client.Atmos.UI
|
||||
{
|
||||
public class GasAnalyzerBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
public GasAnalyzerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
|
||||
private GasAnalyzerWindow? _menu;
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = new GasAnalyzerWindow(this);
|
||||
_menu.OnClose += Close;
|
||||
_menu.OpenCentered();
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
_menu?.Populate((GasAnalyzerBoundUserInterfaceState) state);
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
SendMessage(new GasAnalyzerRefreshMessage());
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing) _menu?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
264
Content.Client/Atmos/UI/GasAnalyzerMenu.cs
Normal file
264
Content.Client/Atmos/UI/GasAnalyzerMenu.cs
Normal file
@@ -0,0 +1,264 @@
|
||||
using Content.Client.Resources;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Temperature;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using static Content.Shared.Atmos.Components.SharedGasAnalyzerComponent;
|
||||
|
||||
namespace Content.Client.Atmos.UI
|
||||
{
|
||||
public class GasAnalyzerWindow : BaseWindow
|
||||
{
|
||||
public GasAnalyzerBoundUserInterface Owner { get; }
|
||||
|
||||
private readonly Control _topContainer;
|
||||
private readonly Control _statusContainer;
|
||||
|
||||
private readonly Label _nameLabel;
|
||||
|
||||
public TextureButton CloseButton { get; set; }
|
||||
|
||||
public GasAnalyzerWindow(GasAnalyzerBoundUserInterface owner)
|
||||
{
|
||||
var resourceCache = IoCManager.Resolve<IResourceCache>();
|
||||
|
||||
Owner = owner;
|
||||
var rootContainer = new LayoutContainer {Name = "WireRoot"};
|
||||
AddChild(rootContainer);
|
||||
|
||||
MouseFilter = MouseFilterMode.Stop;
|
||||
|
||||
var panelTex = resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
|
||||
var back = new StyleBoxTexture
|
||||
{
|
||||
Texture = panelTex,
|
||||
Modulate = Color.FromHex("#25252A"),
|
||||
};
|
||||
back.SetPatchMargin(StyleBox.Margin.All, 10);
|
||||
|
||||
var topPanel = new PanelContainer
|
||||
{
|
||||
PanelOverride = back,
|
||||
MouseFilter = MouseFilterMode.Pass
|
||||
};
|
||||
var bottomWrap = new LayoutContainer
|
||||
{
|
||||
Name = "BottomWrap"
|
||||
};
|
||||
|
||||
rootContainer.AddChild(topPanel);
|
||||
rootContainer.AddChild(bottomWrap);
|
||||
|
||||
LayoutContainer.SetAnchorPreset(topPanel, LayoutContainer.LayoutPreset.Wide);
|
||||
LayoutContainer.SetMarginBottom(topPanel, -80);
|
||||
|
||||
LayoutContainer.SetAnchorPreset(bottomWrap, LayoutContainer.LayoutPreset.VerticalCenterWide);
|
||||
LayoutContainer.SetGrowHorizontal(bottomWrap, LayoutContainer.GrowDirection.Both);
|
||||
|
||||
var topContainerWrap = new VBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
(_topContainer = new VBoxContainer()),
|
||||
new Control {MinSize = (0, 110)}
|
||||
}
|
||||
};
|
||||
|
||||
rootContainer.AddChild(topContainerWrap);
|
||||
|
||||
LayoutContainer.SetAnchorPreset(topContainerWrap, LayoutContainer.LayoutPreset.Wide);
|
||||
|
||||
var font = resourceCache.GetFont("/Fonts/Boxfont-round/Boxfont Round.ttf", 13);
|
||||
var fontSmall = resourceCache.GetFont("/Fonts/Boxfont-round/Boxfont Round.ttf", 10);
|
||||
|
||||
Button refreshButton;
|
||||
var topRow = new HBoxContainer
|
||||
{
|
||||
Margin = new Thickness(4, 4, 12, 2),
|
||||
Children =
|
||||
{
|
||||
(_nameLabel = new Label
|
||||
{
|
||||
Text = Loc.GetString("Gas Analyzer"),
|
||||
FontOverride = font,
|
||||
FontColorOverride = StyleNano.NanoGold,
|
||||
VerticalAlignment = VAlignment.Center
|
||||
}),
|
||||
new Control
|
||||
{
|
||||
MinSize = (20, 0),
|
||||
HorizontalExpand = true,
|
||||
},
|
||||
(refreshButton = new Button {Text = "Refresh"}), //TODO: refresh icon?
|
||||
new Control
|
||||
{
|
||||
MinSize = (2, 0),
|
||||
},
|
||||
(CloseButton = new TextureButton
|
||||
{
|
||||
StyleClasses = {SS14Window.StyleClassWindowCloseButton},
|
||||
VerticalAlignment = VAlignment.Center
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
refreshButton.OnPressed += a =>
|
||||
{
|
||||
Owner.Refresh();
|
||||
};
|
||||
|
||||
var middle = new PanelContainer
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#202025")},
|
||||
Children =
|
||||
{
|
||||
(_statusContainer = new VBoxContainer
|
||||
{
|
||||
Margin = new Thickness(8, 8, 4, 4)
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
_topContainer.AddChild(topRow);
|
||||
_topContainer.AddChild(new PanelContainer
|
||||
{
|
||||
MinSize = (0, 2),
|
||||
PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#525252ff")}
|
||||
});
|
||||
_topContainer.AddChild(middle);
|
||||
_topContainer.AddChild(new PanelContainer
|
||||
{
|
||||
MinSize = (0, 2),
|
||||
PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#525252ff")}
|
||||
});
|
||||
CloseButton.OnPressed += _ => Close();
|
||||
SetSize = (300, 200);
|
||||
}
|
||||
|
||||
|
||||
public void Populate(GasAnalyzerBoundUserInterfaceState state)
|
||||
{
|
||||
_statusContainer.RemoveAllChildren();
|
||||
if (state.Error != null)
|
||||
{
|
||||
_statusContainer.AddChild(new Label
|
||||
{
|
||||
Text = Loc.GetString("Error: {0}", state.Error),
|
||||
FontColorOverride = Color.Red
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
_statusContainer.AddChild(new Label
|
||||
{
|
||||
Text = Loc.GetString("Pressure: {0:0.##} kPa", state.Pressure)
|
||||
});
|
||||
_statusContainer.AddChild(new Label
|
||||
{
|
||||
Text = Loc.GetString("Temperature: {0:0.#}K ({1:0.#}°C)", state.Temperature,
|
||||
TemperatureHelpers.KelvinToCelsius(state.Temperature))
|
||||
});
|
||||
// Return here cause all that stuff down there is gas stuff (so we don't get the seperators)
|
||||
if (state.Gases == null || state.Gases.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Seperator
|
||||
_statusContainer.AddChild(new Control
|
||||
{
|
||||
MinSize = new Vector2(0, 10)
|
||||
});
|
||||
|
||||
// Add a table with all the gases
|
||||
var tableKey = new VBoxContainer();
|
||||
var tableVal = new VBoxContainer();
|
||||
_statusContainer.AddChild(new HBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
tableKey,
|
||||
new Control
|
||||
{
|
||||
MinSize = new Vector2(20, 0)
|
||||
},
|
||||
tableVal
|
||||
}
|
||||
});
|
||||
// This is the gas bar thingy
|
||||
var height = 30;
|
||||
var minSize = 24; // This basically allows gases which are too small, to be shown properly
|
||||
var gasBar = new HBoxContainer
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
MinSize = new Vector2(0, height)
|
||||
};
|
||||
// Seperator
|
||||
_statusContainer.AddChild(new Control
|
||||
{
|
||||
MinSize = new Vector2(0, 10)
|
||||
});
|
||||
|
||||
var totalGasAmount = 0f;
|
||||
foreach (var gas in state.Gases)
|
||||
{
|
||||
totalGasAmount += gas.Amount;
|
||||
}
|
||||
|
||||
for (int i = 0; i < state.Gases.Length; i++)
|
||||
{
|
||||
var gas = state.Gases[i];
|
||||
var color = Color.FromHex($"#{gas.Color}", Color.White);
|
||||
// Add to the table
|
||||
tableKey.AddChild(new Label
|
||||
{
|
||||
Text = Loc.GetString(gas.Name)
|
||||
});
|
||||
tableVal.AddChild(new Label
|
||||
{
|
||||
Text = Loc.GetString("{0:0.##} mol", gas.Amount)
|
||||
});
|
||||
|
||||
// Add to the gas bar //TODO: highlight the currently hover one
|
||||
var left = (i == 0) ? 0f : 2f;
|
||||
var right = (i == state.Gases.Length - 1) ? 0f : 2f;
|
||||
gasBar.AddChild(new PanelContainer
|
||||
{
|
||||
ToolTip = Loc.GetString("{0}: {1:0.##} mol ({2:0.#}%)", gas.Name, gas.Amount,
|
||||
(gas.Amount / totalGasAmount) * 100),
|
||||
HorizontalExpand = true,
|
||||
SizeFlagsStretchRatio = gas.Amount,
|
||||
MouseFilter = MouseFilterMode.Pass,
|
||||
PanelOverride = new StyleBoxFlat
|
||||
{
|
||||
BackgroundColor = color,
|
||||
PaddingLeft = left,
|
||||
PaddingRight = right
|
||||
},
|
||||
MinSize = new Vector2(minSize, 0)
|
||||
});
|
||||
}
|
||||
|
||||
_statusContainer.AddChild(gasBar);
|
||||
}
|
||||
|
||||
protected override DragMode GetDragModeFor(Vector2 relativeMousePos)
|
||||
{
|
||||
return DragMode.Move;
|
||||
}
|
||||
|
||||
protected override bool HasPoint(Vector2 point)
|
||||
{
|
||||
// This makes it so our base window won't count for hit tests,
|
||||
// but we will still receive mouse events coming in from Pass mouse filter mode.
|
||||
// So basically, it perfectly shells out the hit tests to the panels we have!
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
86
Content.Client/Atmos/UI/GasCanisterBoundUserInterface.cs
Normal file
86
Content.Client/Atmos/UI/GasCanisterBoundUserInterface.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using Content.Shared.Atmos.Piping.Binary.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Client.Atmos.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a <see cref="GasCanisterWindow"/> and updates it when new server messages are received.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public class GasCanisterBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
|
||||
private GasCanisterWindow? _window;
|
||||
|
||||
public GasCanisterBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_window = new GasCanisterWindow();
|
||||
|
||||
if(State != null)
|
||||
UpdateState(State);
|
||||
|
||||
_window.OpenCentered();
|
||||
|
||||
_window.OnClose += Close;
|
||||
_window.ReleaseValveCloseButtonPressed += OnReleaseValveClosePressed;
|
||||
_window.ReleaseValveOpenButtonPressed += OnReleaseValveOpenPressed;
|
||||
_window.ReleasePressureSliderChanged += OnReleasePressurePressed;
|
||||
_window.TankEjectButtonPressed += OnTankEjectPressed;
|
||||
}
|
||||
|
||||
private void OnTankEjectPressed()
|
||||
{
|
||||
SendMessage(new GasCanisterHoldingTankEjectMessage());
|
||||
}
|
||||
|
||||
private void OnReleasePressurePressed(float value)
|
||||
{
|
||||
SendMessage(new GasCanisterChangeReleasePressureMessage(value));
|
||||
}
|
||||
|
||||
private void OnReleaseValveOpenPressed()
|
||||
{
|
||||
SendMessage(new GasCanisterChangeReleaseValveMessage(true));
|
||||
}
|
||||
|
||||
private void OnReleaseValveClosePressed()
|
||||
{
|
||||
SendMessage(new GasCanisterChangeReleaseValveMessage(false));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the UI state based on server-sent info
|
||||
/// </summary>
|
||||
/// <param name="state"></param>
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
if (_window == null || state is not GasCanisterBoundUserInterfaceState cast)
|
||||
return;
|
||||
|
||||
_window.SetCanisterLabel(cast.CanisterLabel);
|
||||
_window.SetCanisterPressure(cast.CanisterPressure);
|
||||
_window.SetPortStatus(cast.PortStatus);
|
||||
_window.SetTankLabel(cast.TankLabel);
|
||||
_window.SetTankPressure(cast.TankPressure);
|
||||
_window.SetReleasePressureRange(cast.ReleasePressureMin, cast.ReleasePressureMax);
|
||||
_window.SetReleasePressure(cast.ReleasePressure);
|
||||
_window.SetReleaseValve(cast.ReleaseValve);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if (!disposing) return;
|
||||
_window?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
52
Content.Client/Atmos/UI/GasCanisterWindow.xaml
Normal file
52
Content.Client/Atmos/UI/GasCanisterWindow.xaml
Normal file
@@ -0,0 +1,52 @@
|
||||
<SS14Window xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:s="clr-namespace:Content.Client.Stylesheets"
|
||||
MinSize="480 400" Title="Canister">
|
||||
<VBoxContainer Margin="5 5 5 5" SeparationOverride="10">
|
||||
<VBoxContainer VerticalExpand="True">
|
||||
<Label Text="{Loc comp-gas-canister-ui-canister-status}" FontColorOverride="{x:Static s:StyleNano.NanoGold}" StyleClasses="LabelBig"/>
|
||||
<HBoxContainer>
|
||||
<Label Text="{Loc comp-gas-canister-ui-canister-pressure}"/>
|
||||
<Label Name="CanisterPressureLabel" Align="Center" HorizontalExpand="True"/>
|
||||
</HBoxContainer>
|
||||
<HBoxContainer>
|
||||
<Label Text="{Loc comp-gas-canister-ui-port-status}"/>
|
||||
<Label Name="PortStatusLabel" Align="Center" HorizontalExpand="True"/>
|
||||
</HBoxContainer>
|
||||
</VBoxContainer>
|
||||
|
||||
<VBoxContainer VerticalExpand="True">
|
||||
<Label Text="{Loc comp-gas-canister-ui-holding-tank-status}" FontColorOverride="{x:Static s:StyleNano.NanoGold}" StyleClasses="LabelBig"/>
|
||||
<HBoxContainer>
|
||||
<Label Text="{Loc comp-gas-canister-ui-holding-tank-label}"/>
|
||||
<Label Name="TankLabelLabel" Text="{Loc comp-gas-canister-ui-holding-tank-label-empty}" Align="Center" HorizontalExpand="True"/>
|
||||
<Button Name="TankEjectButton" Text="{Loc comp-gas-canister-ui-holding-tank-eject}"/>
|
||||
</HBoxContainer>
|
||||
<HBoxContainer>
|
||||
<Label Text="{Loc comp-gas-canister-ui-holding-tank-pressure}"/>
|
||||
<Label Name="TankPressureLabel" Align="Center" HorizontalExpand="True"/>
|
||||
</HBoxContainer>
|
||||
</VBoxContainer>
|
||||
|
||||
<VBoxContainer VerticalExpand="True">
|
||||
<Label Text="{Loc comp-gas-canister-ui-release-valve-status}" FontColorOverride="{x:Static s:StyleNano.NanoGold}" StyleClasses="LabelBig"/>
|
||||
<HBoxContainer>
|
||||
<VBoxContainer>
|
||||
<Label Text="{Loc comp-gas-canister-ui-release-pressure}"/>
|
||||
<Control VerticalExpand="True"/>
|
||||
</VBoxContainer>
|
||||
<VBoxContainer HorizontalExpand="True" Margin="15 0 0 15" SeparationOverride="5">
|
||||
<Slider Name="ReleasePressureSlider" HorizontalExpand="True"/>
|
||||
<Label Name="ReleasePressureLabel" Align="Center" HorizontalExpand="True"/>
|
||||
</VBoxContainer>
|
||||
</HBoxContainer>
|
||||
<HBoxContainer>
|
||||
<Label Text="{Loc comp-gas-canister-ui-release-valve}"/>
|
||||
<Control HorizontalExpand="True"/>
|
||||
<Button Name="ReleaseValveOpenButton" Text="{Loc comp-gas-canister-ui-release-valve-open}" ToggleMode="True"/>
|
||||
<Button Name="ReleaseValveCloseButton" Text="{Loc comp-gas-canister-ui-release-valve-close}" ToggleMode="True"/>
|
||||
<Control HorizontalExpand="True"/>
|
||||
</HBoxContainer>
|
||||
</VBoxContainer>
|
||||
</VBoxContainer>
|
||||
</SS14Window>
|
||||
98
Content.Client/Atmos/UI/GasCanisterWindow.xaml.cs
Normal file
98
Content.Client/Atmos/UI/GasCanisterWindow.xaml.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
using System;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Content.Client.Atmos.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Client-side UI used to control a canister.
|
||||
/// </summary>
|
||||
[GenerateTypedNameReferences]
|
||||
public partial class GasCanisterWindow : SS14Window
|
||||
{
|
||||
private readonly ButtonGroup _buttonGroup = new();
|
||||
|
||||
public event Action? TankEjectButtonPressed;
|
||||
public event Action<float>? ReleasePressureSliderChanged;
|
||||
public event Action? ReleaseValveCloseButtonPressed;
|
||||
public event Action? ReleaseValveOpenButtonPressed;
|
||||
|
||||
public GasCanisterWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
ReleaseValveCloseButton.Group = _buttonGroup;
|
||||
ReleaseValveOpenButton.Group = _buttonGroup;
|
||||
|
||||
ReleaseValveCloseButton.OnPressed += _ => ReleaseValveCloseButtonPressed?.Invoke();
|
||||
ReleaseValveOpenButton.OnPressed += _ => ReleaseValveOpenButtonPressed?.Invoke();
|
||||
|
||||
TankEjectButton.OnPressed += _ => TankEjectButtonPressed?.Invoke();
|
||||
ReleasePressureSlider.OnValueChanged += r => ReleasePressureSliderChanged?.Invoke(r.Value);
|
||||
}
|
||||
|
||||
public void SetCanisterLabel(string label)
|
||||
{
|
||||
Title = label;
|
||||
}
|
||||
|
||||
public void SetCanisterPressure(float pressure)
|
||||
{
|
||||
CanisterPressureLabel.Text = Loc.GetString("comp-gas-canister-ui-pressure", ("pressure", Math.Round(pressure)));
|
||||
}
|
||||
|
||||
public void SetPortStatus(bool status)
|
||||
{
|
||||
if (status)
|
||||
{
|
||||
PortStatusLabel.Text = Loc.GetString("comp-gas-canister-ui-port-connected");
|
||||
}
|
||||
else
|
||||
{
|
||||
PortStatusLabel.Text = Loc.GetString("comp-gas-canister-ui-port-disconnected");
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTankLabel(string? label)
|
||||
{
|
||||
if (label == null)
|
||||
{
|
||||
TankEjectButton.Disabled = true;
|
||||
TankLabelLabel.Text = Loc.GetString("comp-gas-canister-ui-holding-tank-label-empty");
|
||||
return;
|
||||
}
|
||||
|
||||
TankEjectButton.Disabled = false;
|
||||
TankLabelLabel.Text = label;
|
||||
}
|
||||
|
||||
public void SetTankPressure(float pressure)
|
||||
{
|
||||
TankPressureLabel.Text = Loc.GetString("comp-gas-canister-ui-pressure", ("pressure", Math.Round(pressure)));
|
||||
}
|
||||
|
||||
public void SetReleasePressureRange(float min, float max)
|
||||
{
|
||||
ReleasePressureSlider.MinValue = min;
|
||||
ReleasePressureSlider.MaxValue = max;
|
||||
}
|
||||
|
||||
public void SetReleasePressure(float pressure)
|
||||
{
|
||||
if(!ReleasePressureSlider.Grabbed)
|
||||
ReleasePressureSlider.SetValueWithoutEvent(pressure);
|
||||
ReleasePressureLabel.Text = Loc.GetString("comp-gas-canister-ui-pressure", ("pressure", Math.Round(pressure)));
|
||||
}
|
||||
|
||||
public void SetReleaseValve(bool valve)
|
||||
{
|
||||
if (valve)
|
||||
ReleaseValveOpenButton.Pressed = true;
|
||||
else
|
||||
ReleaseValveCloseButton.Pressed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
37
Content.Client/Atmos/Visualizers/AtmosPlaqueVisualizer.cs
Normal file
37
Content.Client/Atmos/Visualizers/AtmosPlaqueVisualizer.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Content.Shared.Atmos.Visuals;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Content.Client.Atmos.Visualizers
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class AtmosPlaqueVisualizer : AppearanceVisualizer
|
||||
{
|
||||
[DataField("layer")]
|
||||
private int Layer { get; }
|
||||
|
||||
public override void InitializeEntity(IEntity entity)
|
||||
{
|
||||
base.InitializeEntity(entity);
|
||||
|
||||
entity.GetComponentOrNull<SpriteComponent>()?.LayerMapReserveBlank(Layer);
|
||||
}
|
||||
|
||||
public override void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
base.OnChangeData(component);
|
||||
|
||||
if (!component.Owner.TryGetComponent(out SpriteComponent? sprite))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!component.TryGetData(AtmosPlaqueVisuals.State, out string state))
|
||||
{
|
||||
sprite.LayerSetState(Layer, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
71
Content.Client/Atmos/Visualizers/FireVisualizer.cs
Normal file
71
Content.Client/Atmos/Visualizers/FireVisualizer.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using Content.Shared.Atmos.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Content.Client.Atmos.Visualizers
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class FireVisualizer : AppearanceVisualizer
|
||||
{
|
||||
[DataField("fireStackAlternateState")]
|
||||
private int _fireStackAlternateState = 3;
|
||||
|
||||
[DataField("normalState")]
|
||||
private string? _normalState;
|
||||
|
||||
[DataField("alternateState")]
|
||||
private string? _alternateState;
|
||||
|
||||
[DataField("sprite")]
|
||||
private string? _sprite;
|
||||
|
||||
public override void InitializeEntity(IEntity entity)
|
||||
{
|
||||
base.InitializeEntity(entity);
|
||||
|
||||
var sprite = entity.GetComponent<ISpriteComponent>();
|
||||
|
||||
sprite.LayerMapReserveBlank(FireVisualLayers.Fire);
|
||||
sprite.LayerSetVisible(FireVisualLayers.Fire, false);
|
||||
}
|
||||
|
||||
public override void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
base.OnChangeData(component);
|
||||
|
||||
if (component.TryGetData(FireVisuals.OnFire, out bool onFire))
|
||||
{
|
||||
var fireStacks = 0f;
|
||||
|
||||
if (component.TryGetData(FireVisuals.FireStacks, out float stacks))
|
||||
fireStacks = stacks;
|
||||
|
||||
SetOnFire(component, onFire, fireStacks);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetOnFire(AppearanceComponent component, bool onFire, float fireStacks)
|
||||
{
|
||||
var sprite = component.Owner.GetComponent<ISpriteComponent>();
|
||||
|
||||
if (_sprite != null)
|
||||
{
|
||||
sprite.LayerSetRSI(FireVisualLayers.Fire, _sprite);
|
||||
}
|
||||
|
||||
sprite.LayerSetVisible(FireVisualLayers.Fire, onFire);
|
||||
|
||||
if(fireStacks > _fireStackAlternateState && !string.IsNullOrEmpty(_alternateState))
|
||||
sprite.LayerSetState(FireVisualLayers.Fire, _alternateState);
|
||||
else
|
||||
sprite.LayerSetState(FireVisualLayers.Fire, _normalState);
|
||||
}
|
||||
}
|
||||
|
||||
public enum FireVisualLayers : byte
|
||||
{
|
||||
Fire
|
||||
}
|
||||
}
|
||||
44
Content.Client/Atmos/Visualizers/GasAnalyzerVisualizer.cs
Normal file
44
Content.Client/Atmos/Visualizers/GasAnalyzerVisualizer.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Content.Shared.Atmos.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Content.Client.Atmos.Visualizers
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class GasAnalyzerVisualizer : AppearanceVisualizer
|
||||
{
|
||||
[DataField("state_off")]
|
||||
private string? _stateOff;
|
||||
[DataField("state_working")]
|
||||
private string? _stateWorking;
|
||||
|
||||
public override void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
base.OnChangeData(component);
|
||||
|
||||
if (component.Deleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!component.Owner.TryGetComponent(out ISpriteComponent? sprite))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (component.TryGetData(GasAnalyzerVisuals.VisualState, out GasAnalyzerVisualState visualState))
|
||||
{
|
||||
switch (visualState)
|
||||
{
|
||||
case GasAnalyzerVisualState.Off:
|
||||
sprite.LayerSetState(0, _stateOff);
|
||||
break;
|
||||
case GasAnalyzerVisualState.Working:
|
||||
sprite.LayerSetState(0, _stateWorking);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
54
Content.Client/Atmos/Visualizers/GasCanisterVisualizer.cs
Normal file
54
Content.Client/Atmos/Visualizers/GasCanisterVisualizer.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using Content.Shared.Atmos.Piping.Binary.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Content.Client.Atmos.Visualizers
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class GasCanisterVisualizer : AppearanceVisualizer
|
||||
{
|
||||
[DataField("pressureStates")]
|
||||
private readonly string[] _statePressure = {"", "", "", ""};
|
||||
|
||||
[DataField("insertedTankState")]
|
||||
private readonly string _insertedTankState = string.Empty;
|
||||
|
||||
public override void InitializeEntity(IEntity entity)
|
||||
{
|
||||
base.InitializeEntity(entity);
|
||||
|
||||
var sprite = entity.GetComponent<ISpriteComponent>();
|
||||
|
||||
sprite.LayerMapSet(Layers.PressureLight, sprite.AddLayerState(_statePressure[0]));
|
||||
sprite.LayerSetShader(Layers.PressureLight, "unshaded");
|
||||
sprite.LayerMapSet(Layers.TankInserted, sprite.AddLayerState(_insertedTankState));
|
||||
sprite.LayerSetVisible(Layers.TankInserted, false);
|
||||
}
|
||||
|
||||
public override void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
base.OnChangeData(component);
|
||||
|
||||
if (!component.Owner.TryGetComponent(out ISpriteComponent? sprite))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the canister lights
|
||||
if (component.TryGetData(GasCanisterVisuals.PressureState, out int pressureState))
|
||||
if ((pressureState >= 0) && (pressureState < _statePressure.Length))
|
||||
sprite.LayerSetState(Layers.PressureLight, _statePressure[pressureState]);
|
||||
|
||||
if(component.TryGetData(GasCanisterVisuals.TankInserted, out bool inserted))
|
||||
sprite.LayerSetVisible(Layers.TankInserted, inserted);
|
||||
}
|
||||
|
||||
private enum Layers
|
||||
{
|
||||
PressureLight,
|
||||
TankInserted,
|
||||
}
|
||||
}
|
||||
}
|
||||
54
Content.Client/Atmos/Visualizers/GasPortableVisualizer.cs
Normal file
54
Content.Client/Atmos/Visualizers/GasPortableVisualizer.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using Content.Shared.Atmos.Piping.Unary.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Content.Client.Atmos.Visualizers
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class GasPortableVisualizer : AppearanceVisualizer
|
||||
{
|
||||
[DataField("stateConnected")]
|
||||
private string? _stateConnected;
|
||||
|
||||
public override void InitializeEntity(IEntity entity)
|
||||
{
|
||||
base.InitializeEntity(entity);
|
||||
|
||||
var sprite = entity.GetComponent<ISpriteComponent>();
|
||||
|
||||
if (_stateConnected != null)
|
||||
{
|
||||
sprite.LayerMapSet(Layers.ConnectedToPort, sprite.AddLayerState(_stateConnected));
|
||||
sprite.LayerSetVisible(Layers.ConnectedToPort, false);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
base.OnChangeData(component);
|
||||
|
||||
if (component.Deleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!component.Owner.TryGetComponent(out ISpriteComponent? sprite))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the visuals : Is the canister connected to a port or not
|
||||
if (component.TryGetData(GasPortableVisuals.ConnectedState, out bool isConnected))
|
||||
{
|
||||
sprite.LayerSetVisible(Layers.ConnectedToPort, isConnected);
|
||||
}
|
||||
}
|
||||
|
||||
private enum Layers
|
||||
{
|
||||
ConnectedToPort,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user