Predicted gas pumps (#33717)

* Predicted gas pumps

I wanted to try out atmos and first thing I found.

* a

* Remove details range
This commit is contained in:
metalgearsloth
2024-12-07 14:39:52 +11:00
committed by GitHub
parent 4beb1016cc
commit 9365e3a99b
26 changed files with 355 additions and 308 deletions

View File

@@ -0,0 +1,23 @@
using Content.Client.Atmos.UI;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems;
using Content.Shared.Atmos.Piping.Binary.Components;
namespace Content.Client.Atmos.EntitySystems;
public sealed class GasPressurePumpSystem : SharedGasPressurePumpSystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GasPressurePumpComponent, AfterAutoHandleStateEvent>(OnPumpUpdate);
}
private void OnPumpUpdate(Entity<GasPressurePumpComponent> ent, ref AfterAutoHandleStateEvent args)
{
if (UserInterfaceSystem.TryGetOpenUi<GasPressurePumpBoundUserInterface>(ent.Owner, GasPressurePumpUiKey.Key, out var bui))
{
bui.Update();
}
}
}

View File

@@ -1,65 +1,63 @@
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.Piping.Binary.Components; using Content.Shared.Atmos.Piping.Binary.Components;
using Content.Shared.IdentityManagement;
using Content.Shared.Localizations; using Content.Shared.Localizations;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
namespace Content.Client.Atmos.UI namespace Content.Client.Atmos.UI;
/// <summary>
/// Initializes a <see cref="GasPressurePumpWindow"/> and updates it when new server messages are received.
/// </summary>
[UsedImplicitly]
public sealed class GasPressurePumpBoundUserInterface : BoundUserInterface
{ {
/// <summary> [ViewVariables]
/// Initializes a <see cref="GasPressurePumpWindow"/> and updates it when new server messages are received. private const float MaxPressure = Atmospherics.MaxOutputPressure;
/// </summary>
[UsedImplicitly] [ViewVariables]
public sealed class GasPressurePumpBoundUserInterface : BoundUserInterface private GasPressurePumpWindow? _window;
public GasPressurePumpBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{ {
[ViewVariables] }
private const float MaxPressure = Atmospherics.MaxOutputPressure;
[ViewVariables] protected override void Open()
private GasPressurePumpWindow? _window; {
base.Open();
public GasPressurePumpBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) _window = this.CreateWindow<GasPressurePumpWindow>();
{
}
protected override void Open() _window.ToggleStatusButtonPressed += OnToggleStatusButtonPressed;
{ _window.PumpOutputPressureChanged += OnPumpOutputPressurePressed;
base.Open(); Update();
}
_window = this.CreateWindow<GasPressurePumpWindow>(); public void Update()
{
if (_window == null)
return;
_window.ToggleStatusButtonPressed += OnToggleStatusButtonPressed; _window.Title = Identity.Name(Owner, EntMan);
_window.PumpOutputPressureChanged += OnPumpOutputPressurePressed;
}
private void OnToggleStatusButtonPressed() if (!EntMan.TryGetComponent(Owner, out GasPressurePumpComponent? pump))
{ return;
if (_window is null) return;
SendMessage(new GasPressurePumpToggleStatusMessage(_window.PumpStatus));
}
private void OnPumpOutputPressurePressed(string value) _window.SetPumpStatus(pump.Enabled);
{ _window.MaxPressure = pump.MaxTargetPressure;
var pressure = UserInputParser.TryFloat(value, out var parsed) ? parsed : 0f; _window.SetOutputPressure(pump.TargetPressure);
if (pressure > MaxPressure) pressure = MaxPressure; }
SendMessage(new GasPressurePumpChangeOutputPressureMessage(pressure)); private void OnToggleStatusButtonPressed()
} {
if (_window is null) return;
SendPredictedMessage(new GasPressurePumpToggleStatusMessage(_window.PumpStatus));
}
/// <summary> private void OnPumpOutputPressurePressed(float value)
/// Update the UI state based on server-sent info {
/// </summary> SendPredictedMessage(new GasPressurePumpChangeOutputPressureMessage(value));
/// <param name="state"></param>
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (_window == null || state is not GasPressurePumpBoundUserInterfaceState cast)
return;
_window.Title = (cast.PumpLabel);
_window.SetPumpStatus(cast.Enabled);
_window.SetOutputPressure(cast.OutputPressure);
}
} }
} }

