From 40a7584c2fd2d510ee31bbde72e09e1fab37df2a Mon Sep 17 00:00:00 2001
From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Date: Mon, 25 Jul 2022 14:42:25 +1000
Subject: [PATCH] Gas tank internals alerts (#9567)
---
Content.Server/Alert/Click/ToggleInternals.cs | 53 ++++
.../Atmos/Components/BreathToolComponent.cs | 26 +-
.../Atmos/Components/GasTankComponent.cs | 250 +-----------------
.../AtmosphereSystem.BreathTool.cs | 30 +++
.../Atmos/EntitySystems/AtmosphereSystem.cs | 3 +
.../Atmos/EntitySystems/GasTankSystem.cs | 239 ++++++++++++++++-
.../Body/Components/InternalsComponent.cs | 64 +----
.../Body/Systems/InternalsSystem.cs | 155 ++++++++++-
Content.Server/Body/Systems/LungSystem.cs | 5 +-
.../SmokeSolutionAreaEffectComponent.cs | 2 +-
Content.Server/Clothing/MaskSystem.cs | 12 +-
.../Movement/Systems/JetpackSystem.cs | 7 +-
.../PneumaticCannon/PneumaticCannonSystem.cs | 16 +-
Content.Shared/Alert/AlertCategory.cs | 1 +
Content.Shared/Alert/AlertType.cs | 1 +
.../Movement/Systems/SharedJetpackSystem.cs | 6 +-
.../en-US/actions/actions/internals.ftl | 5 +-
Resources/Prototypes/Alerts/alerts.yml | 14 +
.../Entities/Objects/Tools/gas_tanks.yml | 9 +-
.../Textures/Interface/Actions/internal0.png | Bin 850 -> 0 bytes
.../Textures/Interface/Actions/internal1.png | Bin 861 -> 0 bytes
.../Alerts/internals.rsi/internal0.png | Bin 0 -> 1511 bytes
.../Alerts/internals.rsi/internal1.png | Bin 0 -> 918 bytes
.../Alerts/internals.rsi/internal2.png | Bin 0 -> 896 bytes
.../Interface/Alerts/internals.rsi/meta.json | 26 ++
25 files changed, 570 insertions(+), 354 deletions(-)
create mode 100644 Content.Server/Alert/Click/ToggleInternals.cs
create mode 100644 Content.Server/Atmos/EntitySystems/AtmosphereSystem.BreathTool.cs
delete mode 100644 Resources/Textures/Interface/Actions/internal0.png
delete mode 100644 Resources/Textures/Interface/Actions/internal1.png
create mode 100644 Resources/Textures/Interface/Alerts/internals.rsi/internal0.png
create mode 100644 Resources/Textures/Interface/Alerts/internals.rsi/internal1.png
create mode 100644 Resources/Textures/Interface/Alerts/internals.rsi/internal2.png
create mode 100644 Resources/Textures/Interface/Alerts/internals.rsi/meta.json
diff --git a/Content.Server/Alert/Click/ToggleInternals.cs b/Content.Server/Alert/Click/ToggleInternals.cs
new file mode 100644
index 0000000000..3fa7e525f4
--- /dev/null
+++ b/Content.Server/Alert/Click/ToggleInternals.cs
@@ -0,0 +1,53 @@
+using Content.Server.Atmos.EntitySystems;
+using Content.Server.Body.Components;
+using Content.Server.Body.Systems;
+using Content.Server.Popups;
+using Content.Server.Shuttles.Systems;
+using Content.Shared.Alert;
+using Content.Shared.Shuttles.Components;
+using JetBrains.Annotations;
+using Robust.Shared.Player;
+
+namespace Content.Server.Alert.Click;
+
+///
+/// Attempts to toggle the internals for a particular entity
+///
+[UsedImplicitly]
+[DataDefinition]
+public sealed class ToggleInternals : IAlertClick
+{
+ public void AlertClicked(EntityUid player)
+ {
+ var entManager = IoCManager.Resolve();
+
+ if (!entManager.TryGetComponent(player, out var internals)) return;
+
+ var popups = entManager.EntitySysManager.GetEntitySystem();
+ var internalsSystem = entManager.EntitySysManager.GetEntitySystem();
+
+ // Toggle off if they're on
+ if (internalsSystem.AreInternalsWorking(internals))
+ {
+ internalsSystem.DisconnectTank(internals);
+ return;
+ }
+
+ // If they're not on then check if we have a mask to use
+ if (internals.BreathToolEntity == null)
+ {
+ popups.PopupEntity(Loc.GetString("internals-no-breath-tool"), player, Filter.Entities(player));
+ return;
+ }
+
+ var tank = internalsSystem.FindBestGasTank(internals);
+
+ if (tank == null)
+ {
+ popups.PopupEntity(Loc.GetString("internals-no-tank"), player, Filter.Entities(player));
+ return;
+ }
+
+ entManager.EntitySysManager.GetEntitySystem().ConnectToInternals(tank);
+ }
+}
diff --git a/Content.Server/Atmos/Components/BreathToolComponent.cs b/Content.Server/Atmos/Components/BreathToolComponent.cs
index 7f500ae5ae..edc494bb36 100644
--- a/Content.Server/Atmos/Components/BreathToolComponent.cs
+++ b/Content.Server/Atmos/Components/BreathToolComponent.cs
@@ -1,5 +1,4 @@
-using Content.Server.Body.Components;
-using Content.Shared.Inventory;
+using Content.Shared.Inventory;
namespace Content.Server.Atmos.Components
{
@@ -10,33 +9,12 @@ namespace Content.Server.Atmos.Components
[ComponentProtoName("BreathMask")]
public sealed class BreathToolComponent : Component
{
- [Dependency] private readonly IEntityManager _entities = default!;
-
///
/// Tool is functional only in allowed slots
///
[DataField("allowedSlots")]
public SlotFlags AllowedSlots = SlotFlags.MASK;
public bool IsFunctional;
- public EntityUid ConnectedInternalsEntity;
-
- protected override void Shutdown()
- {
- base.Shutdown();
- DisconnectInternals();
- }
-
- public void DisconnectInternals()
- {
- var old = ConnectedInternalsEntity;
- ConnectedInternalsEntity = default;
-
- if (old != default && _entities.TryGetComponent(old, out var internalsComponent))
- {
- internalsComponent.DisconnectBreathTool();
- }
-
- IsFunctional = false;
- }
+ public EntityUid? ConnectedInternalsEntity;
}
}
diff --git a/Content.Server/Atmos/Components/GasTankComponent.cs b/Content.Server/Atmos/Components/GasTankComponent.cs
index ddacdfd0b4..c4ec400a47 100644
--- a/Content.Server/Atmos/Components/GasTankComponent.cs
+++ b/Content.Server/Atmos/Components/GasTankComponent.cs
@@ -1,49 +1,35 @@
-using Content.Server.Atmos.EntitySystems;
-using Content.Server.Body.Components;
-using Content.Server.Explosion.EntitySystems;
-using Content.Server.UserInterface;
-using Content.Shared.Actions;
using Content.Shared.Actions.ActionTypes;
using Content.Shared.Atmos;
-using Content.Shared.Atmos.Components;
-using Content.Shared.Audio;
using Content.Shared.Sound;
-using Robust.Server.GameObjects;
using Robust.Shared.Audio;
-using Robust.Shared.Containers;
-using Robust.Shared.Player;
namespace Content.Server.Atmos.Components
{
[RegisterComponent]
-#pragma warning disable 618
public sealed class GasTankComponent : Component, IGasMixtureHolder
-#pragma warning restore 618
{
- [Dependency] private readonly IEntityManager _entMan = default!;
-
- private const float MaxExplosionRange = 14f;
+ public const float MaxExplosionRange = 14f;
private const float DefaultOutputPressure = Atmospherics.OneAtmosphere;
- private int _integrity = 3;
+ public int Integrity = 3;
- [ViewVariables] public BoundUserInterface? UserInterface;
+ [ViewVariables(VVAccess.ReadWrite), DataField("ruptureSound")]
+ public SoundSpecifier RuptureSound = new SoundPathSpecifier("/Audio/Effects/spray.ogg");
- [ViewVariables(VVAccess.ReadWrite), DataField("ruptureSound")] private SoundSpecifier _ruptureSound = new SoundPathSpecifier("/Audio/Effects/spray.ogg");
-
- [ViewVariables(VVAccess.ReadWrite), DataField("connectSound")] private SoundSpecifier? _connectSound =
+ [ViewVariables(VVAccess.ReadWrite), DataField("connectSound")]
+ public SoundSpecifier? ConnectSound =
new SoundPathSpecifier("/Audio/Effects/internals.ogg")
{
- Params = AudioParams.Default.WithVolume(10f),
+ Params = AudioParams.Default.WithVolume(5f),
};
- [ViewVariables(VVAccess.ReadWrite), DataField("disconnectSound")] private SoundSpecifier? _disconnectSound;
+ [ViewVariables(VVAccess.ReadWrite), DataField("disconnectSound")]
+ public SoundSpecifier? DisconnectSound;
// Cancel toggles sounds if we re-toggle again.
- private IPlayingAudioStream? _connectStream;
- private IPlayingAudioStream? _disconnectStream;
-
+ public IPlayingAudioStream? ConnectStream;
+ public IPlayingAudioStream? DisconnectStream;
[DataField("air")] [ViewVariables] public GasMixture Air { get; set; } = new();
@@ -52,18 +38,13 @@ namespace Content.Server.Atmos.Components
///
[DataField("outputPressure")]
[ViewVariables]
- public float OutputPressure { get; private set; } = DefaultOutputPressure;
+ public float OutputPressure { get; set; } = DefaultOutputPressure;
///
/// Tank is connected to internals.
///
[ViewVariables] public bool IsConnected { get; set; }
- ///
- /// Represents that tank is functional and can be connected to internals.
- ///
- public bool IsFunctional => GetInternalsComponent() != null;
-
///
/// Pressure at which tanks start leaking.
///
@@ -90,212 +71,5 @@ namespace Content.Server.Atmos.Components
[DataField("toggleAction", required: true)]
public InstantAction ToggleAction = new();
-
- protected override void Initialize()
- {
- base.Initialize();
- UserInterface = Owner.GetUIOrNull(SharedGasTankUiKey.Key);
- if (UserInterface != null)
- {
- UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
- }
- }
-
- protected override void Shutdown()
- {
- base.Shutdown();
- DisconnectFromInternals();
- }
-
- public GasMixture? RemoveAir(float amount)
- {
- var gas = Air?.Remove(amount);
- CheckStatus();
- return gas;
- }
-
- public GasMixture RemoveAirVolume(float volume)
- {
- if (Air == null)
- return new GasMixture(volume);
-
- var tankPressure = Air.Pressure;
- if (tankPressure < OutputPressure)
- {
- OutputPressure = tankPressure;
- UpdateUserInterface();
- }
-
- var molesNeeded = OutputPressure * volume / (Atmospherics.R * Air.Temperature);
-
- var air = RemoveAir(molesNeeded);
-
- if (air != null)
- air.Volume = volume;
- else
- return new GasMixture(volume);
-
- return air;
- }
-
- public void ConnectToInternals()
- {
- if (IsConnected || !IsFunctional) return;
- var internals = GetInternalsComponent();
- if (internals == null) return;
- IsConnected = internals.TryConnectTank(Owner);
- EntitySystem.Get().SetToggled(ToggleAction, IsConnected);
-
- // Couldn't toggle!
- if (!IsConnected) return;
-
- _connectStream?.Stop();
-
- if (_connectSound != null)
- _connectStream = SoundSystem.Play(_connectSound.GetSound(), Filter.Pvs(Owner, entityManager: _entMan), Owner, _connectSound.Params);
-
- UpdateUserInterface();
- }
-
- public void DisconnectFromInternals(EntityUid? owner = null)
- {
- if (!IsConnected) return;
- IsConnected = false;
- EntitySystem.Get().SetToggled(ToggleAction, false);
-
- GetInternalsComponent(owner)?.DisconnectTank();
- _disconnectStream?.Stop();
-
- if (_disconnectSound != null)
- _disconnectStream = SoundSystem.Play(_disconnectSound.GetSound(), Filter.Pvs(Owner, entityManager: _entMan), Owner, _disconnectSound.Params);
-
- UpdateUserInterface();
- }
-
- public void UpdateUserInterface(bool initialUpdate = false)
- {
- var internals = GetInternalsComponent();
- UserInterface?.SetState(
- new GasTankBoundUserInterfaceState
- {
- TankPressure = Air?.Pressure ?? 0,
- OutputPressure = initialUpdate ? OutputPressure : null,
- InternalsConnected = IsConnected,
- CanConnectInternals = IsFunctional && internals != null
- });
- }
-
- private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message)
- {
- switch (message.Message)
- {
- case GasTankSetPressureMessage msg:
- OutputPressure = msg.Pressure;
- break;
- case GasTankToggleInternalsMessage _:
- ToggleInternals();
- break;
- }
- }
-
- internal void ToggleInternals()
- {
- if (IsConnected)
- {
- DisconnectFromInternals();
- return;
- }
-
- ConnectToInternals();
- }
-
- private InternalsComponent? GetInternalsComponent(EntityUid? owner = null)
- {
- if (_entMan.Deleted(Owner)) return null;
- if (owner != null) return _entMan.GetComponentOrNull(owner.Value);
- return Owner.TryGetContainer(out var container)
- ? _entMan.GetComponentOrNull(container.Owner)
- : null;
- }
-
- public void AssumeAir(GasMixture giver)
- {
- var atmos = EntitySystem.Get();
- atmos.Merge(Air, giver);
- CheckStatus(atmos);
- }
-
- public void CheckStatus(AtmosphereSystem? atmosphereSystem=null)
- {
- if (Air == null)
- return;
-
- atmosphereSystem ??= EntitySystem.Get();
-
- var pressure = Air.Pressure;
-
- if (pressure > TankFragmentPressure)
- {
- // Give the gas a chance to build up more pressure.
- for (var i = 0; i < 3; i++)
- {
- atmosphereSystem.React(Air, this);
- }
-
- pressure = Air.Pressure;
- var range = (pressure - TankFragmentPressure) / TankFragmentScale;
-
- // Let's cap the explosion, yeah?
- // !1984
- if (range > MaxExplosionRange)
- {
- range = MaxExplosionRange;
- }
-
- EntitySystem.Get().TriggerExplosive(Owner, radius: range);
-
- return;
- }
-
- if (pressure > TankRupturePressure)
- {
- if (_integrity <= 0)
- {
- var environment = atmosphereSystem.GetContainingMixture(Owner, false, true);
- if(environment != null)
- atmosphereSystem.Merge(environment, Air);
-
- SoundSystem.Play(_ruptureSound.GetSound(), Filter.Pvs(Owner), _entMan.GetComponent(Owner).Coordinates, AudioHelpers.WithVariation(0.125f));
-
- _entMan.QueueDeleteEntity(Owner);
- return;
- }
-
- _integrity--;
- return;
- }
-
- if (pressure > TankLeakPressure)
- {
- if (_integrity <= 0)
- {
- var environment = atmosphereSystem.GetContainingMixture(Owner, false, true);
- if (environment == null)
- return;
-
- var leakedGas = Air.RemoveRatio(0.25f);
- atmosphereSystem.Merge(environment, leakedGas);
- }
- else
- {
- _integrity--;
- }
-
- return;
- }
-
- if (_integrity < 3)
- _integrity++;
- }
}
}
diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.BreathTool.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.BreathTool.cs
new file mode 100644
index 0000000000..22270cb541
--- /dev/null
+++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.BreathTool.cs
@@ -0,0 +1,30 @@
+using Content.Server.Atmos.Components;
+using Content.Server.Body.Components;
+
+namespace Content.Server.Atmos.EntitySystems;
+
+public sealed partial class AtmosphereSystem
+{
+ private void InitializeBreathTool()
+ {
+ SubscribeLocalEvent(OnBreathToolShutdown);
+ }
+
+ private void OnBreathToolShutdown(EntityUid uid, BreathToolComponent component, ComponentShutdown args)
+ {
+ DisconnectInternals(component);
+ }
+
+ public void DisconnectInternals(BreathToolComponent component)
+ {
+ var old = component.ConnectedInternalsEntity;
+ component.ConnectedInternalsEntity = null;
+
+ if (TryComp(old, out var internalsComponent))
+ {
+ _internals.DisconnectBreathTool(internalsComponent);
+ }
+
+ component.IsFunctional = false;
+ }
+}
diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs
index 5089090e12..901154ba22 100644
--- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs
+++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs
@@ -1,5 +1,6 @@
using Content.Server.Administration.Logs;
using Content.Server.Atmos.Components;
+using Content.Server.Body.Systems;
using Content.Server.NodeContainer.EntitySystems;
using Content.Shared.Atmos.EntitySystems;
using Content.Shared.Maps;
@@ -21,6 +22,7 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!;
[Dependency] private readonly IAdminLogManager _adminLog = default!;
+ [Dependency] private readonly InternalsSystem _internals = default!;
[Dependency] private readonly SharedContainerSystem _containers = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly GasTileOverlaySystem _gasTileOverlaySystem = default!;
@@ -36,6 +38,7 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
UpdatesAfter.Add(typeof(NodeGroupSystem));
+ InitializeBreathTool();
InitializeGases();
InitializeCommands();
InitializeCVars();
diff --git a/Content.Server/Atmos/EntitySystems/GasTankSystem.cs b/Content.Server/Atmos/EntitySystems/GasTankSystem.cs
index b8412c14dd..309a508d97 100644
--- a/Content.Server/Atmos/EntitySystems/GasTankSystem.cs
+++ b/Content.Server/Atmos/EntitySystems/GasTankSystem.cs
@@ -1,10 +1,22 @@
using Content.Server.Atmos.Components;
+using Content.Server.Body.Components;
+using Content.Server.Body.Systems;
+using Content.Server.Explosion.EntitySystems;
using Content.Server.UserInterface;
using Content.Shared.Actions;
+using Content.Shared.Atmos;
+using Content.Shared.Atmos.Components;
+using Content.Shared.Audio;
using Content.Shared.Interaction.Events;
using Content.Shared.Toggleable;
using Content.Shared.Examine;
+using Content.Shared.Inventory;
using JetBrains.Annotations;
+using Robust.Server.GameObjects;
+using Robust.Server.Player;
+using Robust.Shared.Audio;
+using Robust.Shared.Containers;
+using Robust.Shared.Player;
namespace Content.Server.Atmos.EntitySystems
{
@@ -12,6 +24,11 @@ namespace Content.Server.Atmos.EntitySystems
public sealed class GasTankSystem : EntitySystem
{
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
+ [Dependency] private readonly ExplosionSystem _explosions = default!;
+ [Dependency] private readonly InternalsSystem _internals = default!;
+ [Dependency] private readonly SharedContainerSystem _containers = default!;
+ [Dependency] private readonly SharedActionsSystem _actions = default!;
+ [Dependency] private readonly UserInterfaceSystem _ui = default!;
private const float TimerDelay = 0.5f;
private float _timer = 0f;
@@ -19,22 +36,57 @@ namespace Content.Server.Atmos.EntitySystems
public override void Initialize()
{
base.Initialize();
+ SubscribeLocalEvent(OnGasShutdown);
SubscribeLocalEvent(BeforeUiOpen);
SubscribeLocalEvent(OnGetActions);
SubscribeLocalEvent(OnExamined);
SubscribeLocalEvent(OnActionToggle);
SubscribeLocalEvent(OnDropped);
+
+ SubscribeLocalEvent(OnGasTankSetPressure);
+ SubscribeLocalEvent(OnGasTankToggleInternals);
+ }
+
+ private void OnGasShutdown(EntityUid uid, GasTankComponent component, ComponentShutdown args)
+ {
+ DisconnectFromInternals(component);
+ }
+
+ private void OnGasTankToggleInternals(EntityUid uid, GasTankComponent component, GasTankToggleInternalsMessage args)
+ {
+ if (args.Session is not IPlayerSession playerSession ||
+ playerSession.AttachedEntity is not {} player) return;
+
+ ToggleInternals(component);
+ }
+
+ private void OnGasTankSetPressure(EntityUid uid, GasTankComponent component, GasTankSetPressureMessage args)
+ {
+ component.OutputPressure = args.Pressure;
+ }
+
+ public void UpdateUserInterface(GasTankComponent component, bool initialUpdate = false)
+ {
+ var internals = GetInternalsComponent(component);
+ _ui.GetUiOrNull(component.Owner, SharedGasTankUiKey.Key)?.SetState(
+ new GasTankBoundUserInterfaceState
+ {
+ TankPressure = component.Air?.Pressure ?? 0,
+ OutputPressure = initialUpdate ? component.OutputPressure : null,
+ InternalsConnected = component.IsConnected,
+ CanConnectInternals = IsFunctional(component) && internals != null
+ });
}
private void BeforeUiOpen(EntityUid uid, GasTankComponent component, BeforeActivatableUIOpenEvent args)
{
// Only initial update includes output pressure information, to avoid overwriting client-input as the updates come in.
- component.UpdateUserInterface(true);
+ UpdateUserInterface(component, true);
}
private void OnDropped(EntityUid uid, GasTankComponent component, DroppedEvent args)
{
- component.DisconnectFromInternals(args.User);
+ DisconnectFromInternals(component, args.User);
}
private void OnGetActions(EntityUid uid, GasTankComponent component, GetItemActionsEvent args)
@@ -55,8 +107,7 @@ namespace Content.Server.Atmos.EntitySystems
if (args.Handled)
return;
- component.ToggleInternals();
-
+ ToggleInternals(component);
args.Handled = true;
}
@@ -72,13 +123,185 @@ namespace Content.Server.Atmos.EntitySystems
foreach (var gasTank in EntityManager.EntityQuery())
{
_atmosphereSystem.React(gasTank.Air, gasTank);
- gasTank.CheckStatus(_atmosphereSystem);
-
- if (gasTank.UserInterface != null && gasTank.UserInterface.SubscribedSessions.Count > 0)
+ CheckStatus(gasTank);
+ if (_ui.IsUiOpen(gasTank.Owner, SharedGasTankUiKey.Key))
{
- gasTank.UpdateUserInterface();
+ UpdateUserInterface(gasTank);
}
}
}
+
+ private void ToggleInternals(GasTankComponent component)
+ {
+ if (component.IsConnected)
+ {
+ DisconnectFromInternals(component);
+ }
+ else
+ {
+ ConnectToInternals(component);
+ }
+ }
+
+ public GasMixture? RemoveAir(GasTankComponent component, float amount)
+ {
+ var gas = component.Air?.Remove(amount);
+ CheckStatus(component);
+ return gas;
+ }
+
+ public GasMixture RemoveAirVolume(GasTankComponent component, float volume)
+ {
+ if (component.Air == null)
+ return new GasMixture(volume);
+
+ var tankPressure = component.Air.Pressure;
+ if (tankPressure < component.OutputPressure)
+ {
+ component.OutputPressure = tankPressure;
+ UpdateUserInterface(component);
+ }
+
+ var molesNeeded = component.OutputPressure * volume / (Atmospherics.R * component.Air.Temperature);
+
+ var air = RemoveAir(component, molesNeeded);
+
+ if (air != null)
+ air.Volume = volume;
+ else
+ return new GasMixture(volume);
+
+ return air;
+ }
+
+ public bool CanConnectToInternals(GasTankComponent component)
+ {
+ return !component.IsConnected && IsFunctional(component);
+ }
+
+ public void ConnectToInternals(GasTankComponent component)
+ {
+ if (!CanConnectToInternals(component)) return;
+ var internals = GetInternalsComponent(component);
+ if (internals == null) return;
+ component.IsConnected = _internals.TryConnectTank(internals, component.Owner);
+ _actions.SetToggled(component.ToggleAction, component.IsConnected);
+
+ // Couldn't toggle!
+ if (!component.IsConnected) return;
+
+ component.ConnectStream?.Stop();
+
+ if (component.ConnectSound != null)
+ component.ConnectStream = SoundSystem.Play(component.ConnectSound.GetSound(), Filter.Pvs(component.Owner, entityManager: EntityManager), component.Owner, component.ConnectSound.Params);
+
+ UpdateUserInterface(component);
+ }
+
+ public void DisconnectFromInternals(GasTankComponent component, EntityUid? owner = null)
+ {
+ if (!component.IsConnected) return;
+ component.IsConnected = false;
+ _actions.SetToggled(component.ToggleAction, false);
+
+ _internals.DisconnectTank(GetInternalsComponent(component, owner));
+ component.DisconnectStream?.Stop();
+
+ if (component.DisconnectSound != null)
+ component.DisconnectStream = SoundSystem.Play(component.DisconnectSound.GetSound(), Filter.Pvs(component.Owner, entityManager: EntityManager), component.Owner, component.DisconnectSound.Params);
+
+ UpdateUserInterface(component);
+ }
+
+ private InternalsComponent? GetInternalsComponent(GasTankComponent component, EntityUid? owner = null)
+ {
+ if (Deleted(component.Owner)) return null;
+ if (owner != null) return CompOrNull(owner.Value);
+ return _containers.TryGetContainingContainer(component.Owner, out var container)
+ ? CompOrNull(container.Owner)
+ : null;
+ }
+
+ public void AssumeAir(GasTankComponent component, GasMixture giver)
+ {
+ _atmosphereSystem.Merge(component.Air, giver);
+ CheckStatus(component);
+ }
+
+ public void CheckStatus(GasTankComponent component)
+ {
+ if (component.Air == null)
+ return;
+
+ var pressure = component.Air.Pressure;
+
+ if (pressure > component.TankFragmentPressure)
+ {
+ // Give the gas a chance to build up more pressure.
+ for (var i = 0; i < 3; i++)
+ {
+ _atmosphereSystem.React(component.Air, component);
+ }
+
+ pressure = component.Air.Pressure;
+ var range = (pressure - component.TankFragmentPressure) / component.TankFragmentScale;
+
+ // Let's cap the explosion, yeah?
+ // !1984
+ if (range > GasTankComponent.MaxExplosionRange)
+ {
+ range = GasTankComponent.MaxExplosionRange;
+ }
+
+ _explosions.TriggerExplosive(component.Owner, radius: range);
+
+ return;
+ }
+
+ if (pressure > component.TankRupturePressure)
+ {
+ if (component.Integrity <= 0)
+ {
+ var environment = _atmosphereSystem.GetContainingMixture(component.Owner, false, true);
+ if(environment != null)
+ _atmosphereSystem.Merge(environment, component.Air);
+
+ SoundSystem.Play(component.RuptureSound.GetSound(), Filter.Pvs(component.Owner), Transform(component.Owner).Coordinates, AudioHelpers.WithVariation(0.125f));
+
+ QueueDel(component.Owner);
+ return;
+ }
+
+ component.Integrity--;
+ return;
+ }
+
+ if (pressure > component.TankLeakPressure)
+ {
+ if (component.Integrity <= 0)
+ {
+ var environment = _atmosphereSystem.GetContainingMixture(component.Owner, false, true);
+ if (environment == null)
+ return;
+
+ var leakedGas = component.Air.RemoveRatio(0.25f);
+ _atmosphereSystem.Merge(environment, leakedGas);
+ }
+ else
+ {
+ component.Integrity--;
+ }
+
+ return;
+ }
+
+ if (component.Integrity < 3)
+ component.Integrity++;
+ }
+
+ private bool IsFunctional(GasTankComponent component)
+ {
+ return GetInternalsComponent(component) != null;
+ }
}
}
diff --git a/Content.Server/Body/Components/InternalsComponent.cs b/Content.Server/Body/Components/InternalsComponent.cs
index b1c50765e1..faef3d32b9 100644
--- a/Content.Server/Body/Components/InternalsComponent.cs
+++ b/Content.Server/Body/Components/InternalsComponent.cs
@@ -1,68 +1,12 @@
-using Content.Server.Atmos.Components;
-
-namespace Content.Server.Body.Components
+namespace Content.Server.Body.Components
{
+ ///
+ /// Handles hooking up a mask (breathing tool) / gas tank together and allowing the Owner to breathe through it.
+ ///
[RegisterComponent]
public sealed class InternalsComponent : Component
{
- [Dependency] private readonly IEntityManager _entMan = default!;
-
[ViewVariables] public EntityUid? GasTankEntity { get; set; }
[ViewVariables] public EntityUid? BreathToolEntity { get; set; }
-
- public void DisconnectBreathTool()
- {
- var old = BreathToolEntity;
- BreathToolEntity = null;
-
- if (_entMan.TryGetComponent(old, out BreathToolComponent? breathTool) )
- {
- breathTool.DisconnectInternals();
- DisconnectTank();
- }
- }
-
- public void ConnectBreathTool(EntityUid toolEntity)
- {
- if (_entMan.TryGetComponent(BreathToolEntity, out BreathToolComponent? tool))
- {
- tool.DisconnectInternals();
- }
-
- BreathToolEntity = toolEntity;
- }
-
- public void DisconnectTank()
- {
- if (_entMan.TryGetComponent(GasTankEntity, out GasTankComponent? tank))
- {
- tank.DisconnectFromInternals(Owner);
- }
-
- GasTankEntity = null;
- }
-
- public bool TryConnectTank(EntityUid tankEntity)
- {
- if (BreathToolEntity == null)
- return false;
-
- if (_entMan.TryGetComponent(GasTankEntity, out GasTankComponent? tank))
- {
- tank.DisconnectFromInternals(Owner);
- }
-
- GasTankEntity = tankEntity;
- return true;
- }
-
- public bool AreInternalsWorking()
- {
- return _entMan.TryGetComponent(BreathToolEntity, out BreathToolComponent? breathTool) &&
- breathTool.IsFunctional &&
- _entMan.TryGetComponent(GasTankEntity, out GasTankComponent? gasTank) &&
- gasTank.Air != null;
- }
-
}
}
diff --git a/Content.Server/Body/Systems/InternalsSystem.cs b/Content.Server/Body/Systems/InternalsSystem.cs
index 5a5076eb92..86825b4500 100644
--- a/Content.Server/Body/Systems/InternalsSystem.cs
+++ b/Content.Server/Body/Systems/InternalsSystem.cs
@@ -1,24 +1,175 @@
using Content.Server.Atmos.Components;
+using Content.Server.Atmos.EntitySystems;
using Content.Server.Body.Components;
+using Content.Server.Hands.Systems;
+using Content.Shared.Alert;
using Content.Shared.Atmos;
+using Content.Shared.Inventory;
+using Robust.Shared.Containers;
+using Robust.Shared.Prototypes;
namespace Content.Server.Body.Systems;
public sealed class InternalsSystem : EntitySystem
{
+ [Dependency] private readonly IPrototypeManager _protoManager = default!;
+ [Dependency] private readonly AlertsSystem _alerts = default!;
+ [Dependency] private readonly AtmosphereSystem _atmos = default!;
+ [Dependency] private readonly GasTankSystem _gasTank = default!;
+ [Dependency] private readonly HandsSystem _hands = default!;
+ [Dependency] private readonly InventorySystem _inventory = default!;
+
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent(OnInhaleLocation);
+ SubscribeLocalEvent(OnInternalsStartup);
+ SubscribeLocalEvent(OnInternalsShutdown);
+ }
+
+ private void OnInternalsStartup(EntityUid uid, InternalsComponent component, ComponentStartup args)
+ {
+ _alerts.ShowAlert(component.Owner, AlertType.Internals, GetSeverity(component));
+ }
+
+ private void OnInternalsShutdown(EntityUid uid, InternalsComponent component, ComponentShutdown args)
+ {
+ _alerts.ClearAlert(uid, AlertType.Internals);
}
private void OnInhaleLocation(EntityUid uid, InternalsComponent component, InhaleLocationEvent args)
{
- if (component.AreInternalsWorking())
+ if (AreInternalsWorking(component))
{
var gasTank = Comp(component.GasTankEntity!.Value);
- args.Gas = gasTank.RemoveAirVolume(Atmospherics.BreathVolume);
+ args.Gas = _gasTank.RemoveAirVolume(gasTank, Atmospherics.BreathVolume);
+ // TODO: Should listen to gas tank updates instead I guess?
+ _alerts.ShowAlert(component.Owner, AlertType.Internals, GetSeverity(component));
}
}
+ public void DisconnectBreathTool(InternalsComponent component)
+ {
+ var old = component.BreathToolEntity;
+ component.BreathToolEntity = null;
+
+ if (TryComp(old, out BreathToolComponent? breathTool) )
+ {
+ _atmos.DisconnectInternals(breathTool);
+ DisconnectTank(component);
+ }
+
+ _alerts.ShowAlert(component.Owner, AlertType.Internals, GetSeverity(component));
+ }
+
+ public void ConnectBreathTool(InternalsComponent component, EntityUid toolEntity)
+ {
+ if (TryComp(component.BreathToolEntity, out BreathToolComponent? tool))
+ {
+ _atmos.DisconnectInternals(tool);
+ }
+
+ component.BreathToolEntity = toolEntity;
+ _alerts.ShowAlert(component.Owner, AlertType.Internals, GetSeverity(component));
+ }
+
+ public void DisconnectTank(InternalsComponent? component)
+ {
+ if (component == null) return;
+
+ if (TryComp(component.GasTankEntity, out GasTankComponent? tank))
+ {
+ _gasTank.DisconnectFromInternals(tank, component.Owner);
+ }
+
+ component.GasTankEntity = null;
+ _alerts.ShowAlert(component.Owner, AlertType.Internals, GetSeverity(component));
+ }
+
+ public bool TryConnectTank(InternalsComponent component, EntityUid tankEntity)
+ {
+ if (component.BreathToolEntity == null)
+ return false;
+
+ if (TryComp(component.GasTankEntity, out GasTankComponent? tank))
+ {
+ _gasTank.DisconnectFromInternals(tank, component.Owner);
+ }
+
+ component.GasTankEntity = tankEntity;
+ _alerts.ShowAlert(component.Owner, AlertType.Internals, GetSeverity(component));
+ return true;
+ }
+
+ public bool AreInternalsWorking(InternalsComponent component)
+ {
+ return TryComp(component.BreathToolEntity, out BreathToolComponent? breathTool) &&
+ breathTool.IsFunctional &&
+ TryComp(component.GasTankEntity, out GasTankComponent? gasTank) &&
+ gasTank.Air != null;
+ }
+
+ private short GetSeverity(InternalsComponent component)
+ {
+ if (component.BreathToolEntity == null || !AreInternalsWorking(component)) return 2;
+
+ if (TryComp(component.GasTankEntity, out var gasTank) && gasTank.Air.Volume < Atmospherics.BreathVolume)
+ return 0;
+
+ return 1;
+ }
+
+ public GasTankComponent? FindBestGasTank(InternalsComponent component)
+ {
+ // Prioritise
+ // 1. exo-slot tanks
+ // 2. in-hand tanks
+ // 3. pocket tanks
+ InventoryComponent? inventory = null;
+ ContainerManagerComponent? containerManager = null;
+
+ if (_inventory.TryGetSlotEntity(component.Owner, "suitstorage", out var entity, inventory, containerManager) &&
+ TryComp(entity, out var gasTank) &&
+ _gasTank.CanConnectToInternals(gasTank))
+ {
+ return gasTank;
+ }
+
+ var tanks = new List();
+
+ foreach (var hand in _hands.EnumerateHands(component.Owner))
+ {
+ if (TryComp(hand.HeldEntity, out gasTank) && _gasTank.CanConnectToInternals(gasTank))
+ {
+ tanks.Add(gasTank);
+ }
+ }
+
+ if (tanks.Count > 0)
+ {
+ tanks.Sort((x, y) => y.Air.TotalMoles.CompareTo(x.Air.TotalMoles));
+ return tanks[0];
+ }
+
+ if (Resolve(component.Owner, ref inventory, false))
+ {
+ var enumerator = new InventorySystem.ContainerSlotEnumerator(component.Owner, inventory.TemplateId, _protoManager, _inventory, SlotFlags.POCKET);
+
+ while (enumerator.MoveNext(out var container))
+ {
+ if (TryComp(container.ContainedEntity, out gasTank) && _gasTank.CanConnectToInternals(gasTank))
+ {
+ tanks.Add(gasTank);
+ }
+ }
+
+ if (tanks.Count > 0)
+ {
+ tanks.Sort((x, y) => y.Air.TotalMoles.CompareTo(x.Air.TotalMoles));
+ return tanks[0];
+ }
+ }
+
+ return null;
+ }
}
diff --git a/Content.Server/Body/Systems/LungSystem.cs b/Content.Server/Body/Systems/LungSystem.cs
index 0e995c78d1..042c90857b 100644
--- a/Content.Server/Body/Systems/LungSystem.cs
+++ b/Content.Server/Body/Systems/LungSystem.cs
@@ -9,6 +9,7 @@ namespace Content.Server.Body.Systems;
public sealed class LungSystem : EntitySystem
{
+ [Dependency] private readonly InternalsSystem _internals = default!;
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
@@ -24,7 +25,7 @@ public sealed class LungSystem : EntitySystem
private void OnGotUnequipped(EntityUid uid, BreathToolComponent component, GotUnequippedEvent args)
{
- component.DisconnectInternals();
+ _atmosphereSystem.DisconnectInternals(component);
}
private void OnGotEquipped(EntityUid uid, BreathToolComponent component, GotEquippedEvent args)
@@ -36,7 +37,7 @@ public sealed class LungSystem : EntitySystem
if (TryComp(args.Equipee, out InternalsComponent? internals))
{
component.ConnectedInternalsEntity = args.Equipee;
- internals.ConnectBreathTool(uid);
+ _internals.ConnectBreathTool(internals, uid);
}
}
diff --git a/Content.Server/Chemistry/Components/SmokeSolutionAreaEffectComponent.cs b/Content.Server/Chemistry/Components/SmokeSolutionAreaEffectComponent.cs
index 0771ba447c..631de97ce5 100644
--- a/Content.Server/Chemistry/Components/SmokeSolutionAreaEffectComponent.cs
+++ b/Content.Server/Chemistry/Components/SmokeSolutionAreaEffectComponent.cs
@@ -34,7 +34,7 @@ namespace Content.Server.Chemistry.Components
return;
if (_entMan.TryGetComponent(entity, out InternalsComponent? internals) &&
- internals.AreInternalsWorking())
+ IoCManager.Resolve().GetEntitySystem().AreInternalsWorking(internals))
return;
var chemistry = EntitySystem.Get();
diff --git a/Content.Server/Clothing/MaskSystem.cs b/Content.Server/Clothing/MaskSystem.cs
index 202d905e50..b03787288e 100644
--- a/Content.Server/Clothing/MaskSystem.cs
+++ b/Content.Server/Clothing/MaskSystem.cs
@@ -5,7 +5,9 @@ using Content.Shared.Inventory.Events;
using Content.Shared.Item;
using Content.Server.Actions;
using Content.Server.Atmos.Components;
+using Content.Server.Atmos.EntitySystems;
using Content.Server.Body.Components;
+using Content.Server.Body.Systems;
using Content.Server.Clothing.Components;
using Content.Server.Disease.Components;
using Content.Server.IdentityManagement;
@@ -18,9 +20,11 @@ namespace Content.Server.Clothing
{
public sealed class MaskSystem : EntitySystem
{
- [Dependency] private readonly PopupSystem _popupSystem = default!;
- [Dependency] private readonly InventorySystem _inventorySystem = default!;
[Dependency] private readonly ActionsSystem _actionSystem = default!;
+ [Dependency] private readonly AtmosphereSystem _atmos = default!;
+ [Dependency] private readonly InternalsSystem _internals = default!;
+ [Dependency] private readonly InventorySystem _inventorySystem = default!;
+ [Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly IdentitySystem _identity = default!;
public override void Initialize()
@@ -100,7 +104,7 @@ namespace Content.Server.Clothing
if (mask.IsToggled)
{
- breathTool.DisconnectInternals();
+ _atmos.DisconnectInternals(breathTool);
}
else
{
@@ -109,7 +113,7 @@ namespace Content.Server.Clothing
if (TryComp(wearer, out InternalsComponent? internals))
{
breathTool.ConnectedInternalsEntity = wearer;
- internals.ConnectBreathTool(uid);
+ _internals.ConnectBreathTool(internals, uid);
}
}
}
diff --git a/Content.Server/Movement/Systems/JetpackSystem.cs b/Content.Server/Movement/Systems/JetpackSystem.cs
index 7c15621992..f1e7a91aac 100644
--- a/Content.Server/Movement/Systems/JetpackSystem.cs
+++ b/Content.Server/Movement/Systems/JetpackSystem.cs
@@ -1,4 +1,5 @@
using Content.Server.Atmos.Components;
+using Content.Server.Atmos.EntitySystems;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
using Robust.Shared.Collections;
@@ -7,6 +8,8 @@ namespace Content.Server.Movement.Systems;
public sealed class JetpackSystem : SharedJetpackSystem
{
+ [Dependency] private readonly GasTankSystem _gasTank = default!;
+
private const float UpdateCooldown = 0.5f;
protected override bool CanEnable(JetpackComponent component)
@@ -26,7 +29,7 @@ public sealed class JetpackSystem : SharedJetpackSystem
if (active.Accumulator < UpdateCooldown) continue;
active.Accumulator -= UpdateCooldown;
- var air = gasTank.RemoveAir(comp.MoleUsage);
+ var air = _gasTank.RemoveAir(gasTank, comp.MoleUsage);
if (air == null || !MathHelper.CloseTo(air.TotalMoles, comp.MoleUsage, 0.001f))
{
@@ -34,7 +37,7 @@ public sealed class JetpackSystem : SharedJetpackSystem
continue;
}
- gasTank.UpdateUserInterface();
+ _gasTank.UpdateUserInterface(gasTank);
}
foreach (var comp in toDisable)
diff --git a/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs b/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs
index 6bc3df18f5..dfd6c2db31 100644
--- a/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs
+++ b/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs
@@ -1,6 +1,7 @@
using System.Linq;
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
+using Content.Server.Camera;
using Content.Server.Nutrition.Components;
using Content.Server.Storage.EntitySystems;
using Content.Server.Storage.Components;
@@ -27,12 +28,13 @@ namespace Content.Server.PneumaticCannon
public sealed class PneumaticCannonSystem : EntitySystem
{
[Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly StunSystem _stun = default!;
[Dependency] private readonly AtmosphereSystem _atmos = default!;
- [Dependency] private readonly SharedCameraRecoilSystem _sharedCameraRecoil = default!;
+ [Dependency] private readonly CameraRecoilSystem _cameraRecoil = default!;
+ [Dependency] private readonly GasTankSystem _gasTank = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
- [Dependency] private readonly ThrowingSystem _throwingSystem = default!;
[Dependency] private readonly StorageSystem _storageSystem = default!;
+ [Dependency] private readonly StunSystem _stun = default!;
+ [Dependency] private readonly ThrowingSystem _throwingSystem = default!;
private HashSet _currentlyFiring = new();
@@ -211,7 +213,7 @@ namespace Content.Server.PneumaticCannon
{
data.User.PopupMessage(Loc.GetString("pneumatic-cannon-component-fire-no-gas",
("cannon", comp.Owner)));
- SoundSystem.Play("/Audio/Items/hiss.ogg", Filter.Pvs(((IComponent) comp).Owner), ((IComponent) comp).Owner, AudioParams.Default);
+ SoundSystem.Play("/Audio/Items/hiss.ogg", Filter.Pvs(comp.Owner), comp.Owner, AudioParams.Default);
return;
}
@@ -227,11 +229,11 @@ namespace Content.Server.PneumaticCannon
var ent = _random.Pick(storage.StoredEntities);
_storageSystem.RemoveAndDrop(comp.Owner, ent, storage);
- SoundSystem.Play(comp.FireSound.GetSound(), Filter.Pvs(data.User), ((IComponent) comp).Owner, AudioParams.Default);
+ SoundSystem.Play(comp.FireSound.GetSound(), Filter.Pvs(data.User), comp.Owner, AudioParams.Default);
if (EntityManager.HasComponent(data.User))
{
var kick = Vector2.One * data.Strength;
- _sharedCameraRecoil.KickCamera(data.User, kick);
+ _cameraRecoil.KickCamera(data.User, kick);
}
_throwingSystem.TryThrow(ent, data.Direction, data.Strength, data.User, GetPushbackRatioFromPower(comp.Power));
@@ -250,7 +252,7 @@ namespace Content.Server.PneumaticCannon
// we checked for this earlier in HasGas so a GetComp is okay
var gas = EntityManager.GetComponent(contained);
var environment = _atmos.GetContainingMixture(comp.Owner, false, true);
- var removed = gas.RemoveAir(GetMoleUsageFromPower(comp.Power));
+ var removed = _gasTank.RemoveAir(gas, GetMoleUsageFromPower(comp.Power));
if (environment != null && removed != null)
{
_atmos.Merge(environment, removed);
diff --git a/Content.Shared/Alert/AlertCategory.cs b/Content.Shared/Alert/AlertCategory.cs
index df310ca469..e6e0845385 100644
--- a/Content.Shared/Alert/AlertCategory.cs
+++ b/Content.Shared/Alert/AlertCategory.cs
@@ -10,6 +10,7 @@ public enum AlertCategory
Breathing,
Buckled,
Health,
+ Internals,
Stamina,
Piloting,
Hunger,
diff --git a/Content.Shared/Alert/AlertType.cs b/Content.Shared/Alert/AlertType.cs
index 208ec62f3e..5adfa594b1 100644
--- a/Content.Shared/Alert/AlertType.cs
+++ b/Content.Shared/Alert/AlertType.cs
@@ -30,6 +30,7 @@
Pulled,
Pulling,
Magboots,
+ Internals,
Toxins,
Muted,
VowOfSilence,
diff --git a/Content.Shared/Movement/Systems/SharedJetpackSystem.cs b/Content.Shared/Movement/Systems/SharedJetpackSystem.cs
index 8d8cdb3fa5..7a1c9e19f9 100644
--- a/Content.Shared/Movement/Systems/SharedJetpackSystem.cs
+++ b/Content.Shared/Movement/Systems/SharedJetpackSystem.cs
@@ -1,7 +1,6 @@
using Content.Shared.Actions;
using Content.Shared.Gravity;
using Content.Shared.Interaction.Events;
-using Content.Shared.Inventory;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Events;
using Content.Shared.Popups;
@@ -17,10 +16,11 @@ namespace Content.Shared.Movement.Systems;
public abstract class SharedJetpackSystem : EntitySystem
{
+ [Dependency] protected readonly IMapManager MapManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly INetManager _network = default!;
[Dependency] protected readonly SharedContainerSystem Container = default!;
- [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!;
+ [Dependency] protected readonly MovementSpeedModifierSystem MovementSpeedModifier = default!;
[Dependency] private readonly SharedPopupSystem _popups = default!;
public override void Initialize()
@@ -178,7 +178,7 @@ public abstract class SharedJetpackSystem : EntitySystem
RemoveUser(user.Value);
}
- _movementSpeedModifier.RefreshMovementSpeedModifiers(user.Value);
+ MovementSpeedModifier.RefreshMovementSpeedModifiers(user.Value);
}
TryComp(component.Owner, out var appearance);
diff --git a/Resources/Locale/en-US/actions/actions/internals.ftl b/Resources/Locale/en-US/actions/actions/internals.ftl
index cc24d80037..ead73cf1e5 100644
--- a/Resources/Locale/en-US/actions/actions/internals.ftl
+++ b/Resources/Locale/en-US/actions/actions/internals.ftl
@@ -1,2 +1,5 @@
action-name-internals-toggle = Toggle Internals
-action-description-internals-toggle = Breathe from the equipped gas tank. Also requires equipped breath mask.
\ No newline at end of file
+action-description-internals-toggle = Breathe from the equipped gas tank. Also requires equipped breath mask.
+
+internals-no-breath-tool = You are not wearing a breathing tool
+internals-no-tank = You are not wearing a gas tank
diff --git a/Resources/Prototypes/Alerts/alerts.yml b/Resources/Prototypes/Alerts/alerts.yml
index 3132191550..e2c53944ad 100644
--- a/Resources/Prototypes/Alerts/alerts.yml
+++ b/Resources/Prototypes/Alerts/alerts.yml
@@ -5,6 +5,8 @@
id: BaseAlertOrder
order:
- category: Health
+ - category: Stamina
+ - category: Internals
- alertType: Fire
- alertType: Handcuffed
- category: Buckled
@@ -142,6 +144,18 @@
minSeverity: 0
maxSeverity: 6
+- type: alert
+ id: Internals
+ category: Internals
+ onClick: !type:ToggleInternals {}
+ icon:
+ sprite: /Textures/Interface/Alerts/internals.rsi
+ state: internal
+ name: Toggle internals
+ description: "Toggles your gas tank internals on or off."
+ minSeverity: 0
+ maxSeverity: 2
+
- type: alert
id: PilotingShuttle
category: Piloting
diff --git a/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml b/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml
index 0b1c3ffe9d..d934756a06 100644
--- a/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml
+++ b/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml
@@ -20,9 +20,14 @@
toggleAction:
name: action-name-internals-toggle
description: action-description-internals-toggle
- icon: Interface/Actions/internal0.png
- iconOn: Interface/Actions/internal1.png
+ icon:
+ sprite: Interface/Alerts/internals.rsi
+ state: internal2
+ iconOn:
+ sprite: Interface/Alerts/internals.rsi
+ state: internal1
event: !type:ToggleActionEvent
+ useDelay: 1.0
- type: Explosive
explosionType: Default
maxIntensity: 20
diff --git a/Resources/Textures/Interface/Actions/internal0.png b/Resources/Textures/Interface/Actions/internal0.png
deleted file mode 100644
index c136016c6473c6036b1db55b7d3a8979424517dc..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 850
zcmV-Y1FigtP)i$6Gc@7~?}e1Cu5ANX(o*c3k>
zsj4dL`-Wlo`p=Jb`T-{r2}-3B0D(XNK(3V#&k0mjl|l#r^0^$_`ubdLUDsDFqNCYh
zAP``B@olYL^`7a)x3%_at)Y5VRb?WP;PU5ZbWg~_m6hc^_?pP
z^|`m;4JWUT2_amo#DXi`L6p$o($gJu34#U@uqG2iaChf70K(T_V~Nsrox<2b07m9E
zqw6|gUlA6Zo_>CJZDSzx6C})K4P?1!i-czib`1=$YtNqAyV==U0AjIN?Oi6FCX>xJ
zg1!_vRN|ebPaBrCZ=iDgfxPWj*iOnyC2&<2au38y8x(E
z>^^&LMAeC73=9r1hbM}7k611B2^}q5|zJGMZe8uHs6{WTA
zOQn))19BFZJ{bfcc6G=Gm*par2PbUIrW=uE`CJZ7)6g`Hd@g4jv*7wJx@|t-Y@F!
z3Wi}Q&VEa3&2O)r_HxzKy{_xhFbuALe9ZdcqkOu0mp%FK0KC#Ski0U;{79CITMuBG
z=CY#TYhY^MCe{xhCHmnD0Hx7~0Cb!^48Y>HcIHR2q@I8FHINvKx{BBCU-UfZNkM(?
z6}*|$oDjluN)$X2*NFDiev!E5F$7H_pk5|~;BMDf05*@lM47TIi@CFV0T`U#h-F!T
z`$Q;sq!X%Jev(?~24E^bO|>jtk?>8yj_z)D?A+;$<#IUyGMS7sR+yZmP%H+*l~N1c
z%zT+<@rBz!(^HVo=e0tyNMUl)f!nr?ZQG8KX7Hh*A$k76WzWtf@NQiZfL~hG#R{xm
zUDstIk-)ZXR|?cvvz=d?9;K_RlS}$Jrq&fvNnbO&mc{o~s%6RNBh}y(G@1}XIOo1n
zsdz4+#^N&LwYoUbn+b$Ni;GBUwp7A2O-$2dwp4P>DYzN89Pr8B44`rS&aFUrCpwcK50HXa%Mt_OT6h0h
zj)BgDTu7ba(CqbJK*fz)lbO4oht=Kzz-gYYuw|J3v86d-!z1T
zL&zfZEb44N9_`Wo2@WkH5G124Tp^G4Xo1!4r@#oPI;fYFlIjd>Ka_I9zEn80xQH4H
zF`Z7!W4qpPEp+-}oPr=yOn3hZ?NhXtl@E|pd#y_2cY&N@M
n{|A<2Agt;`-l_&x_22ayngy}?iV}4800000NkvXXu0mjfLid{T
diff --git a/Resources/Textures/Interface/Alerts/internals.rsi/internal0.png b/Resources/Textures/Interface/Alerts/internals.rsi/internal0.png
new file mode 100644
index 0000000000000000000000000000000000000000..95c4644ec06b7250b51da386bb6369415ba9ab2d
GIT binary patch
literal 1511
zcmVNpvYQe)Xh0enhYP51&z80noTSvpsjyOAn7bPaQ?~l
z{7&xgdhWUB+b(`SpO0-34H(`o97&pbO>+K;nsAgYKJ+
zs@V+)EdYv>@cu^CJpk`@0YV8t;v~EfjXLfhE0>%1x&Yw<5XDJ2nM}g=_BL-13=F`6
z0MaRh13(s!$K$+zZFLm}hlcDn^)L89s1jHn)8jQ5y*>HMr|>X@c^j>|xe1HcuE6ZA
z_j!6I=8(fAP)7tr?=kEszGrR%&c1REZqHQsn9I={;g-26IC&xmvuq4SA+Hb*lDs`S
z?@7Gpr(azkLpfhjBT3ddHU
zmcE0xr~41`v1qg{W${1z24N(8oyB-BtXIk$;)Ru`+z~+gj!l5;iBZ60n;95w-bmgC
z#bUA9Wa|!AVAeKQ`8VGQ7KAL0R;l;FNa%Vq8ozJ3mw+b(3khW{hp-0h8y)51^?IFO
zV>=d$@$I-)so3IZ=Tj+oHu4d?UVGm59K|ui@Ow8K(`XoO01*PLTp)=0)vH{b_CAK3
zfJAW&tzm{>aeN&v4sXEp`MtI{+U`H9(D>~~*Ky_fGNR=`aXJUEjS#Xpn#sbOUb+bi
z#{m`}DMRe7afdkGKb1Yqms$xRMj^tY&~No)K2Wml8yRsxl6c{Gs0lc~1+llsq4@MA
zIDP00Os~XU;(0c=;~XHP1BArW>@(Ke%hy_50`h*wO1O)2O<{t-up`9jL+dabJ^`=Q
z9#V=s0F>fmWB)FNuq>iDdKRB|E?x1NOK|@i5jeU$438S`X~kU;K$FFvE0y@ec+k+%
zN?}LO0Ycn+GK>OVn|%g{o60V0#gzb}kvKj~TT~9aC`#96Us${5EdgkaAFms(ZX1suVEJxMpKE~KSr!qv}~?(Kt76P2(s+A`{Bg{kHLKH4E)VsEI06}
zWbx!@A2uJj_lVkc)$3RXftE-lpjNAaVHog5H4djPOuFV2#WR*LwQV3~83D?*1oCnG
zuGRlUJ`WQU6TE#imvfxwf>+>WGKdmDgrIdqkTCkxXxerw-`z92!zYPOzgzV?pdAP@mH{4Cp!Y%R1JGdzV!hW
z1Xz3BuRb7(r&1|1(r$e~2jNv8kUjsP+w}oj4pn_X7RT=(^?^qAuzwQD=2agc@gVg9
zSv&}U*{i4zkoS~uLFxl4aX$c){-TRXDT@cG59q|b0BrG6sbq-4B#z%f>H}x9%kBR;
zC2_|CQrJaNxjx|Kc_09-1*i`M5pN#=yz2ueD%-9+>hn_|*qm5%&Txm@rR75dzzj
z9`%7%#k~Mr5Y(xw56I%h&%fl)#O}Gf{arv1*pTZ3jSG|7iBX({sZBsKRFEX(gY)^k
zN%etTF4xviFc~B$fDMwf>I2=h9rDb>gpqR+g7B^!+V*51ztySWW=Sp2|+*
zyZf9NxiN}k<6$;8YukeZeeLk{`_L=jgCeV-TLCU!_W2uXy3b(N`<%teha96hg0LDa
zUga;#Xx^A(Jqt7ofbadHTxJs2B~Q(z`XQ?dBwk_`P-0Q8lMyARg~(ZfKp+6Oyzpq~
z3%lO$eS`K?;{~oiLA3ig^WA%ovU|0r7Ef0Hg7s%DPzfxCma-tS04Nix0yW!ZQ@s*&
z9YQb|R9xEkRAJ_q=hDyUiIXkv3Nr{6i)$*Gs{*M7+5Xkl*E8{0EXL-@x7+RPb({}}
zGsHVOIwZ?83%u6`(wUPuEwmtyr6|XSkjxLDhKMB+Oq{>_RB{fa&3wZA5Q9rzT#QWO
zN~Jq3PFA{7j`dGTXuHtx9N^nQ;`|&SpCGj2G$!K`diHf;903f3hS1t*&k(2i#Q9|Y
zCe#}B18=sv+S-gcEK~nqA0RGHnYkclJojiih&Ib<^jN#le7{Xs
z+z^0^&&Y?
zTb*n8_<{907*qoM6N<$f(#U^a{vGU
literal 0
HcmV?d00001
diff --git a/Resources/Textures/Interface/Alerts/internals.rsi/internal2.png b/Resources/Textures/Interface/Alerts/internals.rsi/internal2.png
new file mode 100644
index 0000000000000000000000000000000000000000..401c72e3c079aa2a594bf8b1ecd3f174bb912f1b
GIT binary patch
literal 896
zcmV-`1AqL9P)1RCt{2mrH09Q51&%(Uw$-q$H72!H`8L6>D_SEP_N7>LVqf
zRkAVx#ch|-g>+fOMK?;pm2TQasjXFLn}`dgL4tInsX_?>ZG42nB(-3{`p7uvCO6C@
zlQ(wb+1%G;zW@C9%$-Rab~*kFs7Zj!<&u=+L?R*l69F9T_xrK6wZ-PlW-~QXtArQ|
zAh_4-W&PyZ8oGOWN_{?0LYfs((E-YK;_EA%g_UQHjZ6lU+wU-P@B~^BWd%_7WIMq-
z!amI1na9zE6kA($?ZbiaFeb($7?H0*lP(xKz~OD5zo9318mm(;*gW+i&(Ru*a8$av
zKdLI;-wWq;LUjW8-hY&BCb;3vi)+Pt(78Zxp~C`n+6xogX*Q=7QPzQ2EQXMLZUzQUyc
z5aRK8p-cN-G$xr@EB(d@PLax*;I!-%-1w1Gi!J9s@rG>wdi(kqJe$q3HR^jj9`-mU
zQz;cZ5C}+i`*YkAZtBQ4yTqf
z2P`am9^?XzC6mC!%`MJUJf`dZAko^cgz~XuvKsDr(*jtq<29|D7OclB7F_EDKwWgy
zZv@_u07;S{$S)R0|0x;#lFc;=uUo*tz<@Mly^Pzr$M}$ZgFP~sqEUEV0tim+;`_t6
zb>tGp#991dFP6RRcx?n;mjIj122m6t2m)Sj$fw)0p|U{!A