diff --git a/Content.Server/Atmos/Components/GasTankComponent.cs b/Content.Server/Atmos/Components/GasTankComponent.cs
index 23c9703820..82e6483ae4 100644
--- a/Content.Server/Atmos/Components/GasTankComponent.cs
+++ b/Content.Server/Atmos/Components/GasTankComponent.cs
@@ -32,24 +32,26 @@ namespace Content.Server.Atmos.Components
public IPlayingAudioStream? ConnectStream;
public IPlayingAudioStream? DisconnectStream;
- [DataField("air")] public GasMixture Air { get; set; } = new();
+ [DataField("air"), ViewVariables(VVAccess.ReadWrite)]
+ public GasMixture Air { get; set; } = new();
///
/// Pressure at which tank should be considered 'low' such as for internals.
///
- [DataField("tankLowPressure")]
- public float TankLowPressure { get; set; } = DefaultLowPressure;
+ [DataField("tankLowPressure"), ViewVariables(VVAccess.ReadWrite)]
+ public float TankLowPressure = DefaultLowPressure;
///
/// Distributed pressure.
///
- [DataField("outputPressure")]
- public float OutputPressure { get; set; } = DefaultOutputPressure;
+ [DataField("outputPressure"), ViewVariables(VVAccess.ReadWrite)]
+ public float OutputPressure = DefaultOutputPressure;
///
/// Tank is connected to internals.
///
- [ViewVariables] public bool IsConnected => User != null;
+ [ViewVariables]
+ public bool IsConnected => User != null;
[ViewVariables]
public EntityUid? User;
@@ -64,28 +66,47 @@ namespace Content.Server.Atmos.Components
///
/// Pressure at which tanks start leaking.
///
- [DataField("tankLeakPressure")]
- public float TankLeakPressure { get; set; } = 30 * Atmospherics.OneAtmosphere;
+ [DataField("tankLeakPressure"), ViewVariables(VVAccess.ReadWrite)]
+ public float TankLeakPressure = 30 * Atmospherics.OneAtmosphere;
///
/// Pressure at which tank spills all contents into atmosphere.
///
- [DataField("tankRupturePressure")]
- public float TankRupturePressure { get; set; } = 40 * Atmospherics.OneAtmosphere;
+ [DataField("tankRupturePressure"), ViewVariables(VVAccess.ReadWrite)]
+ public float TankRupturePressure = 40 * Atmospherics.OneAtmosphere;
///
/// Base 3x3 explosion.
///
- [DataField("tankFragmentPressure")]
- public float TankFragmentPressure { get; set; } = 50 * Atmospherics.OneAtmosphere;
+ [DataField("tankFragmentPressure"), ViewVariables(VVAccess.ReadWrite)]
+ public float TankFragmentPressure = 50 * Atmospherics.OneAtmosphere;
///
/// Increases explosion for each scale kPa above threshold.
///
- [DataField("tankFragmentScale")]
- public float TankFragmentScale { get; set; } = 2 * Atmospherics.OneAtmosphere;
+ [DataField("tankFragmentScale"), ViewVariables(VVAccess.ReadWrite)]
+ public float TankFragmentScale = 2 * Atmospherics.OneAtmosphere;
[DataField("toggleAction", required: true)]
public InstantAction ToggleAction = new();
+
+ ///
+ /// Valve to release gas from tank
+ ///
+ [DataField("isValveOpen"), ViewVariables(VVAccess.ReadWrite)]
+ public bool IsValveOpen = false;
+
+ ///
+ /// Gas release rate in L/s
+ ///
+ [DataField("valveOutputRate"), ViewVariables(VVAccess.ReadWrite)]
+ public float ValveOutputRate = 100f;
+
+ [DataField("valveSound"), ViewVariables(VVAccess.ReadWrite)]
+ public SoundSpecifier ValveSound =
+ new SoundCollectionSpecifier("valveSqueak")
+ {
+ Params = AudioParams.Default.WithVolume(-5f),
+ };
}
}
diff --git a/Content.Server/Atmos/EntitySystems/GasTankSystem.cs b/Content.Server/Atmos/EntitySystems/GasTankSystem.cs
index 220d47520c..eea5e6cf53 100644
--- a/Content.Server/Atmos/EntitySystems/GasTankSystem.cs
+++ b/Content.Server/Atmos/EntitySystems/GasTankSystem.cs
@@ -1,3 +1,4 @@
+using System.Numerics;
using Content.Server.Atmos.Components;
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
@@ -15,6 +16,9 @@ using Robust.Server.Player;
using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Robust.Shared.Player;
+using Robust.Shared.Random;
+using Content.Shared.Verbs;
+using Robust.Shared.Physics.Systems;
namespace Content.Server.Atmos.EntitySystems
{
@@ -28,6 +32,8 @@ namespace Content.Server.Atmos.EntitySystems
[Dependency] private readonly SharedContainerSystem _containers = default!;
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly UserInterfaceSystem _ui = default!;
+ [Dependency] private readonly SharedPhysicsSystem _physics = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
private const float TimerDelay = 0.5f;
private float _timer = 0f;
@@ -45,6 +51,7 @@ namespace Content.Server.Atmos.EntitySystems
SubscribeLocalEvent(OnGasTankToggleInternals);
SubscribeLocalEvent(OnAnalyzed);
SubscribeLocalEvent(OnGasTankPrice);
+ SubscribeLocalEvent>(OnGetAlternativeVerb);
}
private void OnGasShutdown(EntityUid uid, GasTankComponent component, ComponentShutdown args)
@@ -103,6 +110,7 @@ namespace Content.Server.Atmos.EntitySystems
args.PushMarkup(Loc.GetString("comp-gas-tank-examine", ("pressure", Math.Round(component.Air?.Pressure ?? 0))));
if (component.IsConnected)
args.PushMarkup(Loc.GetString("comp-gas-tank-connected"));
+ args.PushMarkup(Loc.GetString(component.IsValveOpen ? "comp-gas-tank-examine-open-valve" : "comp-gas-tank-examine-closed-valve"));
}
private void OnActionToggle(EntityUid uid, GasTankComponent component, ToggleActionEvent args)
@@ -123,27 +131,50 @@ namespace Content.Server.Atmos.EntitySystems
if (_timer < TimerDelay) return;
_timer -= TimerDelay;
- foreach (var gasTank in EntityManager.EntityQuery())
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out var gasTank))
{
+ if (gasTank.IsValveOpen && !gasTank.IsLowPressure)
+ {
+ ReleaseGas(uid, gasTank);
+ }
+
if (gasTank.CheckUser)
{
gasTank.CheckUser = false;
- if (Transform(gasTank.Owner).ParentUid != gasTank.User)
+ if (Transform(uid).ParentUid != gasTank.User)
{
DisconnectFromInternals(gasTank);
continue;
}
}
- _atmosphereSystem.React(gasTank.Air, gasTank);
+ if (gasTank.Air != null)
+ {
+ _atmosphereSystem.React(gasTank.Air, gasTank);
+ }
CheckStatus(gasTank);
- if (_ui.IsUiOpen(gasTank.Owner, SharedGasTankUiKey.Key))
+ if (_ui.IsUiOpen(uid, SharedGasTankUiKey.Key))
{
UpdateUserInterface(gasTank);
}
}
}
+ private void ReleaseGas(EntityUid uid, GasTankComponent component)
+ {
+ var removed = RemoveAirVolume(component, component.ValveOutputRate * TimerDelay);
+ var environment = _atmosphereSystem.GetContainingMixture(uid, false, true);
+ if (environment != null)
+ {
+ _atmosphereSystem.Merge(environment, removed);
+ }
+ var impulse = removed.TotalMoles * removed.Temperature;
+ _physics.ApplyLinearImpulse(uid, _random.NextAngle().ToWorldVec() * impulse);
+ _physics.ApplyAngularImpulse(uid, _random.NextFloat(-3f, 3f));
+ _audioSys.PlayPvs(component.RuptureSound, uid);
+ }
+
private void ToggleInternals(GasTankComponent component)
{
if (component.IsConnected)
@@ -183,7 +214,7 @@ namespace Content.Server.Atmos.EntitySystems
public bool CanConnectToInternals(GasTankComponent component)
{
var internals = GetInternalsComponent(component);
- return internals != null && internals.BreathToolEntity != null;
+ return internals != null && internals.BreathToolEntity != null && !component.IsValveOpen;
}
public void ConnectToInternals(GasTankComponent component)
@@ -330,5 +361,21 @@ namespace Content.Server.Atmos.EntitySystems
{
args.Price += _atmosphereSystem.GetPrice(component.Air);
}
+
+ private void OnGetAlternativeVerb(EntityUid uid, GasTankComponent component, GetVerbsEvent args)
+ {
+ if (!args.CanAccess || !args.CanInteract || args.Hands == null)
+ return;
+ args.Verbs.Add(new AlternativeVerb()
+ {
+ Text = component.IsValveOpen ? Loc.GetString("comp-gas-tank-close-valve") : Loc.GetString("comp-gas-tank-open-valve"),
+ Act = () =>
+ {
+ component.IsValveOpen = !component.IsValveOpen;
+ _audioSys.PlayPvs(component.ValveSound, uid);
+ },
+ Disabled = component.IsConnected,
+ });
+ }
}
}
diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs
index 70fc5bafad..416e2e4f64 100644
--- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs
+++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs
@@ -260,7 +260,7 @@ public sealed class GasCanisterSystem : EntitySystem
return;
// Check the used item is valid...
- if (!HasComp(args.Used))
+ if (!TryComp(args.Used, out var gasTank) || gasTank.IsValveOpen)
return;
// Preventing inserting a tank since if its locked you cant remove it.
diff --git a/Resources/Locale/en-US/atmos/gas-tank-component.ftl b/Resources/Locale/en-US/atmos/gas-tank-component.ftl
index ee7e3bfb22..ae10d5630c 100644
--- a/Resources/Locale/en-US/atmos/gas-tank-component.ftl
+++ b/Resources/Locale/en-US/atmos/gas-tank-component.ftl
@@ -6,6 +6,10 @@ comp-gas-tank-examine = Pressure: [color=orange]{PRESSURE($pressure)}[/color].
# Examine text when internals are active.
comp-gas-tank-connected = It's connected to an external component.
+# Examine text when valve is open or closed.
+comp-gas-tank-examine-open-valve = Gas release valve is [color=red]open[/color].
+comp-gas-tank-examine-closed-valve = Gas release valve is [color=green]closed[/color].
+
## ControlVerb
control-verb-open-control-panel-text = Open Control Panel
@@ -17,3 +21,7 @@ gas-tank-window-tank-pressure-text = Pressure: {$tankPressure} kPA
gas-tank-window-internal-text = Internals: {$status}
gas-tank-window-internal-connected = [color=green]Connected[/color]
gas-tank-window-internal-disconnected = [color=red]Disconnected[/color]
+
+## Valve
+comp-gas-tank-open-valve = Open Valve
+comp-gas-tank-close-valve = Close Valve