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);
|
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>
|
/// <summary>
|
||||||
/// Checks whether a gas mixture is probably safe.
|
/// Checks whether a gas mixture is probably safe.
|
||||||
/// This only checks temperature and pressure, not gas composition.
|
/// 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,
|
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-signalvalve = Signal Valve
|
||||||
guide-entry-pneumaticvalve = Pneumatic Valve
|
guide-entry-pneumaticvalve = Pneumatic Valve
|
||||||
guide-entry-passivegate = Passive Gate
|
guide-entry-passivegate = Passive Gate
|
||||||
|
guide-entry-ressureregulator = Pressure Regulator
|
||||||
guide-entry-mixingandfiltering = Mixing and Filtering
|
guide-entry-mixingandfiltering = Mixing and Filtering
|
||||||
guide-entry-gascanisters = Gas Canisters
|
guide-entry-gascanisters = Gas Canisters
|
||||||
guide-entry-thermomachines = Thermomachines
|
guide-entry-thermomachines = Thermomachines
|
||||||
|
|||||||
@@ -155,6 +155,62 @@
|
|||||||
guides:
|
guides:
|
||||||
- Pumps
|
- 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
|
- type: entity
|
||||||
parent: GasBinaryBase
|
parent: GasBinaryBase
|
||||||
id: GasPassiveGate
|
id: GasPassiveGate
|
||||||
|
|||||||
@@ -120,6 +120,7 @@
|
|||||||
- SignalValve
|
- SignalValve
|
||||||
- PneumaticValve
|
- PneumaticValve
|
||||||
- PassiveGate
|
- PassiveGate
|
||||||
|
- PressureRegulator
|
||||||
|
|
||||||
- type: guideEntry
|
- type: guideEntry
|
||||||
id: ManualValve
|
id: ManualValve
|
||||||
@@ -141,6 +142,11 @@
|
|||||||
name: guide-entry-passivegate
|
name: guide-entry-passivegate
|
||||||
text: "/ServerInfo/Guidebook/Engineering/PassiveGate.xml"
|
text: "/ServerInfo/Guidebook/Engineering/PassiveGate.xml"
|
||||||
|
|
||||||
|
- type: guideEntry
|
||||||
|
id: PressureRegulator
|
||||||
|
name: guide-entry-ressureregulator
|
||||||
|
text: "/ServerInfo/Guidebook/Engineering/PressureRegulator.xml"
|
||||||
|
|
||||||
- type: guideEntry
|
- type: guideEntry
|
||||||
id: MixingAndFiltering
|
id: MixingAndFiltering
|
||||||
name: guide-entry-mixingandfiltering
|
name: guide-entry-mixingandfiltering
|
||||||
|
|||||||
@@ -22,6 +22,12 @@
|
|||||||
amount: 2
|
amount: 2
|
||||||
doAfter: 1
|
doAfter: 1
|
||||||
|
|
||||||
|
- to: pressureregulator
|
||||||
|
steps:
|
||||||
|
- material: Steel
|
||||||
|
amount: 2
|
||||||
|
doAfter: 1
|
||||||
|
|
||||||
- to: valve
|
- to: valve
|
||||||
steps:
|
steps:
|
||||||
- material: Steel
|
- material: Steel
|
||||||
@@ -106,6 +112,22 @@
|
|||||||
- tool: Welding
|
- tool: Welding
|
||||||
doAfter: 1
|
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
|
- node: valve
|
||||||
entity: GasValve
|
entity: GasValve
|
||||||
edges:
|
edges:
|
||||||
|
|||||||
@@ -572,6 +572,17 @@
|
|||||||
conditions:
|
conditions:
|
||||||
- !type:NoUnstackableInTile
|
- !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
|
- type: construction
|
||||||
id: GasValve
|
id: GasValve
|
||||||
graph: GasBinary
|
graph: GasBinary
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
<GuideEntityEmbed Entity="SignalControlledValve" Caption=""/>
|
<GuideEntityEmbed Entity="SignalControlledValve" Caption=""/>
|
||||||
<GuideEntityEmbed Entity="PressureControlledValve" Caption=""/>
|
<GuideEntityEmbed Entity="PressureControlledValve" Caption=""/>
|
||||||
<GuideEntityEmbed Entity="GasPassiveGate" Caption=""/>
|
<GuideEntityEmbed Entity="GasPassiveGate" Caption=""/>
|
||||||
|
<GuideEntityEmbed Entity="GasPressureRegulator" Caption=""/>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
## Valves
|
## 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="SignalControlledValve"/>
|
||||||
<GuideEntityEmbed Entity="PressureControlledValve"/>
|
<GuideEntityEmbed Entity="PressureControlledValve"/>
|
||||||
<GuideEntityEmbed Entity="GasPassiveGate"/>
|
<GuideEntityEmbed Entity="GasPassiveGate"/>
|
||||||
|
<GuideEntityEmbed Entity="GasPressureRegulator"/>
|
||||||
</Box>
|
</Box>
|
||||||
All valves do not require [textlink="power" link="Power"] to operate.
|
All valves do not require [textlink="power" link="Power"] to operate.
|
||||||
|
|
||||||
|
|||||||
@@ -1,62 +1,165 @@
|
|||||||
{
|
{
|
||||||
"version":1,
|
"version": 1,
|
||||||
"size":{
|
"license": "CC-BY-SA-3.0",
|
||||||
"x":32,
|
"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)",
|
||||||
"y":32
|
"size": {
|
||||||
},
|
"x": 32,
|
||||||
"license":"CC-BY-SA-3.0",
|
"y": 32
|
||||||
"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":[
|
"states": [
|
||||||
{
|
{
|
||||||
"name":"pumpDigitalValve",
|
"name": "pumpDigitalValve",
|
||||||
"directions":4
|
"directions": 4
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name":"pumpManualValve",
|
"name": "pumpManualValve",
|
||||||
"directions":4
|
"directions": 4
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name":"pumpManualValveOn",
|
"name": "pumpManualValveOn",
|
||||||
"directions":4
|
"directions": 4
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name":"pumpSignalValve",
|
"name": "pumpSignalValve",
|
||||||
"directions":4
|
"directions": 4
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name":"pumpSignalValveOn",
|
"name": "pumpSignalValveOn",
|
||||||
"directions":4
|
"directions": 4
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name":"pumpPassiveGate",
|
"name": "pumpPassiveGate",
|
||||||
"directions":4
|
"directions": 4
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name":"pumpPassiveGateOn",
|
"name": "pumpPassiveGateOn",
|
||||||
"directions":4
|
"directions": 4
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name":"pumpPressure",
|
"name": "pumpPressure",
|
||||||
"directions":4
|
"directions": 4
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name":"pumpPressureOn",
|
"name": "pumpPressureOn",
|
||||||
"directions":4,
|
"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,
|
||||||
"name":"pumpVolume",
|
0.1,
|
||||||
"directions":4
|
0.1,
|
||||||
},
|
0.1,
|
||||||
{
|
0.1
|
||||||
"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 ] ]
|
0.1,
|
||||||
},
|
0.1,
|
||||||
{
|
0.1,
|
||||||
"name":"pumpVolumeBlocked",
|
0.1,
|
||||||
"directions":4,
|
0.1
|
||||||
"delays":[ [ 1.0, 1.0 ], [ 1.0, 1.0 ], [ 1.0, 1.0 ], [ 1.0, 1.0 ] ]
|
],
|
||||||
}
|
[
|
||||||
]
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pumpVolume",
|
||||||
|
"directions": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pumpVolumeBlocked",
|
||||||
|
"directions": 4,
|
||||||
|
"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,
|
"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": {
|
"size": {
|
||||||
"x": 32,
|
"x": 32,
|
||||||
"y": 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": [
|
"states": [
|
||||||
{
|
{
|
||||||
"name": "pumpDigitalValve",
|
"name": "pumpDigitalValve",
|
||||||
@@ -136,22 +136,30 @@
|
|||||||
"directions": 4,
|
"directions": 4,
|
||||||
"delays": [
|
"delays": [
|
||||||
[
|
[
|
||||||
1.0,
|
1,
|
||||||
1.0
|
1
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
1.0,
|
1,
|
||||||
1.0
|
1
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
1.0,
|
1,
|
||||||
1.0
|
1
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
1.0,
|
1,
|
||||||
1.0
|
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,
|
"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": {
|
"size": {
|
||||||
"x": 32,
|
"x": 32,
|
||||||
"y": 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": [
|
"states": [
|
||||||
{
|
{
|
||||||
"name": "pumpDigitalValve",
|
"name": "pumpDigitalValve",
|
||||||
@@ -136,22 +136,30 @@
|
|||||||
"directions": 4,
|
"directions": 4,
|
||||||
"delays": [
|
"delays": [
|
||||||
[
|
[
|
||||||
1.0,
|
1,
|
||||||
1.0
|
1
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
1.0,
|
1,
|
||||||
1.0
|
1
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
1.0,
|
1,
|
||||||
1.0
|
1
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
1.0,
|
1,
|
||||||
1.0
|
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 |