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#=yz2u&#eD%-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