View File

@@ -1,22 +1,18 @@
<DefaultWindow xmlns="https://spacestation14.io" <controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
MinSize="200 120" Title="Pressure Pump"> xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
SetSize="340 110" MinSize="340 110" Title="Pressure Pump">
<BoxContainer Orientation="Vertical" Margin="5 5 5 5" SeparationOverride="10"> <BoxContainer Orientation="Vertical" Margin="5 5 5 5" SeparationOverride="10">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True"> <BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Label Text="{Loc comp-gas-pump-ui-pump-status}"/> <Label Text="{Loc comp-gas-pump-ui-pump-status}" Margin="0 0 5 0"/>
<Control MinSize="5 0" />
<Button Name="ToggleStatusButton"/> <Button Name="ToggleStatusButton"/>
<Control HorizontalExpand="True"/>
<Button HorizontalAlignment="Right" Name="SetOutputPressureButton" Text="{Loc comp-gas-pump-ui-pump-set-rate}" Disabled="True" Margin="0 0 5 0"/>
<Button Name="SetMaxPressureButton" Text="{Loc comp-gas-pump-ui-pump-set-max}" />
</BoxContainer> </BoxContainer>
<BoxContainer Orientation="Horizontal" HorizontalExpand="True"> <BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Label Text="{Loc comp-gas-pump-ui-pump-output-pressure}"/> <Label Text="{Loc comp-gas-pump-ui-pump-output-pressure}"/>
<Control MinSize="5 0" /> <FloatSpinBox HorizontalExpand="True" Name="PumpPressureOutputInput" MinSize="70 0" />
<LineEdit Name="PumpPressureOutputInput" MinSize="70 0" />
<Control MinSize="5 0" />
<Button Name="SetMaxPressureButton" Text="{Loc comp-gas-pump-ui-pump-set-max}" />
<Control MinSize="5 0" />
<Control HorizontalExpand="True" />
<Button Name="SetOutputPressureButton" Text="{Loc comp-gas-pump-ui-pump-set-rate}" HorizontalAlignment="Right" Disabled="True"/>
</BoxContainer> </BoxContainer>
</BoxContainer> </BoxContainer>
</DefaultWindow> </controls:FancyWindow>

View File

