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