Pressure Relief Valve (#36708)
* initial system (this math is probably WRONG) * General code cleanup and OnExamined support (holy moly this code sucks) * UICode and related events foundation TODO: - Actually write the XAML UI and the underlying system - Un-shitcode the entire thing - Actually test everything... * Working UI code TODO: Make predicted, as this certainly isn't predicted. Even though I said it was. It isn't. * Remove one TODO for unshitcoding the examine code * Add reminder yea * Make predicted (defenitely isn't) (also defenitely isn't a copypaste from pressure pump code) * It's predicted! TODO: - Give it snazzy predicted visuals! - Have a different field for pressure entry, lest it gets bulldozed every UI update. * Improve gas pressure relief valve UI TODO: Reminder to reduce amount of dirties using deltafields * Implement DirtyField prediction * Entity<T> cleanup A lot of Entity<T> conversions and lukewarm cleanup. Also got caught copy pasting code in 4K UHD but it's not like you couldn't tell. * More cleanup and comments * Remove TODO comment on bulldozing window title * """refactoring""" - Move appearance out of shared and finally fix it. Pointless to predict appearance in this instance. - More Entity<T> conversions because I like them. - Move UI creation handling over entirely to the ActivatableUI system. - Fix a hardcoded locale string (why????). * Add visuals * Revert debugging variable replacememt yea * Revert skissue * Remove unused using directives and remove TODO * Localize, cleanup, document * Fix adminlogging discrepancy * Add ability to construct, add guidebook entry * Clear up comment * Add guidebook tooltip to valve * Convert GasPressureReliefValveBoundUserInterface declaration into primary constructor * Adds more input handling and adds autofill on open * Un-deepfry input validator shitcode Genuinely what was I smoking * improve visuals logic * Refactor again - Update math to the correct implementation - Moved code that could be re-used in the future into a helper method under AtmosphereSystem.Gases.cs * I'm sorry but I hate warnings * Remove unused using directive in AtmosphereSystem.Gases.cs * Review and cleanup * Lukewarm UI glossup * Maintainer for the upstream project btw * Remove redundant state sets and messy logic * Unduplicate valve updater code * Redo UI (im sorry Slarti) * run tests * Test refactored UI messaging * Second round of UI improvements - God please find a way to improve this system. Feels bad. * Update loop implementation * Further predict UI * Clear up SetToCurrentThreshold * cleanup * Update to master + pipe layers and bug fixes want to run tests * fixes * Deploy rename pipebomb * Documentation and requested changes * Rename the method that wiggled away * Undo rounding changes * Fix comment * Rename and cleanup * Apply suggestions from code review --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
@@ -0,0 +1,31 @@
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
using Content.Shared.Atmos.Piping.Binary.Components;
|
||||
|
||||
namespace Content.Client.Atmos.EntitySystems;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the client system responsible for managing and updating the gas pressure regulator interface.
|
||||
/// Inherits from the shared system <see cref="SharedGasPressureRegulatorSystem"/>.
|
||||
/// </summary>
|
||||
public sealed partial class GasPressureRegulatorSystem : SharedGasPressureRegulatorSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<GasPressureRegulatorComponent, AfterAutoHandleStateEvent>(OnValveUpdate);
|
||||
}
|
||||
|
||||
private void OnValveUpdate(Entity<GasPressureRegulatorComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
UpdateUi(ent);
|
||||
}
|
||||
|
||||
protected override void UpdateUi(Entity<GasPressureRegulatorComponent> ent)
|
||||
{
|
||||
if (UserInterfaceSystem.TryGetOpenUi(ent.Owner, GasPressureRegulatorUiKey.Key, out var bui))
|
||||
{
|
||||
bui.Update();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using Content.Shared.Atmos.Piping.Binary.Components;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Localizations;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.Atmos.UI;
|
||||
|
||||
public sealed class GasPressureRegulatorBoundUserInterface(EntityUid owner, Enum uiKey)
|
||||
: BoundUserInterface(owner, uiKey)
|
||||
{
|
||||
private GasPressureRegulatorWindow? _window;
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_window = this.CreateWindow<GasPressureRegulatorWindow>();
|
||||
|
||||
_window.SetEntity(Owner);
|
||||
|
||||
_window.ThresholdPressureChanged += OnThresholdChanged;
|
||||
|
||||
if (EntMan.TryGetComponent(Owner, out GasPressureRegulatorComponent? comp))
|
||||
_window.SetThresholdPressureInput(comp.Threshold);
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
if (_window == null)
|
||||
return;
|
||||
|
||||
_window.Title = Identity.Name(Owner, EntMan);
|
||||
|
||||
if (!EntMan.TryGetComponent(Owner, out GasPressureRegulatorComponent? comp))
|
||||
return;
|
||||
|
||||
_window.SetThresholdPressureLabel(comp.Threshold);
|
||||
_window.UpdateInfo(comp.InletPressure, comp.OutletPressure, comp.FlowRate);
|
||||
}
|
||||
|
||||
private void OnThresholdChanged(string newThreshold)
|
||||
{
|
||||
var sentThreshold = 0f;
|
||||
|
||||
if (UserInputParser.TryFloat(newThreshold, out var parsedNewThreshold) && parsedNewThreshold >= 0 &&
|
||||
!float.IsInfinity(parsedNewThreshold))
|
||||
{
|
||||
sentThreshold = parsedNewThreshold;
|
||||
}
|
||||
|
||||
// Autofill to zero if the user inputs an invalid value.
|
||||
_window?.SetThresholdPressureInput(sentThreshold);
|
||||
|
||||
SendPredictedMessage(new GasPressureRegulatorChangeThresholdMessage(sentThreshold));
|
||||
}
|
||||
}
|
||||
96
Content.Client/Atmos/UI/GasPressureRegulatorWindow.xaml
Normal file
@@ -0,0 +1,96 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
SetSize="345 380"
|
||||
MinSize="345 380"
|
||||
Title="{Loc gas-pressure-regulator-ui-title}"
|
||||
Resizable="False">
|
||||
|
||||
<BoxContainer Orientation="Vertical">
|
||||
|
||||
<BoxContainer Orientation="Vertical" Margin="0 10 0 10">
|
||||
|
||||
<BoxContainer Orientation="Vertical" Align="Center">
|
||||
<Label Text="{Loc gas-pressure-regulator-ui-outlet}" Align="Center" StyleClasses="LabelKeyText" />
|
||||
<BoxContainer Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<Label Name="OutletPressureLabel" Text="N/A" Margin="0 0 4 0" />
|
||||
<Label Text="{Loc gas-pressure-regulator-ui-pressure-unit}" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
|
||||
<BoxContainer Orientation="Horizontal" Align="Center">
|
||||
<BoxContainer Orientation="Vertical" Align="Center" HorizontalExpand="True">
|
||||
<Label Text="{Loc gas-pressure-regulator-ui-target}" Align="Right" StyleClasses="LabelKeyText" />
|
||||
<BoxContainer Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<Label Name="TargetPressureLabel" Margin="0 0 4 0" />
|
||||
<Label Text="{Loc gas-pressure-regulator-ui-pressure-unit}" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
|
||||
<ProgressBar Name="ToTargetBar" MaxValue="1" SetSize="5 75" Margin="10" Vertical="True" />
|
||||
|
||||
<SpriteView Name="EntityView" SetSize="64 64" Scale="3 3" OverrideDirection="North" Margin="0" />
|
||||
|
||||
<ProgressBar Name="FlowRateBar" MaxValue="1" SetSize="5 75" Margin="10" Vertical="True" />
|
||||
|
||||
<BoxContainer Orientation="Vertical" Align="Center" HorizontalExpand="True">
|
||||
<Label Text="{Loc gas-pressure-regulator-ui-flow}" StyleClasses="LabelKeyText" />
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Name="CurrentFlowLabel" Text="N/A" Margin="0 0 4 0" />
|
||||
<Label Text="{Loc gas-pressure-regulator-ui-flow-rate-unit}" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
|
||||
<BoxContainer Orientation="Vertical" Align="Center" Margin="1">
|
||||
<Label Text="{Loc gas-pressure-regulator-ui-inlet}" Align="Center" StyleClasses="LabelKeyText" />
|
||||
<BoxContainer Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<Label Name="InletPressureLabel" Text="N/A" Margin="0 0 4 0" />
|
||||
<Label Text="{Loc gas-pressure-regulator-ui-pressure-unit}" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
|
||||
</BoxContainer>
|
||||
|
||||
<!-- Controls to Set Pressure -->
|
||||
<controls:StripeBack Name="SetPressureStripeBack" HorizontalExpand="True">
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" Margin="10 10 10 10">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<LineEdit Name="ThresholdInput" HorizontalExpand="True" MinSize="70 0" />
|
||||
<Button Name="SetThresholdButton" Text="{Loc gas-pressure-regulator-ui-set-threshold}"
|
||||
Disabled="True" Margin="5 0 0 0" />
|
||||
</BoxContainer>
|
||||
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="0 5 0 0">
|
||||
<Button Name="Subtract1000Button" Text="{Loc gas-pressure-regulator-ui-subtract-1000}"
|
||||
HorizontalExpand="True" Margin="0 2 2 0"
|
||||
StyleClasses="OpenBoth" />
|
||||
<Button Name="Subtract100Button" Text="{Loc gas-pressure-regulator-ui-subtract-100}"
|
||||
HorizontalExpand="True" Margin="0 2 2 0"
|
||||
StyleClasses="OpenBoth" />
|
||||
<Button Name="Subtract10Button" Text="{Loc gas-pressure-regulator-ui-subtract-10}"
|
||||
HorizontalExpand="True" Margin="0 2 2 0"
|
||||
StyleClasses="OpenBoth" />
|
||||
<Button Name="Add10Button" Text="{Loc gas-pressure-regulator-ui-add-10}" HorizontalExpand="True"
|
||||
Margin="0 2 2 0"
|
||||
StyleClasses="OpenBoth" />
|
||||
<Button Name="Add100Button" Text="{Loc gas-pressure-regulator-ui-add-100}"
|
||||
HorizontalExpand="True" Margin="0 2 2 0"
|
||||
StyleClasses="OpenBoth" />
|
||||
<Button Name="Add1000Button" Text="{Loc gas-pressure-regulator-ui-add-1000}"
|
||||
HorizontalExpand="True" Margin="0 2 2 0"
|
||||
StyleClasses="OpenBoth" />
|
||||
</BoxContainer>
|
||||
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="0 5 0 0">
|
||||
<Button Name="ZeroThresholdButton" Text="{Loc gas-pressure-regulator-ui-zero-threshold}"
|
||||
HorizontalExpand="True" Margin="0 0 5 0" />
|
||||
<Button Name="SetToCurrentPressureButton"
|
||||
Text="{Loc gas-pressure-regulator-ui-set-to-current-pressure}" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</controls:StripeBack>
|
||||
|
||||
</BoxContainer>
|
||||
|
||||
</controls:FancyWindow>
|
||||
129
Content.Client/Atmos/UI/GasPressureRegulatorWindow.xaml.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
using System.Globalization;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Atmos.UI;
|
||||
|
||||
/// <summary>
|
||||
/// Client-side UI for controlling a pressure regulator.
|
||||
/// </summary>
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class GasPressureRegulatorWindow : FancyWindow
|
||||
{
|
||||
private float _flowRate;
|
||||
|
||||
public GasPressureRegulatorWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
ThresholdInput.OnTextChanged += _ => SetThresholdButton.Disabled = false;
|
||||
SetThresholdButton.OnPressed += _ =>
|
||||
{
|
||||
ThresholdPressureChanged?.Invoke(ThresholdInput.Text ??= "");
|
||||
SetThresholdButton.Disabled = true;
|
||||
};
|
||||
|
||||
SetToCurrentPressureButton.OnPressed += _ =>
|
||||
{
|
||||
if (InletPressureLabel.Text != null)
|
||||
{
|
||||
ThresholdInput.Text = InletPressureLabel.Text;
|
||||
}
|
||||
|
||||
SetThresholdButton.Disabled = false;
|
||||
};
|
||||
|
||||
ZeroThresholdButton.OnPressed += _ =>
|
||||
{
|
||||
ThresholdInput.Text = "0";
|
||||
SetThresholdButton.Disabled = false;
|
||||
};
|
||||
|
||||
Add1000Button.OnPressed += _ => AdjustThreshold(1000);
|
||||
Add100Button.OnPressed += _ => AdjustThreshold(100);
|
||||
Add10Button.OnPressed += _ => AdjustThreshold(10);
|
||||
Subtract10Button.OnPressed += _ => AdjustThreshold(-10);
|
||||
Subtract100Button.OnPressed += _ => AdjustThreshold(-100);
|
||||
Subtract1000Button.OnPressed += _ => AdjustThreshold(-1000);
|
||||
return;
|
||||
|
||||
void AdjustThreshold(float adjustment)
|
||||
{
|
||||
if (float.TryParse(ThresholdInput.Text, out var currentValue))
|
||||
{
|
||||
ThresholdInput.Text = (currentValue + adjustment).ToString(CultureInfo.CurrentCulture);
|
||||
SetThresholdButton.Disabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event Action<string>? ThresholdPressureChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current threshold pressure label. This is not setting the threshold input box.
|
||||
/// </summary>
|
||||
/// <param name="threshold"> Threshold to set.</param>
|
||||
public void SetThresholdPressureLabel(float threshold)
|
||||
{
|
||||
TargetPressureLabel.Text = threshold.ToString(CultureInfo.CurrentCulture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the threshold pressure input field with the given value.
|
||||
/// When the client opens the UI the field will be autofilled with the current threshold pressure.
|
||||
/// </summary>
|
||||
/// <param name="input">The threshold pressure value to autofill into the input field.</param>
|
||||
public void SetThresholdPressureInput(float input)
|
||||
{
|
||||
ThresholdInput.Text = input.ToString(CultureInfo.CurrentCulture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the entity to be visible in the UI.
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
public void SetEntity(EntityUid entity)
|
||||
{
|
||||
EntityView.SetEntity(entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the UI for the labels.
|
||||
/// </summary>
|
||||
/// <param name="inletPressure">The current pressure at the valve's inlet.</param>
|
||||
/// <param name="outletPressure">The current pressure at the valve's outlet.</param>
|
||||
/// <param name="flowRate">The current flow rate through the valve.</param>
|
||||
public void UpdateInfo(float inletPressure, float outletPressure, float flowRate)
|
||||
{
|
||||
if (float.TryParse(TargetPressureLabel.Text, out var parsedfloat))
|
||||
ToTargetBar.Value = inletPressure / parsedfloat;
|
||||
|
||||
InletPressureLabel.Text = float.Round(inletPressure).ToString(CultureInfo.CurrentCulture);
|
||||
OutletPressureLabel.Text = float.Round(outletPressure).ToString(CultureInfo.CurrentCulture);
|
||||
CurrentFlowLabel.Text = float.IsNaN(flowRate) ? "0" : flowRate.ToString(CultureInfo.CurrentCulture);
|
||||
_flowRate = flowRate;
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
// Defines the flow rate at which the progress bar fills in one second.
|
||||
// If the flow rate is >50 L/s, the bar will take <1 second to fill.
|
||||
// If the flow rate is <50 L/s, the bar will take >1 second to fill.
|
||||
const int barFillPerSecond = 50;
|
||||
|
||||
var maxValue = FlowRateBar.MaxValue;
|
||||
|
||||
// Increment the progress bar value based on elapsed time
|
||||
FlowRateBar.Value += (_flowRate / barFillPerSecond) * args.DeltaSeconds;
|
||||
|
||||
// Reset the progress bar when it is fully filled
|
||||
if (FlowRateBar.Value >= maxValue)
|
||||
{
|
||||
FlowRateBar.Value = 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -252,6 +252,128 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
Merge(destination, buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the dimensionless fraction of gas required to equalize pressure between two gas mixtures.
|
||||
/// </summary>
|
||||
/// <param name="gasMixture1">The first gas mixture involved in the pressure equalization.
|
||||
/// This mixture should be the one you always expect to be the highest pressure.</param>
|
||||
/// <param name="gasMixture2">The second gas mixture involved in the pressure equalization.</param>
|
||||
/// <returns>A float (from 0 to 1) representing the dimensionless fraction of gas that needs to be transferred from the
|
||||
/// mixture of higher pressure to the mixture of lower pressure.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This properly takes into account the effect
|
||||
/// of gas merging from inlet to outlet affecting the temperature
|
||||
/// (and possibly increasing the pressure) in the outlet.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The gas is assumed to expand freely,
|
||||
/// so the temperature of the gas with the greater pressure is not changing.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// If you want to calculate the moles required to equalize pressure between an inlet and an outlet,
|
||||
/// multiply the fraction returned by the source moles.
|
||||
/// </example>
|
||||
public float FractionToEqualizePressure(GasMixture gasMixture1, GasMixture gasMixture2)
|
||||
{
|
||||
/*
|
||||
Problem: the gas being merged from the inlet to the outlet could affect the
|
||||
temp. of the gas and cause a pressure rise.
|
||||
We want the pressure to be equalized, so we have to account for this.
|
||||
|
||||
For clarity, let's assume that gasMixture1 is the inlet and gasMixture2 is the outlet.
|
||||
|
||||
We require mechanical equilibrium, so \( P_1' = P_2' \)
|
||||
|
||||
Before the transfer, we have:
|
||||
\( P_1 = \frac{n_1 R T_1}{V_1} \)
|
||||
\( P_2 = \frac{n_2 R T_2}{V_2} \)
|
||||
|
||||
After removing fraction \( x \) moles from the inlet, we have:
|
||||
\( P_1' = \frac{(1 - x) n_1 R T_1}{V_1} \)
|
||||
|
||||
The outlet will gain the same \( x n_1 \) moles of gas.
|
||||
So \( n_2' = n_2 + x n_1 \)
|
||||
|
||||
After mixing, the outlet temperature will be changed.
|
||||
Denote the new mixture temperature as \( T_2' \).
|
||||
Volume is constant.
|
||||
So we have:
|
||||
\( P_2' = \frac{(n_2 + x n_1) R T_2}{V_2} \)
|
||||
|
||||
The total energy of the incoming inlet to outlet gas at \( T_1 \) plus the existing energy of the outlet gas at \( T_2 \)
|
||||
will be equal to the energy of the new outlet gas at \( T_2' \).
|
||||
This leads to the following derivation:
|
||||
\( x n_1 C_1 T_1 + n_2 C_2 T_2 = (x n_1 C_1 + n_2 C_2) T_2' \)
|
||||
|
||||
Where \( C_1 \) and \( C_2 \) are the heat capacities of the inlet and outlet gases, respectively.
|
||||
|
||||
Solving for \( T_2' \) gives us:
|
||||
\( T_2' = \frac{x n_1 C_1 T_1 + n_2 C_2 T_2}{x n_1 C_1 + n_2 C_2} \)
|
||||
|
||||
Once again, we require mechanical equilibrium (\( P_1' = P_2' \)),
|
||||
so we can substitute \( T_2' \) into the pressure equation:
|
||||
|
||||
\( \frac{(1 - x) n_1 R T_1}{V_1} =
|
||||
\frac{(n_2 + x n_1) R}{V_2} \cdot
|
||||
\frac{x n_1 C_1 T_1 + n_2 C_2 T_2}
|
||||
{x n_1 C_1 + n_2 C_2} \)
|
||||
|
||||
Now it's a matter of solving for \( x \).
|
||||
Not going to show the full derivation here, just steps.
|
||||
1. Cancel common factor \( R \).
|
||||
2. Multiply both sides by \( x n_1 C_1 + n_2 C_2 \), so that everything
|
||||
becomes a polynomial in terms of \( x \).
|
||||
3. Expand both sides.
|
||||
4. Collect like powers of \( x \).
|
||||
5. After collecting, you should end up with a polynomial of the form:
|
||||
|
||||
\( (-n_1 C_1 T_1 (1 + \frac{V_2}{V_1})) x^2 +
|
||||
(n_1 T_1 \frac{V_2}{V_1} (C_1 - C_2) - n_2 C_1 T_1 - n_1 C_2 T_2) x +
|
||||
(n_1 T_1 \frac{V_2}{V_1} C_2 - n_2 C_2 T_2) = 0 \)
|
||||
|
||||
Divide through by \( n_1 C_1 T_1 \) and replace each ratio with a symbol for clarity:
|
||||
\( k_V = \frac{V_2}{V_1} \)
|
||||
\( k_n = \frac{n_2}{n_1} \)
|
||||
\( k_T = \frac{T_2}{T_1} \)
|
||||
\( k_C = \frac{C_2}{C_1} \)
|
||||
*/
|
||||
|
||||
// Ensure that P_1 > P_2 so the quadratic works out.
|
||||
if (gasMixture1.Pressure < gasMixture2.Pressure)
|
||||
{
|
||||
(gasMixture1, gasMixture2) = (gasMixture2, gasMixture1);
|
||||
}
|
||||
|
||||
// Establish the dimensionless ratios.
|
||||
var volumeRatio = gasMixture2.Volume / gasMixture1.Volume;
|
||||
var molesRatio = gasMixture2.TotalMoles / gasMixture1.TotalMoles;
|
||||
var temperatureRatio = gasMixture2.Temperature / gasMixture1.Temperature;
|
||||
var heatCapacityRatio = GetHeatCapacity(gasMixture2) / GetHeatCapacity(gasMixture1);
|
||||
|
||||
// The quadratic equation is solved for the transfer fraction.
|
||||
var quadraticA = 1 + volumeRatio;
|
||||
var quadraticB = molesRatio - volumeRatio + heatCapacityRatio * (temperatureRatio + volumeRatio);
|
||||
var quadraticC = heatCapacityRatio * (molesRatio * temperatureRatio - volumeRatio);
|
||||
|
||||
return (-quadraticB + MathF.Sqrt(quadraticB * quadraticB - 4 * quadraticA * quadraticC)) / (2 * quadraticA);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the number of moles that need to be removed from a <see cref="GasMixture"/> to reach a target pressure threshold.
|
||||
/// </summary>
|
||||
/// <param name="gasMixture">The gas mixture whose moles and properties will be used in the calculation.</param>
|
||||
/// <param name="targetPressure">The target pressure threshold to calculate against.</param>
|
||||
/// <returns>The difference in moles required to reach the target pressure threshold.</returns>
|
||||
/// <remarks>The temperature of the gas is assumed to be not changing due to a free expansion.</remarks>
|
||||
public static float MolesToPressureThreshold(GasMixture gasMixture, float targetPressure)
|
||||
{
|
||||
// Kid named PV = nRT.
|
||||
return gasMixture.TotalMoles -
|
||||
targetPressure * gasMixture.Volume / (Atmospherics.R * gasMixture.Temperature);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether a gas mixture is probably safe.
|
||||
/// This only checks temperature and pressure, not gas composition.
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Atmos.Piping.Components;
|
||||
using Content.Server.NodeContainer.EntitySystems;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
using Content.Shared.Atmos.Piping;
|
||||
using Content.Shared.Atmos.Piping.Binary.Components;
|
||||
using Content.Shared.Audio;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Binary.EntitySystems;
|
||||
|
||||
/// <summary>
|
||||
/// Handles serverside logic for pressure regulators. Gas will only flow through the regulator
|
||||
/// if the pressure on the inlet side is over a certain pressure threshold.
|
||||
/// See https://en.wikipedia.org/wiki/Pressure_regulator
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed class GasPressureRegulatorSystem : SharedGasPressureRegulatorSystem
|
||||
{
|
||||
[Dependency] private readonly SharedAmbientSoundSystem _ambientSound = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
||||
[Dependency] private readonly NodeContainerSystem _nodeContainer = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<GasPressureRegulatorComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<GasPressureRegulatorComponent, AtmosDeviceUpdateEvent>(OnPressureRegulatorUpdated);
|
||||
SubscribeLocalEvent<GasPressureRegulatorComponent, MapInitEvent>(OnMapInit);
|
||||
}
|
||||
|
||||
private void OnMapInit(Entity<GasPressureRegulatorComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
ent.Comp.NextUiUpdate = _timing.CurTime + ent.Comp.UpdateInterval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dirties the regulator every second or so, so that the UI can update.
|
||||
/// The UI automatically updates after an AutoHandleStateEvent.
|
||||
/// </summary>
|
||||
/// <param name="frameTime"></param>
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQueryEnumerator<GasPressureRegulatorComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var comp))
|
||||
{
|
||||
if (comp.NextUiUpdate > _timing.CurTime)
|
||||
continue;
|
||||
|
||||
comp.NextUiUpdate += comp.UpdateInterval;
|
||||
|
||||
DirtyFields(uid,
|
||||
comp,
|
||||
null,
|
||||
nameof(comp.InletPressure),
|
||||
nameof(comp.OutletPressure),
|
||||
nameof(comp.FlowRate));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnInit(Entity<GasPressureRegulatorComponent> ent, ref ComponentInit args)
|
||||
{
|
||||
UpdateAppearance(ent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the updating logic for the pressure regulator.
|
||||
/// </summary>
|
||||
/// <param name="ent"> the <see cref="Entity{T}" /> of the pressure regulator</param>
|
||||
/// <param name="args"> Args provided to us via <see cref="AtmosDeviceUpdateEvent" /></param>
|
||||
private void OnPressureRegulatorUpdated(Entity<GasPressureRegulatorComponent> ent,
|
||||
ref AtmosDeviceUpdateEvent args)
|
||||
{
|
||||
if (!_nodeContainer.TryGetNodes(ent.Owner,
|
||||
ent.Comp.InletName,
|
||||
ent.Comp.OutletName,
|
||||
out PipeNode? inletPipeNode,
|
||||
out PipeNode? outletPipeNode))
|
||||
{
|
||||
ChangeStatus(false, ent, inletPipeNode, outletPipeNode, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
It's time for some math! :)
|
||||
|
||||
Gas is simply transferred from the inlet to the outlet, restricted by flow rate and pressure.
|
||||
We want to transfer enough gas to bring the inlet pressure below the threshold,
|
||||
and only as much as our max flow rate allows.
|
||||
|
||||
The equations:
|
||||
PV = nRT
|
||||
P1 = P2
|
||||
|
||||
Can be used to calculate the amount of gas we need to transfer.
|
||||
*/
|
||||
|
||||
var p1 = inletPipeNode.Air.Pressure;
|
||||
var p2 = outletPipeNode.Air.Pressure;
|
||||
|
||||
if (p1 <= ent.Comp.Threshold || p2 >= p1)
|
||||
{
|
||||
ChangeStatus(false, ent, inletPipeNode, outletPipeNode, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
var t1 = inletPipeNode.Air.Temperature;
|
||||
|
||||
// First, calculate the amount of gas we need to transfer to bring us below the threshold.
|
||||
var deltaMolesToPressureThreshold =
|
||||
AtmosphereSystem.MolesToPressureThreshold(inletPipeNode.Air, ent.Comp.Threshold);
|
||||
|
||||
// Second, calculate the moles required to equalize the pressure.
|
||||
// We round here to avoid the valve staying enabled for 0.00001 pressure differences.
|
||||
var deltaMolesToEqualizePressure =
|
||||
float.Round(_atmosphere.FractionToEqualizePressure(inletPipeNode.Air, outletPipeNode.Air) *
|
||||
inletPipeNode.Air.TotalMoles,
|
||||
1,
|
||||
MidpointRounding.ToPositiveInfinity);
|
||||
|
||||
// Third, make sure we only transfer the minimum of the two.
|
||||
// We do this so that we don't accidentally transfer so much gas to the point
|
||||
// where the outlet pressure is higher than the inlet.
|
||||
var deltaMolesToTransfer = Math.Min(deltaMolesToPressureThreshold, deltaMolesToEqualizePressure);
|
||||
|
||||
// Fourth, convert to the desired volume to transfer.
|
||||
var desiredVolumeToTransfer = deltaMolesToTransfer * ((Atmospherics.R * t1) / p1);
|
||||
|
||||
// And finally, limit the transfer volume to the max flow rate of the valve.
|
||||
var actualVolumeToTransfer = Math.Min(desiredVolumeToTransfer,
|
||||
ent.Comp.MaxTransferRate * _atmosphere.PumpSpeedup() * args.dt);
|
||||
|
||||
// We remove the gas from the inlet and merge it into the outlet.
|
||||
var removed = inletPipeNode.Air.RemoveVolume(actualVolumeToTransfer);
|
||||
_atmosphere.Merge(outletPipeNode.Air, removed);
|
||||
|
||||
// Calculate the flow rate in L/s for the UI.
|
||||
var sentFlowRate = MathF.Round(actualVolumeToTransfer / args.dt, 1);
|
||||
|
||||
ChangeStatus(true, ent, inletPipeNode, outletPipeNode, sentFlowRate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the visual appearance of the pressure regulator based on its current state.
|
||||
/// </summary>
|
||||
/// <param name="ent">The <see cref="Entity{GasPressureRegulatorComponent, AppearanceComponent}"/>
|
||||
/// representing the pressure regulator with respective components.</param>
|
||||
private void UpdateAppearance(Entity<GasPressureRegulatorComponent> ent)
|
||||
{
|
||||
_appearance.SetData(ent,
|
||||
PressureRegulatorVisuals.State,
|
||||
ent.Comp.Enabled);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the pressure regulator's appearance and sound based on its current state, while
|
||||
/// also preventing network spamming.
|
||||
/// Also prepares data for dirtying.
|
||||
/// </summary>
|
||||
/// <param name="enabled">The new state to set</param>
|
||||
/// <param name="ent">The pressure regulator to update</param>
|
||||
/// <param name="inletNode">The inlet node of the pressure regulator</param>
|
||||
/// <param name="outletNode">The outlet node of the pressure regulator</param>
|
||||
/// <param name="flowRate">Current flow rate of the pressure regulator</param>
|
||||
private void ChangeStatus(bool enabled,
|
||||
Entity<GasPressureRegulatorComponent> ent,
|
||||
PipeNode? inletNode,
|
||||
PipeNode? outletNode,
|
||||
float flowRate)
|
||||
{
|
||||
// First, set data on the component server-side.
|
||||
ent.Comp.InletPressure = inletNode?.Air.Pressure ?? 0f;
|
||||
ent.Comp.OutletPressure = outletNode?.Air.Pressure ?? 0f;
|
||||
ent.Comp.FlowRate = flowRate;
|
||||
|
||||
// We need to prevent spamming the network with updates, so only check if we've
|
||||
// switched states.
|
||||
if (ent.Comp.Enabled == enabled)
|
||||
return;
|
||||
|
||||
ent.Comp.Enabled = enabled;
|
||||
_ambientSound.SetAmbience(ent, enabled);
|
||||
UpdateAppearance(ent);
|
||||
|
||||
// The regulator has changed state, so we need to dirty all applicable fields *right now* so the UI updates
|
||||
// at the same time as everything else.
|
||||
DirtyFields(ent.AsNullable(),
|
||||
null,
|
||||
nameof(ent.Comp.InletPressure),
|
||||
nameof(ent.Comp.OutletPressure),
|
||||
nameof(ent.Comp.FlowRate));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.Piping.Binary.Components;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Examine;
|
||||
|
||||
namespace Content.Shared.Atmos.EntitySystems;
|
||||
|
||||
/// <summary>
|
||||
/// Handles all shared interactions with the gas pressure regulator.
|
||||
/// </summary>
|
||||
public abstract class SharedGasPressureRegulatorSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
[Dependency] protected readonly SharedUserInterfaceSystem UserInterfaceSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<GasPressureRegulatorComponent, ExaminedEvent>(OnExamined);
|
||||
SubscribeLocalEvent<GasPressureRegulatorComponent, GasPressureRegulatorChangeThresholdMessage>(
|
||||
OnThresholdChangeMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Presents predicted examine information to the person examining the valve.
|
||||
/// </summary>
|
||||
/// <param name="ent"> <see cref="Entity{T}"/> of the valve</param>
|
||||
/// <param name="args">Event arguments for examination</param>
|
||||
private void OnExamined(Entity<GasPressureRegulatorComponent> ent, ref ExaminedEvent args)
|
||||
{
|
||||
if (!Transform(ent).Anchored || !args.IsInDetailsRange)
|
||||
return;
|
||||
|
||||
using (args.PushGroup(nameof(GasPressureRegulatorComponent)))
|
||||
{
|
||||
args.PushMarkup(Loc.GetString("gas-pressure-regulator-system-examined",
|
||||
("statusColor", ent.Comp.Enabled ? "green" : "red"),
|
||||
("open", ent.Comp.Enabled)));
|
||||
|
||||
args.PushMarkup(Loc.GetString("gas-pressure-regulator-examined-threshold-pressure",
|
||||
("threshold", $"{ent.Comp.Threshold:0.#}")));
|
||||
|
||||
args.PushMarkup(Loc.GetString("gas-pressure-regulator-examined-flow-rate",
|
||||
("flowRate", $"{ent.Comp.FlowRate:0.#}")));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates, logs, and updates the pressure threshold of the valve.
|
||||
/// </summary>
|
||||
/// <param name="ent">The <see cref="Entity{T}"/> of the valve.</param>
|
||||
/// <param name="args">The received pressure from the <see cref="GasPressurePumpChangeOutputPressureMessage"/>message.</param>
|
||||
private void OnThresholdChangeMessage(Entity<GasPressureRegulatorComponent> ent,
|
||||
ref GasPressureRegulatorChangeThresholdMessage args)
|
||||
{
|
||||
ent.Comp.Threshold = Math.Max(0f, args.ThresholdPressure);
|
||||
_adminLogger.Add(LogType.AtmosVolumeChanged,
|
||||
LogImpact.Medium,
|
||||
$"{ToPrettyString(args.Actor):player} set the pressure threshold on {ToPrettyString(ent):device} to {ent.Comp.Threshold}");
|
||||
// Dirty the entire entity to ensure we get all of that Fresh:tm: UI info from the server.
|
||||
Dirty(ent);
|
||||
UpdateUi(ent);
|
||||
}
|
||||
|
||||
protected virtual void UpdateUi(Entity<GasPressureRegulatorComponent> ent)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using Content.Shared.Guidebook;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Shared.Atmos.Piping.Binary.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a gas pressure regulator,
|
||||
/// which releases gas depending on a set pressure threshold between two pipe nodes.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[AutoGenerateComponentState(true, true), AutoGenerateComponentPause]
|
||||
public sealed partial class GasPressureRegulatorComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether the valve is open or closed.
|
||||
/// Used for showing the valve animation, the UI,
|
||||
/// and on examine.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool Enabled;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the pipe node name to be treated as the inlet.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string InletName = "inlet";
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the pipe node name to be treated as the outlet.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string OutletName = "outlet";
|
||||
|
||||
/// <summary>
|
||||
/// The max transfer rate of the pressure regulator.
|
||||
/// </summary>
|
||||
[GuidebookData]
|
||||
[DataField]
|
||||
public float MaxTransferRate = Atmospherics.MaxTransferRate;
|
||||
|
||||
/// <summary>
|
||||
/// The server time at which the next UI update will be sent.
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
[AutoPausedField]
|
||||
public TimeSpan NextUiUpdate = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the opening threshold of the pressure regulator.
|
||||
/// </summary>
|
||||
/// <example> If set to 500 kPa, the regulator will only
|
||||
/// open if the pressure in the inlet side is above
|
||||
/// 500 kPa. </example>
|
||||
[DataField, AutoNetworkedField]
|
||||
public float Threshold;
|
||||
|
||||
/// <summary>
|
||||
/// How often the UI update is sent.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan UpdateInterval = TimeSpan.FromSeconds(1);
|
||||
|
||||
#region UI/Examine Info
|
||||
|
||||
/// <summary>
|
||||
/// The current flow rate of the pressure regulator.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
[DataField, AutoNetworkedField]
|
||||
public float FlowRate;
|
||||
|
||||
/// <summary>
|
||||
/// Current inlet pressure the pressure regulator.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
[DataField, AutoNetworkedField]
|
||||
public float InletPressure;
|
||||
|
||||
/// <summary>
|
||||
/// Current outlet pressure of the pressure regulator.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
[DataField, AutoNetworkedField]
|
||||
public float OutletPressure;
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Atmos.Piping.Binary.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the unique key for the UI.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public enum GasPressureRegulatorUiKey : byte
|
||||
{
|
||||
Key,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Message sent to change the pressure threshold of the gas pressure regulator.
|
||||
/// </summary>
|
||||
/// <param name="pressure">The new pressure threshold value.</param>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class GasPressureRegulatorChangeThresholdMessage(float pressure) : BoundUserInterfaceMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the new threshold pressure value.
|
||||
/// </summary>
|
||||
public float ThresholdPressure { get; } = pressure;
|
||||
}
|
||||
@@ -31,4 +31,10 @@ namespace Content.Shared.Atmos.Piping
|
||||
{
|
||||
Enabled,
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum PressureRegulatorVisuals : byte
|
||||
{
|
||||
State,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# Examine Text
|
||||
gas-pressure-regulator-system-examined = The valve is [color={$statusColor}]{$open ->
|
||||
[true] open
|
||||
*[false] closed
|
||||
}[/color].
|
||||
gas-pressure-regulator-examined-threshold-pressure = The threshold pressure is set at [color=lightblue]{$threshold} kPa[/color].
|
||||
gas-pressure-regulator-examined-flow-rate = The flow rate meter indicates [color=lightblue]{$flowRate} L/s[/color].
|
||||
@@ -0,0 +1,19 @@
|
||||
# UI Labels
|
||||
gas-pressure-regulator-ui-set-threshold = Set
|
||||
gas-pressure-regulator-ui-zero-threshold = Zero
|
||||
gas-pressure-regulator-ui-set-to-current-pressure = Set to Inlet Pressure
|
||||
gas-pressure-regulator-ui-add-10 = +10
|
||||
gas-pressure-regulator-ui-add-100 = +100
|
||||
gas-pressure-regulator-ui-add-1000 = +1000
|
||||
gas-pressure-regulator-ui-subtract-10 = -10
|
||||
gas-pressure-regulator-ui-subtract-100 = -100
|
||||
gas-pressure-regulator-ui-subtract-1000 = -1000
|
||||
gas-pressure-regulator-ui-title = Inlet Pressure Regulator
|
||||
gas-pressure-regulator-ui-target = Setpoint
|
||||
gas-pressure-regulator-ui-flow = Flow
|
||||
gas-pressure-regulator-ui-outlet = Outlet
|
||||
gas-pressure-regulator-ui-inlet = Inlet
|
||||
|
||||
# Units
|
||||
gas-pressure-regulator-ui-flow-rate-unit = L/s
|
||||
gas-pressure-regulator-ui-pressure-unit = kPa
|
||||
@@ -20,6 +20,7 @@ guide-entry-manualvalve = Manual Valve
|
||||
guide-entry-signalvalve = Signal Valve
|
||||
guide-entry-pneumaticvalve = Pneumatic Valve
|
||||
guide-entry-passivegate = Passive Gate
|
||||
guide-entry-ressureregulator = Pressure Regulator
|
||||
guide-entry-mixingandfiltering = Mixing and Filtering
|
||||
guide-entry-gascanisters = Gas Canisters
|
||||
guide-entry-thermomachines = Thermomachines
|
||||
|
||||
@@ -155,6 +155,62 @@
|
||||
guides:
|
||||
- Pumps
|
||||
|
||||
- type: entity
|
||||
parent: GasBinaryBase
|
||||
id: GasPressureRegulator
|
||||
name: inlet pressure regulator
|
||||
description: A valve that releases gas when the inlet pressure exceeds a certain threshold.
|
||||
placement:
|
||||
mode: SnapgridCenter
|
||||
components:
|
||||
- type: Rotatable
|
||||
- type: Transform
|
||||
noRot: false
|
||||
- type: SubFloorHide
|
||||
visibleLayers:
|
||||
- enum.SubfloorLayers.FirstLayer
|
||||
- type: Sprite
|
||||
sprite: Structures/Piping/Atmospherics/pump.rsi
|
||||
layers:
|
||||
- sprite: Structures/Piping/Atmospherics/pipe.rsi
|
||||
state: pipeStraight
|
||||
map: [ "enum.PipeVisualLayers.Pipe" ]
|
||||
- state: pumpPressureRegulator
|
||||
map: [ "enum.SubfloorLayers.FirstLayer", "enabled" ]
|
||||
- type: Appearance
|
||||
- type: GenericVisualizer
|
||||
visuals:
|
||||
enum.PressureRegulatorVisuals.State:
|
||||
enabled:
|
||||
True: { state: pumpPressureRegulatorOn }
|
||||
False: { state: pumpPressureRegulator }
|
||||
- type: PipeColorVisuals
|
||||
- type: GasPressureRegulator
|
||||
enabled: false
|
||||
threshold: 4500
|
||||
- type: Construction
|
||||
graph: GasBinary
|
||||
node: pressureregulator
|
||||
- type: ActivatableUI
|
||||
key: enum.GasPressureRegulatorUiKey.Key
|
||||
blockSpectators: true
|
||||
- type: ActivatableUIRequiresAnchor
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
enum.GasPressureRegulatorUiKey.Key:
|
||||
type: GasPressureRegulatorBoundUserInterface
|
||||
- type: AmbientSound
|
||||
enabled: false
|
||||
volume: -9
|
||||
range: 5
|
||||
sound:
|
||||
path: /Audio/Ambience/Objects/gas_hiss.ogg
|
||||
- type: AtmosMonitoringConsoleDevice
|
||||
navMapBlip: GasFlowRegulator
|
||||
- type: GuideHelp
|
||||
guides:
|
||||
- PressureRegulator
|
||||
|
||||
- type: entity
|
||||
parent: GasBinaryBase
|
||||
id: GasPassiveGate
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
- SignalValve
|
||||
- PneumaticValve
|
||||
- PassiveGate
|
||||
- PressureRegulator
|
||||
|
||||
- type: guideEntry
|
||||
id: ManualValve
|
||||
@@ -141,6 +142,11 @@
|
||||
name: guide-entry-passivegate
|
||||
text: "/ServerInfo/Guidebook/Engineering/PassiveGate.xml"
|
||||
|
||||
- type: guideEntry
|
||||
id: PressureRegulator
|
||||
name: guide-entry-ressureregulator
|
||||
text: "/ServerInfo/Guidebook/Engineering/PressureRegulator.xml"
|
||||
|
||||
- type: guideEntry
|
||||
id: MixingAndFiltering
|
||||
name: guide-entry-mixingandfiltering
|
||||
|
||||
@@ -22,6 +22,12 @@
|
||||
amount: 2
|
||||
doAfter: 1
|
||||
|
||||
- to: pressureregulator
|
||||
steps:
|
||||
- material: Steel
|
||||
amount: 2
|
||||
doAfter: 1
|
||||
|
||||
- to: valve
|
||||
steps:
|
||||
- material: Steel
|
||||
@@ -106,6 +112,22 @@
|
||||
- tool: Welding
|
||||
doAfter: 1
|
||||
|
||||
- node: pressureregulator
|
||||
entity: GasPressureRegulator
|
||||
edges:
|
||||
- to: start
|
||||
conditions:
|
||||
- !type:EntityAnchored
|
||||
anchored: false
|
||||
completed:
|
||||
- !type:SpawnPrototype
|
||||
prototype: SheetSteel1
|
||||
amount: 2
|
||||
- !type:DeleteEntity
|
||||
steps:
|
||||
- tool: Welding
|
||||
doAfter: 1
|
||||
|
||||
- node: valve
|
||||
entity: GasValve
|
||||
edges:
|
||||
|
||||
@@ -572,6 +572,17 @@
|
||||
conditions:
|
||||
- !type:NoUnstackableInTile
|
||||
|
||||
- type: construction
|
||||
id: GasPressureRegulator
|
||||
graph: GasBinary
|
||||
startNode: start
|
||||
targetNode: pressureregulator
|
||||
category: construction-category-utilities
|
||||
placementMode: SnapgridCenter
|
||||
canBuildInImpassable: false
|
||||
conditions:
|
||||
- !type:NoUnstackableInTile
|
||||
|
||||
- type: construction
|
||||
id: GasValve
|
||||
graph: GasBinary
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
<GuideEntityEmbed Entity="SignalControlledValve" Caption=""/>
|
||||
<GuideEntityEmbed Entity="PressureControlledValve" Caption=""/>
|
||||
<GuideEntityEmbed Entity="GasPassiveGate" Caption=""/>
|
||||
<GuideEntityEmbed Entity="GasPressureRegulator" Caption=""/>
|
||||
</Box>
|
||||
<Box>
|
||||
## Valves
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<Document>
|
||||
# Inlet Pressure Regulator
|
||||
The Inlet Pressure Regulator is a passive device that allows gas to escape from a [textlink="pipenet" link="PipeNetworks"] when the pressure exceeds a certain threshold.
|
||||
|
||||
<Box>
|
||||
<GuideEntityEmbed Entity="GasPressureRegulator"/>
|
||||
</Box>
|
||||
|
||||
## Operation
|
||||
The valve will automatically [color=green]open[/color] when the pressure in the pipe exceeds the set threshold, allowing gas to escape to the connected output pipe.
|
||||
|
||||
The valve will [color=red]close[/color] again when the pressure drops below the set threshold.
|
||||
|
||||
The flow rate of the valve is limited to [color=orange][protodata="GasPressureRegulator" comp="GasPressureRegulator" member="MaxTransferRate"/] L/s[/color].
|
||||
|
||||
## Example Uses
|
||||
The valve is commonly used to prevent overpressure situations in gas systems, such as [textlink="TEG" link="TEG"] cooling loops and [textlink="pipes" link="Pipes"], which would cause a failure in the system (clogged [textlink="pumps" link="Pumps"]).
|
||||
|
||||
The valve can also be used to vent off ready-to-use, hot gas from a burn chamber.
|
||||
For example, it may be undesirable to allow a burn chamber to drop below a specific pressure for a long time, as this may cause the gas to cool down too much and thin out, which would cause a flameout.
|
||||
|
||||
An inlet pressure regulator can be used to vent off excess gas, while keeping the pressure in the burn chamber above a certain threshold, which may help in sustaining a fire in the chamber.
|
||||
</Document>
|
||||
@@ -6,6 +6,7 @@
|
||||
<GuideEntityEmbed Entity="SignalControlledValve"/>
|
||||
<GuideEntityEmbed Entity="PressureControlledValve"/>
|
||||
<GuideEntityEmbed Entity="GasPassiveGate"/>
|
||||
<GuideEntityEmbed Entity="GasPressureRegulator"/>
|
||||
</Box>
|
||||
All valves do not require [textlink="power" link="Power"] to operate.
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Taken from https://github.com/tgstation/tgstation at commit 57cd1d59ca019dd0e7811ac451f295f818e573da. Signal valve is a digital valve modified by deltanedas. Manual valve modified by Deerstop at https://github.com/space-wizards/space-station-14/pull/34378. pvalve taken from https://github.com/tgstation/tgstation/commit/584068b59e271c0108557902e8516c70d6ae56f2 and modified by ArtisticRoomba (GitHub)",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"license":"CC-BY-SA-3.0",
|
||||
"copyright":"Taken from https://github.com/tgstation/tgstation at commit 57cd1d59ca019dd0e7811ac451f295f818e573da. Signal valve is a digital valve modified by deltanedas. Manual valve modified by Deerstop at https://github.com/space-wizards/space-station-14/pull/34378",
|
||||
"states": [
|
||||
{
|
||||
"name": "pumpDigitalValve",
|
||||
@@ -42,7 +42,36 @@
|
||||
{
|
||||
"name": "pumpPressureOn",
|
||||
"directions": 4,
|
||||
"delays":[ [ 0.1, 0.1, 0.1, 0.1, 0.1 ], [ 0.1, 0.1, 0.1, 0.1, 0.1 ], [ 0.1, 0.1, 0.1, 0.1, 0.1 ], [ 0.1, 0.1, 0.1, 0.1, 0.1 ] ]
|
||||
"delays": [
|
||||
[
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1
|
||||
],
|
||||
[
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1
|
||||
],
|
||||
[
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1
|
||||
],
|
||||
[
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "pumpVolume",
|
||||
@@ -51,12 +80,86 @@
|
||||
{
|
||||
"name": "pumpVolumeOn",
|
||||
"directions": 4,
|
||||
"delays":[ [ 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1 ], [ 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1 ], [ 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1 ], [ 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1 ] ]
|
||||
"delays": [
|
||||
[
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1
|
||||
],
|
||||
[
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1
|
||||
],
|
||||
[
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1
|
||||
],
|
||||
[
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "pumpVolumeBlocked",
|
||||
"directions": 4,
|
||||
"delays":[ [ 1.0, 1.0 ], [ 1.0, 1.0 ], [ 1.0, 1.0 ], [ 1.0, 1.0 ] ]
|
||||
"delays": [
|
||||
[
|
||||
1,
|
||||
1
|
||||
],
|
||||
[
|
||||
1,
|
||||
1
|
||||
],
|
||||
[
|
||||
1,
|
||||
1
|
||||
],
|
||||
[
|
||||
1,
|
||||
1
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "pumpPressureRegulator",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "pumpPressureRegulatorOn",
|
||||
"directions": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 743 B |
|
After Width: | Height: | Size: 742 B |
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Taken from https://github.com/tgstation/tgstation at commit 57cd1d59ca019dd0e7811ac451f295f818e573da. Signal valve is a digital valve modified by deltanedas. Manual valve modified by Deerstop at https://github.com/space-wizards/space-station-14/pull/34378. Modified by chromiumboy. Modified by ArtisticRoomba.",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Taken from https://github.com/tgstation/tgstation at commit 57cd1d59ca019dd0e7811ac451f295f818e573da. Signal valve is a digital valve modified by deltanedas. Manual valve modified by Deerstop at https://github.com/space-wizards/space-station-14/pull/34378. Modified by chromiumboy.",
|
||||
"states": [
|
||||
{
|
||||
"name": "pumpDigitalValve",
|
||||
@@ -136,22 +136,30 @@
|
||||
"directions": 4,
|
||||
"delays": [
|
||||
[
|
||||
1.0,
|
||||
1.0
|
||||
1,
|
||||
1
|
||||
],
|
||||
[
|
||||
1.0,
|
||||
1.0
|
||||
1,
|
||||
1
|
||||
],
|
||||
[
|
||||
1.0,
|
||||
1.0
|
||||
1,
|
||||
1
|
||||
],
|
||||
[
|
||||
1.0,
|
||||
1.0
|
||||
1,
|
||||
1
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "pumpPressureRegulator",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "pumpPressureRegulatorOn",
|
||||
"directions": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 618 B |
|
After Width: | Height: | Size: 618 B |
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Taken from https://github.com/tgstation/tgstation at commit 57cd1d59ca019dd0e7811ac451f295f818e573da. Signal valve is a digital valve modified by deltanedas. Manual valve modified by Deerstop at https://github.com/space-wizards/space-station-14/pull/34378. Modified by chromiumboy. Modified by ArtisticRoomba.",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Taken from https://github.com/tgstation/tgstation at commit 57cd1d59ca019dd0e7811ac451f295f818e573da. Signal valve is a digital valve modified by deltanedas. Manual valve modified by Deerstop at https://github.com/space-wizards/space-station-14/pull/34378. Modified by chromiumboy.",
|
||||
"states": [
|
||||
{
|
||||
"name": "pumpDigitalValve",
|
||||
@@ -136,22 +136,30 @@
|
||||
"directions": 4,
|
||||
"delays": [
|
||||
[
|
||||
1.0,
|
||||
1.0
|
||||
1,
|
||||
1
|
||||
],
|
||||
[
|
||||
1.0,
|
||||
1.0
|
||||
1,
|
||||
1
|
||||
],
|
||||
[
|
||||
1.0,
|
||||
1.0
|
||||
1,
|
||||
1
|
||||
],
|
||||
[
|
||||
1.0,
|
||||
1.0
|
||||
1,
|
||||
1
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "pumpPressureRegulator",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "pumpPressureRegulatorOn",
|
||||
"directions": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 618 B |
|
After Width: | Height: | Size: 618 B |