@@ -1,14 +1,8 @@
using System; using Content.Client.UserInterface.Controls;
using System.Collections.Generic;
using System.Globalization;
using Content.Client.Atmos.EntitySystems;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.Atmos.Prototypes;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared.Localization;
namespace Content.Client.Atmos.UI namespace Content.Client.Atmos.UI
{ {
@@ -16,12 +10,25 @@ namespace Content.Client.Atmos.UI
/// Client-side UI used to control a gas pressure pump. /// Client-side UI used to control a gas pressure pump.
/// </summary> /// </summary>
[GenerateTypedNameReferences] [GenerateTypedNameReferences]
public sealed partial class GasPressurePumpWindow : DefaultWindow public sealed partial class GasPressurePumpWindow : FancyWindow
{ {
public bool PumpStatus = true; public bool PumpStatus = true;
public event Action? ToggleStatusButtonPressed; public event Action? ToggleStatusButtonPressed;
public event Action<string>? PumpOutputPressureChanged; public event Action<float>? PumpOutputPressureChanged;
public float MaxPressure
{
get => _maxPressure;
set
{
_maxPressure = value;
PumpPressureOutputInput.Value = MathF.Min(value, PumpPressureOutputInput.Value);
}
}
private float _maxPressure = Atmospherics.MaxOutputPressure;
public GasPressurePumpWindow() public GasPressurePumpWindow()
{ {
@@ -30,23 +37,25 @@ namespace Content.Client.Atmos.UI
ToggleStatusButton.OnPressed += _ => SetPumpStatus(!PumpStatus); ToggleStatusButton.OnPressed += _ => SetPumpStatus(!PumpStatus);
ToggleStatusButton.OnPressed += _ => ToggleStatusButtonPressed?.Invoke(); ToggleStatusButton.OnPressed += _ => ToggleStatusButtonPressed?.Invoke();
PumpPressureOutputInput.OnTextChanged += _ => SetOutputPressureButton.Disabled = false; PumpPressureOutputInput.OnValueChanged += _ => SetOutputPressureButton.Disabled = false;
SetOutputPressureButton.OnPressed += _ => SetOutputPressureButton.OnPressed += _ =>
{ {
PumpOutputPressureChanged?.Invoke(PumpPressureOutputInput.Text ??= ""); PumpPressureOutputInput.Value = Math.Clamp(PumpPressureOutputInput.Value, 0f, _maxPressure);
PumpOutputPressureChanged?.Invoke(PumpPressureOutputInput.Value);
SetOutputPressureButton.Disabled = true; SetOutputPressureButton.Disabled = true;
}; };
SetMaxPressureButton.OnPressed += _ => SetMaxPressureButton.OnPressed += _ =>
{ {
PumpPressureOutputInput.Text = Atmospherics.MaxOutputPressure.ToString(CultureInfo.CurrentCulture); PumpPressureOutputInput.Value = _maxPressure;
SetOutputPressureButton.Disabled = false; SetOutputPressureButton.Disabled = false;
}; };
} }
public void SetOutputPressure(float pressure) public void SetOutputPressure(float pressure)
{ {
PumpPressureOutputInput.Text = pressure.ToString(CultureInfo.CurrentCulture); PumpPressureOutputInput.Value = pressure;
} }
public void SetPumpStatus(bool enabled) public void SetPumpStatus(bool enabled)

View File

@@ -11,6 +11,7 @@ using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems; using Content.Server.Power.EntitySystems;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.Atmos.Monitor; using Content.Shared.Atmos.Monitor;
using Content.Shared.Atmos.Piping.Components;
using Content.Shared.DeviceNetwork; using Content.Shared.DeviceNetwork;
using Content.Shared.Power; using Content.Shared.Power;
using Content.Shared.Tag; using Content.Shared.Tag;

View File

@@ -1,31 +0,0 @@
using Content.Shared.Atmos;
namespace Content.Server.Atmos.Piping.Binary.Components
{
[RegisterComponent]
public sealed partial class GasPressurePumpComponent : Component
{
[ViewVariables(VVAccess.ReadWrite)]
[DataField("enabled")]
public bool Enabled { get; set; } = true;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("inlet")]
public string InletName { get; set; } = "inlet";
[ViewVariables(VVAccess.ReadWrite)]
[DataField("outlet")]
public string OutletName { get; set; } = "outlet";
[ViewVariables(VVAccess.ReadWrite)]
[DataField("targetPressure")]
public float TargetPressure { get; set; } = Atmospherics.OneAtmosphere;
/// <summary>
/// Max pressure of the target gas (NOT relative to source).
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("maxTargetPressure")]
public float MaxTargetPressure = Atmospherics.MaxOutputPressure;
}
}

View File

@@ -1,169 +1,57 @@
using Content.Server.Administration.Logs;
using Content.Server.Atmos.EntitySystems; using Content.Server.Atmos.EntitySystems;
using Content.Server.Atmos.Piping.Binary.Components;
using Content.Server.Atmos.Piping.Components; using Content.Server.Atmos.Piping.Components;
using Content.Server.NodeContainer.EntitySystems; using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NodeContainer.Nodes; using Content.Server.NodeContainer.Nodes;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.Atmos.Piping; using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.Piping.Binary.Components; using Content.Shared.Atmos.EntitySystems;
using Content.Shared.Audio; using Content.Shared.Audio;
using Content.Shared.Database;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Popups;
using Content.Shared.Power;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.Player;
namespace Content.Server.Atmos.Piping.Binary.EntitySystems namespace Content.Server.Atmos.Piping.Binary.EntitySystems;
[UsedImplicitly]
public sealed class GasPressurePumpSystem : SharedGasPressurePumpSystem
{ {
[UsedImplicitly] [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
public sealed class GasPressurePumpSystem : EntitySystem [Dependency] private readonly SharedAmbientSoundSystem _ambientSoundSystem = default!;
[Dependency] private readonly NodeContainerSystem _nodeContainer = default!;
public override void Initialize()
{ {
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!; base.Initialize();
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
[Dependency] private readonly SharedAmbientSoundSystem _ambientSoundSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly NodeContainerSystem _nodeContainer = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
public override void Initialize() SubscribeLocalEvent<GasPressurePumpComponent, AtmosDeviceUpdateEvent>(OnPumpUpdated);
}
private void OnPumpUpdated(EntityUid uid, GasPressurePumpComponent pump, ref AtmosDeviceUpdateEvent args)
{
if (!pump.Enabled
|| (TryComp<ApcPowerReceiverComponent>(uid, out var power) && !power.Powered)
|| !_nodeContainer.TryGetNodes(uid, pump.InletName, pump.OutletName, out PipeNode? inlet, out PipeNode? outlet))
{ {
base.Initialize(); _ambientSoundSystem.SetAmbience(uid, false);
return;
SubscribeLocalEvent<GasPressurePumpComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<GasPressurePumpComponent, AtmosDeviceUpdateEvent>(OnPumpUpdated);
SubscribeLocalEvent<GasPressurePumpComponent, AtmosDeviceDisabledEvent>(OnPumpLeaveAtmosphere);
SubscribeLocalEvent<GasPressurePumpComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<GasPressurePumpComponent, ActivateInWorldEvent>(OnPumpActivate);
SubscribeLocalEvent<GasPressurePumpComponent, PowerChangedEvent>(OnPowerChanged);
// Bound UI subscriptions
SubscribeLocalEvent<GasPressurePumpComponent, GasPressurePumpChangeOutputPressureMessage>(OnOutputPressureChangeMessage);
SubscribeLocalEvent<GasPressurePumpComponent, GasPressurePumpToggleStatusMessage>(OnToggleStatusMessage);
} }
private void OnInit(EntityUid uid, GasPressurePumpComponent pump, ComponentInit args) var outputStartingPressure = outlet.Air.Pressure;
if (outputStartingPressure >= pump.TargetPressure)
{ {
UpdateAppearance(uid, pump); _ambientSoundSystem.SetAmbience(uid, false);
return; // No need to pump gas if target has been reached.
} }
private void OnExamined(EntityUid uid, GasPressurePumpComponent pump, ExaminedEvent args) if (inlet.Air.TotalMoles > 0 && inlet.Air.Temperature > 0)
{ {
if (!EntityManager.GetComponent<TransformComponent>(uid).Anchored || !args.IsInDetailsRange) // Not anchored? Out of range? No status. // We calculate the necessary moles to transfer using our good ol' friend PV=nRT.
return; var pressureDelta = pump.TargetPressure - outputStartingPressure;
var transferMoles = (pressureDelta * outlet.Air.Volume) / (inlet.Air.Temperature * Atmospherics.R);
if (Loc.TryGetString("gas-pressure-pump-system-examined", out var str, var removed = inlet.Air.Remove(transferMoles);
("statusColor", "lightblue"), // TODO: change with pressure? _atmosphereSystem.Merge(outlet.Air, removed);
("pressure", pump.TargetPressure) _ambientSoundSystem.SetAmbience(uid, removed.TotalMoles > 0f);
))
{
args.PushMarkup(str);
}
}
private void OnPowerChanged(EntityUid uid, GasPressurePumpComponent component, ref PowerChangedEvent args)
{
UpdateAppearance(uid, component);
}
private void OnPumpUpdated(EntityUid uid, GasPressurePumpComponent pump, ref AtmosDeviceUpdateEvent args)
{
if (!pump.Enabled
|| (TryComp<ApcPowerReceiverComponent>(uid, out var power) && !power.Powered)
|| !_nodeContainer.TryGetNodes(uid, pump.InletName, pump.OutletName, out PipeNode? inlet, out PipeNode? outlet))
{
_ambientSoundSystem.SetAmbience(uid, false);
return;
}
var outputStartingPressure = outlet.Air.Pressure;
if (outputStartingPressure >= pump.TargetPressure)
{
_ambientSoundSystem.SetAmbience(uid, false);
return; // No need to pump gas if target has been reached.
}
if (inlet.Air.TotalMoles > 0 && inlet.Air.Temperature > 0)
{
// We calculate the necessary moles to transfer using our good ol' friend PV=nRT.
var pressureDelta = pump.TargetPressure - outputStartingPressure;
var transferMoles = (pressureDelta * outlet.Air.Volume) / (inlet.Air.Temperature * Atmospherics.R);
var removed = inlet.Air.Remove(transferMoles);
_atmosphereSystem.Merge(outlet.Air, removed);
_ambientSoundSystem.SetAmbience(uid, removed.TotalMoles > 0f);
}
}
private void OnPumpLeaveAtmosphere(EntityUid uid, GasPressurePumpComponent pump, ref AtmosDeviceDisabledEvent args)
{
pump.Enabled = false;
UpdateAppearance(uid, pump);
DirtyUI(uid, pump);
_userInterfaceSystem.CloseUi(uid, GasPressurePumpUiKey.Key);
}
private void OnPumpActivate(EntityUid uid, GasPressurePumpComponent pump, ActivateInWorldEvent args)
{
if (args.Handled || !args.Complex)
return;
if (!EntityManager.TryGetComponent(args.User, out ActorComponent? actor))
return;
if (Transform(uid).Anchored)
{
_userInterfaceSystem.OpenUi(uid, GasPressurePumpUiKey.Key, actor.PlayerSession);
DirtyUI(uid, pump);
}
else
{
_popup.PopupCursor(Loc.GetString("comp-gas-pump-ui-needs-anchor"), args.User);
}
args.Handled = true;
}
private void OnToggleStatusMessage(EntityUid uid, GasPressurePumpComponent pump, GasPressurePumpToggleStatusMessage args)
{
pump.Enabled = args.Enabled;
_adminLogger.Add(LogType.AtmosPowerChanged, LogImpact.Medium,
$"{ToPrettyString(args.Actor):player} set the power on {ToPrettyString(uid):device} to {args.Enabled}");
DirtyUI(uid, pump);
UpdateAppearance(uid, pump);
}
private void OnOutputPressureChangeMessage(EntityUid uid, GasPressurePumpComponent pump, GasPressurePumpChangeOutputPressureMessage args)
{
pump.TargetPressure = Math.Clamp(args.Pressure, 0f, Atmospherics.MaxOutputPressure);
_adminLogger.Add(LogType.AtmosPressureChanged, LogImpact.Medium,
$"{ToPrettyString(args.Actor):player} set the pressure on {ToPrettyString(uid):device} to {args.Pressure}kPa");
DirtyUI(uid, pump);
}
private void DirtyUI(EntityUid uid, GasPressurePumpComponent? pump)
{
if (!Resolve(uid, ref pump))
return;
_userInterfaceSystem.SetUiState(uid, GasPressurePumpUiKey.Key,
new GasPressurePumpBoundUserInterfaceState(EntityManager.GetComponent<MetaDataComponent>(uid).EntityName, pump.TargetPressure, pump.Enabled));
}
private void UpdateAppearance(EntityUid uid, GasPressurePumpComponent? pump = null, AppearanceComponent? appearance = null)
{
if (!Resolve(uid, ref pump, ref appearance, false))
return;
bool pumpOn = pump.Enabled && (TryComp<ApcPowerReceiverComponent>(uid, out var power) && power.Powered);
_appearance.SetData(uid, PumpVisuals.Enabled, pumpOn, appearance);
} }
} }
} }

View File

@@ -6,6 +6,7 @@ using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NodeContainer.Nodes; using Content.Server.NodeContainer.Nodes;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.Atmos.Piping; using Content.Shared.Atmos.Piping;
using Content.Shared.Atmos.Piping.Components;
using Content.Shared.Audio; using Content.Shared.Audio;
using Content.Shared.Examine; using Content.Shared.Examine;
using JetBrains.Annotations; using JetBrains.Annotations;

View File

@@ -10,6 +10,7 @@ using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NodeContainer.Nodes; using Content.Server.NodeContainer.Nodes;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Shared.Atmos.Piping.Binary.Components; using Content.Shared.Atmos.Piping.Binary.Components;
using Content.Shared.Atmos.Piping.Components;
using Content.Shared.Atmos.Visuals; using Content.Shared.Atmos.Visuals;
using Content.Shared.Audio; using Content.Shared.Audio;
using Content.Shared.Database; using Content.Shared.Database;

View File

@@ -67,15 +67,3 @@ public readonly struct AtmosDeviceUpdateEvent(float dt, Entity<GridAtmosphereCom
/// </summary> /// </summary>
public readonly Entity<MapAtmosphereComponent?>? Map = map; public readonly Entity<MapAtmosphereComponent?>? Map = map;
} }
/// <summary>
/// Raised directed on an atmos device when it is enabled.
/// </summary>
[ByRefEvent]
public record struct AtmosDeviceEnabledEvent;
/// <summary>
/// Raised directed on an atmos device when it is enabled.
/// </summary>
[ByRefEvent]
public record struct AtmosDeviceDisabledEvent;

View File

@@ -1,6 +1,7 @@
using Content.Server.Atmos.Components; using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems; using Content.Server.Atmos.EntitySystems;
using Content.Server.Atmos.Piping.Components; using Content.Server.Atmos.Piping.Components;
using Content.Shared.Atmos.Piping.Components;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using Robust.Shared.Utility; using Robust.Shared.Utility;

View File

@@ -7,6 +7,7 @@ using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NodeContainer.Nodes; using Content.Server.NodeContainer.Nodes;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.Atmos.Piping; using Content.Shared.Atmos.Piping;
using Content.Shared.Atmos.Piping.Components;
using Content.Shared.Atmos.Piping.Trinary.Components; using Content.Shared.Atmos.Piping.Trinary.Components;
using Content.Shared.Audio; using Content.Shared.Audio;
using Content.Shared.Database; using Content.Shared.Database;

View File

@@ -7,6 +7,7 @@ using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NodeContainer.Nodes; using Content.Server.NodeContainer.Nodes;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.Atmos.Piping; using Content.Shared.Atmos.Piping;
using Content.Shared.Atmos.Piping.Components;
using Content.Shared.Atmos.Piping.Trinary.Components; using Content.Shared.Atmos.Piping.Trinary.Components;
using Content.Shared.Audio; using Content.Shared.Audio;
using Content.Shared.Database; using Content.Shared.Database;

View File

@@ -5,6 +5,7 @@ using Content.Server.NodeContainer;
using Content.Server.NodeContainer.EntitySystems; using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NodeContainer.Nodes; using Content.Server.NodeContainer.Nodes;
using Content.Shared.Atmos.Piping; using Content.Shared.Atmos.Piping;
using Content.Shared.Atmos.Piping.Components;
using Content.Shared.Audio; using Content.Shared.Audio;
using JetBrains.Annotations; using JetBrains.Annotations;

View File

@@ -11,6 +11,7 @@ using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NodeContainer.Nodes; using Content.Server.NodeContainer.Nodes;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.Atmos.Monitor; using Content.Shared.Atmos.Monitor;
using Content.Shared.Atmos.Piping.Components;
using Content.Shared.Atmos.Piping.Unary; using Content.Shared.Atmos.Piping.Unary;
using Content.Shared.Atmos.Piping.Unary.Components; using Content.Shared.Atmos.Piping.Unary.Components;
using Content.Shared.Atmos.Visuals; using Content.Shared.Atmos.Visuals;

View File

@@ -13,6 +13,7 @@ using Content.Server.Power.Components;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.Atmos.Piping.Unary.Visuals; using Content.Shared.Atmos.Piping.Unary.Visuals;
using Content.Shared.Atmos.Monitor; using Content.Shared.Atmos.Monitor;
using Content.Shared.Atmos.Piping.Components;
using Content.Shared.Atmos.Piping.Unary.Components; using Content.Shared.Atmos.Piping.Unary.Components;
using Content.Shared.Audio; using Content.Shared.Audio;
using Content.Shared.DeviceNetwork; using Content.Shared.DeviceNetwork;

View File

@@ -0,0 +1,25 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Atmos.Components;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class GasPressurePumpComponent : Component
{
[DataField, AutoNetworkedField]
public bool Enabled = true;
[DataField("inlet")]
public string InletName = "inlet";
[DataField("outlet")]
public string OutletName = "outlet";
[DataField, AutoNetworkedField]
public float TargetPressure = Atmospherics.OneAtmosphere;
/// <summary>
/// Max pressure of the target gas (NOT relative to source).
/// </summary>
[DataField]
public float MaxTargetPressure = Atmospherics.MaxOutputPressure;
}

View File

@@ -0,0 +1,97 @@
using Content.Shared.Administration.Logs;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.Piping;
using Content.Shared.Atmos.Piping.Binary.Components;
using Content.Shared.Atmos.Piping.Components;
using Content.Shared.Database;
using Content.Shared.Examine;
using Content.Shared.Popups;
using Content.Shared.Power;
using Content.Shared.Power.Components;
using Content.Shared.Power.EntitySystems;
using Content.Shared.UserInterface;
namespace Content.Shared.Atmos.EntitySystems;
public abstract class SharedGasPressurePumpSystem : EntitySystem
{
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
[Dependency] private readonly SharedPowerReceiverSystem _receiver = default!;
[Dependency] protected readonly SharedUserInterfaceSystem UserInterfaceSystem = default!;
// TODO: Check enabled for activatableUI
// TODO: Add activatableUI to it.
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GasPressurePumpComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<GasPressurePumpComponent, PowerChangedEvent>(OnPowerChanged);
SubscribeLocalEvent<GasPressurePumpComponent, GasPressurePumpChangeOutputPressureMessage>(OnOutputPressureChangeMessage);
SubscribeLocalEvent<GasPressurePumpComponent, GasPressurePumpToggleStatusMessage>(OnToggleStatusMessage);
SubscribeLocalEvent<GasPressurePumpComponent, AtmosDeviceDisabledEvent>(OnPumpLeaveAtmosphere);
SubscribeLocalEvent<GasPressurePumpComponent, ExaminedEvent>(OnExamined);
}
private void OnExamined(EntityUid uid, GasPressurePumpComponent pump, ExaminedEvent args)
{
if (!Transform(uid).Anchored)
return;
if (Loc.TryGetString("gas-pressure-pump-system-examined", out var str,
("statusColor", "lightblue"), // TODO: change with pressure?
("pressure", pump.TargetPressure)
))
{
args.PushMarkup(str);
}
}
private void OnInit(EntityUid uid, GasPressurePumpComponent pump, ComponentInit args)
{
UpdateAppearance(uid, pump);
}
private void OnPowerChanged(EntityUid uid, GasPressurePumpComponent component, ref PowerChangedEvent args)
{
UpdateAppearance(uid, component);
}
private void UpdateAppearance(EntityUid uid, GasPressurePumpComponent? pump = null, AppearanceComponent? appearance = null)
{
if (!Resolve(uid, ref pump, ref appearance, false))
return;
var pumpOn = pump.Enabled && _receiver.IsPowered(uid);
Appearance.SetData(uid, PumpVisuals.Enabled, pumpOn, appearance);
}
private void OnToggleStatusMessage(EntityUid uid, GasPressurePumpComponent pump, GasPressurePumpToggleStatusMessage args)
{
pump.Enabled = args.Enabled;
_adminLogger.Add(LogType.AtmosPowerChanged, LogImpact.Medium,
$"{ToPrettyString(args.Actor):player} set the power on {ToPrettyString(uid):device} to {args.Enabled}");
Dirty(uid, pump);
UpdateAppearance(uid, pump);
}
private void OnOutputPressureChangeMessage(EntityUid uid, GasPressurePumpComponent pump, GasPressurePumpChangeOutputPressureMessage args)
{
pump.TargetPressure = Math.Clamp(args.Pressure, 0f, Atmospherics.MaxOutputPressure);
_adminLogger.Add(LogType.AtmosPressureChanged, LogImpact.Medium,
$"{ToPrettyString(args.Actor):player} set the pressure on {ToPrettyString(uid):device} to {args.Pressure}kPa");
Dirty(uid, pump);
}
private void OnPumpLeaveAtmosphere(EntityUid uid, GasPressurePumpComponent pump, ref AtmosDeviceDisabledEvent args)
{
pump.Enabled = false;
Dirty(uid, pump);
UpdateAppearance(uid, pump);
UserInterfaceSystem.CloseUi(uid, GasPressurePumpUiKey.Key);
}
}

View File

@@ -1,47 +1,21 @@
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
namespace Content.Shared.Atmos.Piping.Binary.Components namespace Content.Shared.Atmos.Piping.Binary.Components;
[Serializable, NetSerializable]
public enum GasPressurePumpUiKey : byte
{ {
[Serializable, NetSerializable] Key,
public enum GasPressurePumpUiKey }
{
Key, [Serializable, NetSerializable]
} public sealed class GasPressurePumpToggleStatusMessage(bool enabled) : BoundUserInterfaceMessage
{
[Serializable, NetSerializable] public bool Enabled { get; } = enabled;
public sealed class GasPressurePumpBoundUserInterfaceState : BoundUserInterfaceState }
{
public string PumpLabel { get; } [Serializable, NetSerializable]
public float OutputPressure { get; } public sealed class GasPressurePumpChangeOutputPressureMessage(float pressure) : BoundUserInterfaceMessage
public bool Enabled { get; } {
public float Pressure { get; } = pressure;
public GasPressurePumpBoundUserInterfaceState(string pumpLabel, float outputPressure, bool enabled)
{
PumpLabel = pumpLabel;
OutputPressure = outputPressure;
Enabled = enabled;
}
}
[Serializable, NetSerializable]
public sealed class GasPressurePumpToggleStatusMessage : BoundUserInterfaceMessage
{
public bool Enabled { get; }
public GasPressurePumpToggleStatusMessage(bool enabled)
{
Enabled = enabled;
}
}
[Serializable, NetSerializable]
public sealed class GasPressurePumpChangeOutputPressureMessage : BoundUserInterfaceMessage
{
public float Pressure { get; }
public GasPressurePumpChangeOutputPressureMessage(float pressure)
{
Pressure = pressure;
}
}
} }

View File

@@ -0,0 +1,7 @@
namespace Content.Shared.Atmos.Piping.Components;
/// <summary>
/// Raised directed on an atmos device when it is enabled.
/// </summary>
[ByRefEvent]
public readonly record struct AtmosDeviceDisabledEvent;

View File

@@ -0,0 +1,7 @@
namespace Content.Shared.Atmos.Piping.Components;
/// <summary>
/// Raised directed on an atmos device when it is enabled.
/// </summary>
[ByRefEvent]
public readonly record struct AtmosDeviceEnabledEvent;

View File

@@ -0,0 +1,13 @@
using Robust.Shared.GameStates;
namespace Content.Shared.UserInterface;
/// <summary>
/// Specifies the entity as requiring anchoring to keep the ActivatableUI open.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class ActivatableUIRequiresAnchorComponent : Component
{
[DataField]
public LocId? Popup = "ui-needs-anchor";
}

View File

@@ -0,0 +1,41 @@
using Content.Shared.Popups;
namespace Content.Shared.UserInterface;
/// <summary>
/// <see cref="ActivatableUIRequiresAnchorComponent"/>
/// </summary>
public sealed class ActivatableUIRequiresAnchorSystem : EntitySystem
{
[Dependency] private readonly SharedPopupSystem _popup = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ActivatableUIRequiresAnchorComponent, ActivatableUIOpenAttemptEvent>(OnActivatableUIOpenAttempt);
SubscribeLocalEvent<ActivatableUIRequiresAnchorComponent, BoundUserInterfaceCheckRangeEvent>(OnUICheck);
}
private void OnUICheck(Entity<ActivatableUIRequiresAnchorComponent> ent, ref BoundUserInterfaceCheckRangeEvent args)
{
if (args.Result == BoundUserInterfaceRangeResult.Fail)
return;
if (!Transform(ent.Owner).Anchored)
{
args.Result = BoundUserInterfaceRangeResult.Fail;
}
}
private void OnActivatableUIOpenAttempt(Entity<ActivatableUIRequiresAnchorComponent> ent, ref ActivatableUIOpenAttemptEvent args)
{
if (args.Cancelled)
return;
if (!Transform(ent.Owner).Anchored)
{
_popup.PopupClient(Loc.GetString("comp-gas-pump-ui-needs-anchor"), args.User);
args.Cancel();
}
}
}

