2021-11-23 22:08:17 -06:00
using Content.Server.Administration.Logs ;
2021-06-19 13:25:05 +02:00
using Content.Server.Atmos.Components ;
2021-06-23 11:35:30 +02:00
using Content.Server.Atmos.EntitySystems ;
2021-06-19 13:25:05 +02:00
using Content.Server.Atmos.Piping.Components ;
2021-06-23 12:02:28 +02:00
using Content.Server.Atmos.Piping.Unary.Components ;
2022-08-07 18:49:06 -07:00
using Content.Server.Cargo.Systems ;
2021-06-19 13:25:05 +02:00
using Content.Server.NodeContainer ;
2023-06-28 14:28:38 +03:00
using Content.Server.NodeContainer.EntitySystems ;
2021-07-12 10:00:50 +02:00
using Content.Server.NodeContainer.NodeGroups ;
2021-07-04 18:11:52 +02:00
using Content.Server.NodeContainer.Nodes ;
2022-09-16 09:06:29 -05:00
using Content.Server.Popups ;
2021-06-19 13:25:05 +02:00
using Content.Shared.Atmos ;
using Content.Shared.Atmos.Piping.Binary.Components ;
2023-12-16 01:28:27 -07:00
using Content.Shared.Containers.ItemSlots ;
2021-11-28 14:56:53 +01:00
using Content.Shared.Database ;
2021-06-19 13:25:05 +02:00
using Content.Shared.Interaction ;
2023-02-11 20:12:29 -05:00
using Content.Shared.Lock ;
2021-06-19 13:25:05 +02:00
using Robust.Server.GameObjects ;
2023-11-27 22:12:34 +11:00
using Robust.Shared.Audio.Systems ;
2021-06-19 13:25:05 +02:00
using Robust.Shared.Containers ;
2023-10-29 04:21:02 +11:00
using Robust.Shared.Player ;
2021-06-19 13:25:05 +02:00
2023-06-15 12:03:20 +00:00
namespace Content.Server.Atmos.Piping.Unary.EntitySystems ;
public sealed class GasCanisterSystem : EntitySystem
2021-06-19 13:25:05 +02:00
{
2023-06-15 12:03:20 +00:00
[Dependency] private readonly AtmosphereSystem _atmos = default ! ;
[Dependency] private readonly IAdminLogManager _adminLogger = default ! ;
[Dependency] private readonly SharedAppearanceSystem _appearance = default ! ;
[Dependency] private readonly SharedAudioSystem _audio = default ! ;
[Dependency] private readonly PopupSystem _popup = default ! ;
[Dependency] private readonly UserInterfaceSystem _ui = default ! ;
2023-06-28 14:28:38 +03:00
[Dependency] private readonly NodeContainerSystem _nodeContainer = default ! ;
2023-12-16 01:28:27 -07:00
[Dependency] private readonly ItemSlotsSystem _slots = default ! ;
2023-06-15 12:03:20 +00:00
public override void Initialize ( )
2021-06-19 13:25:05 +02:00
{
2023-06-15 12:03:20 +00:00
base . Initialize ( ) ;
SubscribeLocalEvent < GasCanisterComponent , ComponentStartup > ( OnCanisterStartup ) ;
SubscribeLocalEvent < GasCanisterComponent , AtmosDeviceUpdateEvent > ( OnCanisterUpdated ) ;
2023-06-30 16:39:34 -03:00
SubscribeLocalEvent < GasCanisterComponent , ActivateInWorldEvent > ( OnCanisterActivate , after : new [ ] { typeof ( LockSystem ) } ) ;
2023-06-15 12:03:20 +00:00
SubscribeLocalEvent < GasCanisterComponent , InteractHandEvent > ( OnCanisterInteractHand ) ;
2023-12-16 01:28:27 -07:00
SubscribeLocalEvent < GasCanisterComponent , ItemSlotInsertAttemptEvent > ( OnCanisterInsertAttempt ) ;
2023-06-15 12:03:20 +00:00
SubscribeLocalEvent < GasCanisterComponent , EntInsertedIntoContainerMessage > ( OnCanisterContainerInserted ) ;
SubscribeLocalEvent < GasCanisterComponent , EntRemovedFromContainerMessage > ( OnCanisterContainerRemoved ) ;
SubscribeLocalEvent < GasCanisterComponent , PriceCalculationEvent > ( CalculateCanisterPrice ) ;
SubscribeLocalEvent < GasCanisterComponent , GasAnalyzerScanEvent > ( OnAnalyzed ) ;
// Bound UI subscriptions
SubscribeLocalEvent < GasCanisterComponent , GasCanisterHoldingTankEjectMessage > ( OnHoldingTankEjectMessage ) ;
SubscribeLocalEvent < GasCanisterComponent , GasCanisterChangeReleasePressureMessage > ( OnCanisterChangeReleasePressure ) ;
SubscribeLocalEvent < GasCanisterComponent , GasCanisterChangeReleaseValveMessage > ( OnCanisterChangeReleaseValve ) ;
}
2021-11-05 19:19:12 -05:00
2023-06-15 12:03:20 +00:00
/// <summary>
/// Completely dumps the content of the canister into the world.
/// </summary>
public void PurgeContents ( EntityUid uid , GasCanisterComponent ? canister = null , TransformComponent ? transform = null )
{
if ( ! Resolve ( uid , ref canister , ref transform ) )
return ;
2021-11-05 19:19:12 -05:00
2024-03-30 17:17:53 +13:00
var environment = _atmos . GetContainingMixture ( ( uid , transform ) , false , true ) ;
2023-06-15 12:03:20 +00:00
if ( environment is not null )
_atmos . Merge ( environment , canister . Air ) ;
2021-11-05 19:19:12 -05:00
2023-06-15 12:03:20 +00:00
_adminLogger . Add ( LogType . CanisterPurged , LogImpact . Medium , $"Canister {ToPrettyString(uid):canister} purged its contents of {canister.Air:gas} into the environment." ) ;
canister . Air . Clear ( ) ;
}
2021-11-05 19:19:12 -05:00
2023-06-15 12:03:20 +00:00
private void OnCanisterStartup ( EntityUid uid , GasCanisterComponent comp , ComponentStartup args )
{
2023-12-16 01:28:27 -07:00
// Ensure container
_slots . AddItemSlot ( uid , comp . ContainerName , comp . GasTankSlot ) ;
2023-06-15 12:03:20 +00:00
}
2021-06-23 12:02:28 +02:00
2023-06-15 12:03:20 +00:00
private void DirtyUI ( EntityUid uid ,
2023-12-16 01:28:27 -07:00
GasCanisterComponent ? canister = null , NodeContainerComponent ? nodeContainer = null )
2023-06-15 12:03:20 +00:00
{
2023-12-16 01:28:27 -07:00
if ( ! Resolve ( uid , ref canister , ref nodeContainer ) )
2023-06-15 12:03:20 +00:00
return ;
2022-09-16 09:06:29 -05:00
2023-06-15 12:03:20 +00:00
var portStatus = false ;
string? tankLabel = null ;
var tankPressure = 0f ;
2023-06-28 14:28:38 +03:00
if ( _nodeContainer . TryGetNode ( nodeContainer , canister . PortName , out PipeNode ? portNode ) & & portNode . NodeGroup ? . Nodes . Count > 1 )
2023-06-15 12:03:20 +00:00
portStatus = true ;
2021-06-19 13:25:05 +02:00
2023-12-16 01:28:27 -07:00
if ( canister . GasTankSlot . Item ! = null )
2021-06-19 13:25:05 +02:00
{
2023-12-16 01:28:27 -07:00
var tank = canister . GasTankSlot . Item . Value ;
2023-06-15 12:03:20 +00:00
var tankComponent = Comp < GasTankComponent > ( tank ) ;
tankLabel = Name ( tank ) ;
tankPressure = tankComponent . Air . Pressure ;
}
2021-06-19 13:25:05 +02:00
2024-04-26 18:16:24 +10:00
_ui . SetUiState ( uid , GasCanisterUiKey . Key ,
2023-06-15 12:03:20 +00:00
new GasCanisterBoundUserInterfaceState ( Name ( uid ) ,
canister . Air . Pressure , portStatus , tankLabel , tankPressure , canister . ReleasePressure ,
canister . ReleaseValve , canister . MinReleasePressure , canister . MaxReleasePressure ) ) ;
}
2021-06-19 13:25:05 +02:00
2023-06-15 12:03:20 +00:00
private void OnHoldingTankEjectMessage ( EntityUid uid , GasCanisterComponent canister , GasCanisterHoldingTankEjectMessage args )
{
2024-04-26 18:16:24 +10:00
if ( canister . GasTankSlot . Item = = null )
2023-06-15 12:03:20 +00:00
return ;
2021-06-19 13:25:05 +02:00
2023-12-16 01:28:27 -07:00
var item = canister . GasTankSlot . Item ;
2024-04-26 18:16:24 +10:00
_slots . TryEjectToHands ( uid , canister . GasTankSlot , args . Actor ) ;
_adminLogger . Add ( LogType . CanisterTankEjected , LogImpact . Medium , $"Player {ToPrettyString(args.Actor):player} ejected tank {ToPrettyString(item):tank} from {ToPrettyString(uid):canister}" ) ;
2023-06-15 12:03:20 +00:00
}
2021-06-19 13:25:05 +02:00
2023-06-15 12:03:20 +00:00
private void OnCanisterChangeReleasePressure ( EntityUid uid , GasCanisterComponent canister , GasCanisterChangeReleasePressureMessage args )
{
var pressure = Math . Clamp ( args . Pressure , canister . MinReleasePressure , canister . MaxReleasePressure ) ;
2021-06-19 13:25:05 +02:00
2024-04-26 18:16:24 +10:00
_adminLogger . Add ( LogType . CanisterPressure , LogImpact . Medium , $"{ToPrettyString(args.Actor):player} set the release pressure on {ToPrettyString(uid):canister} to {args.Pressure}" ) ;
2021-06-19 13:25:05 +02:00
2023-06-15 12:03:20 +00:00
canister . ReleasePressure = pressure ;
DirtyUI ( uid , canister ) ;
}
2021-06-19 13:25:05 +02:00
2023-06-15 12:03:20 +00:00
private void OnCanisterChangeReleaseValve ( EntityUid uid , GasCanisterComponent canister , GasCanisterChangeReleaseValveMessage args )
{
2023-12-21 18:48:18 -07:00
var impact = LogImpact . High ;
2023-12-16 01:28:27 -07:00
// filling a jetpack with plasma is less important than filling a room with it
impact = canister . GasTankSlot . HasItem ? LogImpact . Medium : LogImpact . High ;
2021-06-19 13:25:05 +02:00
2023-06-15 12:03:20 +00:00
var containedGasDict = new Dictionary < Gas , float > ( ) ;
2024-04-26 18:16:24 +10:00
var containedGasArray = Enum . GetValues ( typeof ( Gas ) ) ;
2021-11-23 22:08:17 -06:00
2023-06-15 12:03:20 +00:00
for ( int i = 0 ; i < containedGasArray . Length ; i + + )
{
2024-03-24 03:34:56 +11:00
containedGasDict . Add ( ( Gas ) i , canister . Air [ i ] ) ;
2021-09-19 09:32:38 +02:00
}
2024-04-26 18:16:24 +10:00
_adminLogger . Add ( LogType . CanisterValve , impact , $"{ToPrettyString(args.Actor):player} set the valve on {ToPrettyString(uid):canister} to {args.Valve:valveState} while it contained [{string.Join(" , ", containedGasDict)}]" ) ;
2021-11-24 16:52:31 -06:00
2023-06-15 12:03:20 +00:00
canister . ReleaseValve = args . Valve ;
DirtyUI ( uid , canister ) ;
}
2022-08-07 20:21:56 -03:00
2023-12-21 18:48:18 -07:00
private void OnCanisterUpdated ( EntityUid uid , GasCanisterComponent canister , ref AtmosDeviceUpdateEvent args )
2023-06-15 12:03:20 +00:00
{
_atmos . React ( canister . Air , canister ) ;
2022-08-07 20:21:56 -03:00
2023-06-15 12:03:20 +00:00
if ( ! TryComp < NodeContainerComponent > ( uid , out var nodeContainer )
| | ! TryComp < AppearanceComponent > ( uid , out var appearance ) )
return ;
2021-11-23 22:08:17 -06:00
2023-06-28 14:28:38 +03:00
if ( ! _nodeContainer . TryGetNode ( nodeContainer , canister . PortName , out PortablePipeNode ? portNode ) )
2023-06-15 12:03:20 +00:00
return ;
2021-06-19 13:25:05 +02:00
2023-06-15 12:03:20 +00:00
if ( portNode . NodeGroup is PipeNet { NodeCount : > 1 } net )
2021-06-19 13:25:05 +02:00
{
2023-06-15 12:03:20 +00:00
MixContainerWithPipeNet ( canister . Air , net . Air ) ;
}
2023-12-21 18:48:18 -07:00
2023-06-15 12:03:20 +00:00
// Release valve is open, release gas.
if ( canister . ReleaseValve )
{
2023-12-16 01:28:27 -07:00
if ( canister . GasTankSlot . Item ! = null )
2021-07-12 10:00:50 +02:00
{
2023-12-16 01:28:27 -07:00
var gasTank = Comp < GasTankComponent > ( canister . GasTankSlot . Item . Value ) ;
2023-06-15 12:03:20 +00:00
_atmos . ReleaseGasTo ( canister . Air , gasTank . Air , canister . ReleasePressure ) ;
2021-07-12 10:00:50 +02:00
}
2023-06-15 12:03:20 +00:00
else
2021-06-23 12:02:28 +02:00
{
2024-03-30 17:17:53 +13:00
var environment = _atmos . GetContainingMixture ( uid , args . Grid , args . Map , false , true ) ;
2023-06-15 12:03:20 +00:00
_atmos . ReleaseGasTo ( canister . Air , environment , canister . ReleasePressure ) ;
2021-06-23 12:02:28 +02:00
}
2023-06-15 12:03:20 +00:00
}
2021-06-23 12:02:28 +02:00
2023-06-15 12:03:20 +00:00
// If last pressure is very close to the current pressure, do nothing.
if ( MathHelper . CloseToPercent ( canister . Air . Pressure , canister . LastPressure ) )
return ;
2021-06-19 13:25:05 +02:00
2023-12-16 01:28:27 -07:00
DirtyUI ( uid , canister , nodeContainer ) ;
2022-02-20 08:15:47 +13:00
2023-06-15 12:03:20 +00:00
canister . LastPressure = canister . Air . Pressure ;
2021-06-19 13:25:05 +02:00
2023-06-15 12:03:20 +00:00
if ( canister . Air . Pressure < 10 )
{
_appearance . SetData ( uid , GasCanisterVisuals . PressureState , 0 , appearance ) ;
2021-06-19 13:25:05 +02:00
}
2023-06-15 12:03:20 +00:00
else if ( canister . Air . Pressure < Atmospherics . OneAtmosphere )
2021-06-19 13:25:05 +02:00
{
2023-06-15 12:03:20 +00:00
_appearance . SetData ( uid , GasCanisterVisuals . PressureState , 1 , appearance ) ;
}
else if ( canister . Air . Pressure < ( 15 * Atmospherics . OneAtmosphere ) )
{
_appearance . SetData ( uid , GasCanisterVisuals . PressureState , 2 , appearance ) ;
2021-06-19 13:25:05 +02:00
}
2023-06-15 12:03:20 +00:00
else
{
_appearance . SetData ( uid , GasCanisterVisuals . PressureState , 3 , appearance ) ;
}
}
2021-06-19 13:25:05 +02:00
2023-06-15 12:03:20 +00:00
private void OnCanisterActivate ( EntityUid uid , GasCanisterComponent component , ActivateInWorldEvent args )
{
2024-05-31 16:26:19 -04:00
if ( ! args . Complex )
return ;
2023-06-15 12:03:20 +00:00
if ( ! TryComp < ActorComponent > ( args . User , out var actor ) )
return ;
2021-06-19 13:25:05 +02:00
2023-06-15 12:03:20 +00:00
if ( CheckLocked ( uid , component , args . User ) )
return ;
2021-06-19 13:25:05 +02:00
2023-06-30 16:39:34 -03:00
// Needs to be here so the locked check still happens if the canister
// is locked and you don't have permissions
if ( args . Handled )
return ;
2024-04-26 18:16:24 +10:00
_ui . OpenUi ( uid , GasCanisterUiKey . Key , actor . PlayerSession ) ;
2023-06-15 12:03:20 +00:00
args . Handled = true ;
}
2021-06-19 13:25:05 +02:00
2023-06-15 12:03:20 +00:00
private void OnCanisterInteractHand ( EntityUid uid , GasCanisterComponent component , InteractHandEvent args )
{
if ( ! TryComp < ActorComponent > ( args . User , out var actor ) )
return ;
2021-06-19 13:25:05 +02:00
2023-06-15 12:03:20 +00:00
if ( CheckLocked ( uid , component , args . User ) )
return ;
2021-06-19 13:25:05 +02:00
2024-04-26 18:16:24 +10:00
_ui . OpenUi ( uid , GasCanisterUiKey . Key , actor . PlayerSession ) ;
2023-06-15 12:03:20 +00:00
args . Handled = true ;
}
2021-06-19 13:25:05 +02:00
2023-12-16 01:28:27 -07:00
private void OnCanisterInsertAttempt ( EntityUid uid , GasCanisterComponent component , ref ItemSlotInsertAttemptEvent args )
2023-06-15 12:03:20 +00:00
{
2023-12-16 01:28:27 -07:00
if ( args . Slot . ID ! = component . ContainerName | | args . User = = null )
2023-06-15 12:03:20 +00:00
return ;
2021-11-24 16:52:31 -06:00
2023-12-16 01:28:27 -07:00
if ( ! TryComp < GasTankComponent > ( args . Item , out var gasTank ) | | gasTank . IsValveOpen )
{
args . Cancelled = true ;
2023-06-15 12:03:20 +00:00
return ;
2023-12-16 01:28:27 -07:00
}
2021-06-19 13:25:05 +02:00
2023-06-15 12:03:20 +00:00
// Preventing inserting a tank since if its locked you cant remove it.
2023-12-21 18:48:18 -07:00
if ( ! CheckLocked ( uid , component , args . User . Value ) )
2023-06-15 12:03:20 +00:00
return ;
2023-12-21 18:48:18 -07:00
2023-12-16 01:28:27 -07:00
args . Cancelled = true ;
2023-06-15 12:03:20 +00:00
}
2021-06-19 13:25:05 +02:00
2023-06-15 12:03:20 +00:00
private void OnCanisterContainerInserted ( EntityUid uid , GasCanisterComponent component , EntInsertedIntoContainerMessage args )
{
if ( args . Container . ID ! = component . ContainerName )
return ;
2021-06-19 13:25:05 +02:00
2023-06-15 12:03:20 +00:00
DirtyUI ( uid , component ) ;
2022-07-15 08:46:30 -04:00
2023-06-15 12:03:20 +00:00
_appearance . SetData ( uid , GasCanisterVisuals . TankInserted , true ) ;
}
2022-07-15 08:46:30 -04:00
2023-06-15 12:03:20 +00:00
private void OnCanisterContainerRemoved ( EntityUid uid , GasCanisterComponent component , EntRemovedFromContainerMessage args )
{
if ( args . Container . ID ! = component . ContainerName )
return ;
2022-07-15 08:46:30 -04:00
2023-06-15 12:03:20 +00:00
DirtyUI ( uid , component ) ;
2022-07-15 08:46:30 -04:00
2023-06-15 12:03:20 +00:00
_appearance . SetData ( uid , GasCanisterVisuals . TankInserted , false ) ;
}
2022-08-07 18:49:06 -07:00
2023-06-15 12:03:20 +00:00
/// <summary>
/// Mix air from a gas container into a pipe net.
/// Useful for anything that uses connector ports.
/// </summary>
public void MixContainerWithPipeNet ( GasMixture containerAir , GasMixture pipeNetAir )
{
var buffer = new GasMixture ( pipeNetAir . Volume + containerAir . Volume ) ;
2022-09-08 14:22:14 +00:00
2023-06-15 12:03:20 +00:00
_atmos . Merge ( buffer , pipeNetAir ) ;
_atmos . Merge ( buffer , containerAir ) ;
pipeNetAir . Clear ( ) ;
_atmos . Merge ( pipeNetAir , buffer ) ;
pipeNetAir . Multiply ( pipeNetAir . Volume / buffer . Volume ) ;
containerAir . Clear ( ) ;
_atmos . Merge ( containerAir , buffer ) ;
containerAir . Multiply ( containerAir . Volume / buffer . Volume ) ;
}
private void CalculateCanisterPrice ( EntityUid uid , GasCanisterComponent component , ref PriceCalculationEvent args )
{
args . Price + = _atmos . GetPrice ( component . Air ) ;
}
2022-09-16 09:06:29 -05:00
2023-06-15 12:03:20 +00:00
/// <summary>
/// Returns the gas mixture for the gas analyzer
/// </summary>
2024-04-17 19:42:24 +02:00
private void OnAnalyzed ( EntityUid uid , GasCanisterComponent canisterComponent , GasAnalyzerScanEvent args )
2023-06-15 12:03:20 +00:00
{
2024-04-17 19:42:24 +02:00
args . GasMixtures ? ? = new List < ( string , GasMixture ? ) > ( ) ;
args . GasMixtures . Add ( ( Name ( uid ) , canisterComponent . Air ) ) ;
// if a tank is inserted show it on the analyzer as well
if ( canisterComponent . GasTankSlot . Item ! = null )
{
var tank = canisterComponent . GasTankSlot . Item . Value ;
var tankComponent = Comp < GasTankComponent > ( tank ) ;
args . GasMixtures . Add ( ( Name ( tank ) , tankComponent . Air ) ) ;
}
2023-06-15 12:03:20 +00:00
}
/// <summary>
/// Check if the canister is locked, playing its sound and popup if so.
/// </summary>
/// <returns>
/// True if locked, false otherwise.
/// </returns>
private bool CheckLocked ( EntityUid uid , GasCanisterComponent comp , EntityUid user )
{
if ( TryComp < LockComponent > ( uid , out var lockComp ) & & lockComp . Locked )
2022-09-16 09:06:29 -05:00
{
2023-06-15 12:03:20 +00:00
_popup . PopupEntity ( Loc . GetString ( "gas-canister-popup-denied" ) , uid , user ) ;
2023-12-16 01:28:27 -07:00
_audio . PlayPvs ( comp . AccessDeniedSound , uid ) ;
2023-06-15 12:03:20 +00:00
return true ;
2022-09-16 09:06:29 -05:00
}
2023-06-15 12:03:20 +00:00
return false ;
2021-06-19 13:25:05 +02:00
}
}