View File

@@ -8,5 +8,3 @@ comp-gas-pump-ui-pump-set-max = Max
comp-gas-pump-ui-pump-output-pressure = Output Pressure (kPa): comp-gas-pump-ui-pump-output-pressure = Output Pressure (kPa):
comp-gas-pump-ui-pump-transfer-rate = Transfer Rate (L/s): comp-gas-pump-ui-pump-transfer-rate = Transfer Rate (L/s):
comp-gas-pump-ui-needs-anchor = Anchor it first!

View File

@@ -1,3 +1,5 @@
### Loc for the various UI-related verbs ### Loc for the various UI-related verbs
ui-verb-toggle-open = Toggle UI ui-verb-toggle-open = Toggle UI
verb-instrument-openui = Play Music verb-instrument-openui = Play Music
ui-needs-anchor = Anchor it first!

View File

@@ -57,10 +57,13 @@
- type: PipeColorVisuals - type: PipeColorVisuals
- type: GasPressurePump - type: GasPressurePump
enabled: false enabled: false
- type: ActivatableUI
key: enum.GasPressurePumpUiKey.Key
- type: ActivatableUIRequiresAnchor
- type: UserInterface - type: UserInterface
interfaces: interfaces:
enum.GasPressurePumpUiKey.Key: enum.GasPressurePumpUiKey.Key:
type: GasPressurePumpBoundUserInterface type: GasPressurePumpBoundUserInterface
- type: Construction - type: Construction
graph: GasBinary graph: GasBinary
node: pressurepump node: pressurepump