using System; using Content.Server.Administration.Logs; using Content.Server.Atmos.Components; using Content.Server.Atmos.EntitySystems; using Content.Server.Atmos.Piping.Components; using Content.Server.Atmos.Piping.Unary.Components; using Content.Server.Hands.Components; using Content.Server.NodeContainer; using Content.Server.NodeContainer.NodeGroups; using Content.Server.NodeContainer.Nodes; using Content.Shared.ActionBlocker; using Content.Shared.Atmos; using Content.Shared.Atmos.Piping.Binary.Components; using Content.Shared.Database; using Content.Shared.Interaction; using Content.Shared.Interaction.Helpers; using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Maths; using Robust.Shared.Players; namespace Content.Server.Atmos.Piping.Unary.EntitySystems { [UsedImplicitly] public class GasCanisterSystem : EntitySystem { [Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!; [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; [Dependency] private readonly AdminLogSystem _adminLogSystem = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnCanisterStartup); SubscribeLocalEvent(OnCanisterUpdated); SubscribeLocalEvent(OnCanisterActivate); SubscribeLocalEvent(OnCanisterInteractHand); SubscribeLocalEvent(OnCanisterInteractUsing); SubscribeLocalEvent(OnCanisterContainerInserted); SubscribeLocalEvent(OnCanisterContainerRemoved); // Bound UI subscriptions SubscribeLocalEvent(OnHoldingTankEjectMessage); SubscribeLocalEvent(OnCanisterChangeReleasePressure); SubscribeLocalEvent(OnCanisterChangeReleaseValve); } /// /// Completely dumps the content of the canister into the world. /// public void PurgeContents(EntityUid uid, GasCanisterComponent? canister = null, TransformComponent? transform = null) { if (!Resolve(uid, ref canister, ref transform)) return; var environment = _atmosphereSystem.GetTileMixture(transform.Coordinates, true); if (environment is not null) _atmosphereSystem.Merge(environment, canister.Air); _adminLogSystem.Add(LogType.CanisterPurged, LogImpact.Medium, $"Canister {ToPrettyString(uid):canister} purged its contents of {canister.Air:gas} into the environment."); canister.Air.Clear(); } private void OnCanisterStartup(EntityUid uid, GasCanisterComponent canister, ComponentStartup args) { // Ensure container manager. var containerManager = EntityManager.EnsureComponent(uid); // Ensure container. if (!containerManager.TryGetContainer(canister.ContainerName, out _)) { containerManager.MakeContainer(canister.ContainerName); } } private bool CheckInteract(ICommonSession session) { if (session.AttachedEntity is not {Valid: true} uid || !_actionBlockerSystem.CanInteract(uid) || !_actionBlockerSystem.CanUse(uid)) return false; return true; } private void DirtyUI(EntityUid uid, GasCanisterComponent? canister = null, NodeContainerComponent? nodeContainer = null, ContainerManagerComponent? containerManager = null) { if (!Resolve(uid, ref canister, ref nodeContainer, ref containerManager)) return; var portStatus = false; string? tankLabel = null; var tankPressure = 0f; if (nodeContainer.TryGetNode(canister.PortName, out PipeNode? portNode) && portNode.NodeGroup?.Nodes.Count > 1) portStatus = true; if (containerManager.TryGetContainer(canister.ContainerName, out var tankContainer) && tankContainer.ContainedEntities.Count > 0) { var tank = tankContainer.ContainedEntities[0]; var tankComponent = EntityManager.GetComponent(tank); tankLabel = EntityManager.GetComponent(tank).EntityName; tankPressure = tankComponent.Air.Pressure; } _userInterfaceSystem.TrySetUiState(uid, GasCanisterUiKey.Key, new GasCanisterBoundUserInterfaceState(EntityManager.GetComponent(canister.Owner).EntityName, canister.Air.Pressure, portStatus, tankLabel, tankPressure, canister.ReleasePressure, canister.ReleaseValve, canister.MinReleasePressure, canister.MaxReleasePressure)); } private void OnHoldingTankEjectMessage(EntityUid uid, GasCanisterComponent canister, GasCanisterHoldingTankEjectMessage args) { if (!CheckInteract(args.Session)) return; if (!EntityManager.TryGetComponent(uid, out ContainerManagerComponent? containerManager) || !containerManager.TryGetContainer(canister.ContainerName, out var container)) return; if (container.ContainedEntities.Count == 0) return; _adminLogSystem.Add(LogType.CanisterTankEjected, LogImpact.Medium, $"Player {ToPrettyString(args.Session.AttachedEntity.GetValueOrDefault()):player} ejected tank {ToPrettyString(container.ContainedEntities[0]):tank} from {ToPrettyString(uid):canister}"); container.Remove(container.ContainedEntities[0]); } private void OnCanisterChangeReleasePressure(EntityUid uid, GasCanisterComponent canister, GasCanisterChangeReleasePressureMessage args) { if (!CheckInteract(args.Session)) return; var pressure = Math.Clamp(args.Pressure, canister.MinReleasePressure, canister.MaxReleasePressure); _adminLogSystem.Add(LogType.CanisterPressure, LogImpact.Medium, $"{ToPrettyString(args.Session.AttachedEntity.GetValueOrDefault()):player} set the release pressure on {ToPrettyString(uid):canister} to {args.Pressure}"); canister.ReleasePressure = pressure; DirtyUI(uid, canister); } private void OnCanisterChangeReleaseValve(EntityUid uid, GasCanisterComponent canister, GasCanisterChangeReleaseValveMessage args) { if (!CheckInteract(args.Session)) return; var impact = LogImpact.High; if (EntityManager.TryGetComponent(uid, out ContainerManagerComponent containerManager) && containerManager.TryGetContainer(canister.ContainerName, out var container)) impact = container.ContainedEntities.Count != 0 ? LogImpact.Medium : LogImpact.High; _adminLogSystem.Add(LogType.CanisterValve, impact, $"{ToPrettyString(args.Session.AttachedEntity.GetValueOrDefault()):player} set the valve on {ToPrettyString(uid):canister} to {args.Valve:valveState}"); canister.ReleaseValve = args.Valve; DirtyUI(uid, canister); } private void OnCanisterUpdated(EntityUid uid, GasCanisterComponent canister, AtmosDeviceUpdateEvent args) { if (!EntityManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer) || !EntityManager.TryGetComponent(uid, out AppearanceComponent? appearance)) return; if (!nodeContainer.TryGetNode(canister.PortName, out PortablePipeNode? portNode)) return; _atmosphereSystem.React(canister.Air, portNode); if (portNode.NodeGroup is PipeNet {NodeCount: > 1} net) { var buffer = new GasMixture(net.Air.Volume + canister.Air.Volume); _atmosphereSystem.Merge(buffer, net.Air); _atmosphereSystem.Merge(buffer, canister.Air); net.Air.Clear(); _atmosphereSystem.Merge(net.Air, buffer); net.Air.Multiply(net.Air.Volume / buffer.Volume); canister.Air.Clear(); _atmosphereSystem.Merge(canister.Air, buffer); canister.Air.Multiply(canister.Air.Volume / buffer.Volume); } ContainerManagerComponent? containerManager = null; // Release valve is open, release gas. if (canister.ReleaseValve) { if (!EntityManager.TryGetComponent(uid, out containerManager) || !containerManager.TryGetContainer(canister.ContainerName, out var container)) return; if (container.ContainedEntities.Count > 0) { var gasTank = EntityManager.GetComponent(container.ContainedEntities[0]); _atmosphereSystem.ReleaseGasTo(canister.Air, gasTank.Air, canister.ReleasePressure); } else { var environment = _atmosphereSystem.GetTileMixture(EntityManager.GetComponent(canister.Owner).Coordinates, true); _atmosphereSystem.ReleaseGasTo(canister.Air, environment, canister.ReleasePressure); } } DirtyUI(uid, canister, nodeContainer, containerManager); // If last pressure is very close to the current pressure, do nothing. if (MathHelper.CloseToPercent(canister.Air.Pressure, canister.LastPressure)) return; canister.LastPressure = canister.Air.Pressure; if (canister.Air.Pressure < 10) { appearance.SetData(GasCanisterVisuals.PressureState, 0); } else if (canister.Air.Pressure < Atmospherics.OneAtmosphere) { appearance.SetData(GasCanisterVisuals.PressureState, 1); } else if (canister.Air.Pressure < (15 * Atmospherics.OneAtmosphere)) { appearance.SetData(GasCanisterVisuals.PressureState, 2); } else { appearance.SetData(GasCanisterVisuals.PressureState, 3); } } private void OnCanisterActivate(EntityUid uid, GasCanisterComponent component, ActivateInWorldEvent args) { if (!EntityManager.TryGetComponent(args.User, out ActorComponent? actor)) return; _userInterfaceSystem.GetUiOrNull(uid, GasCanisterUiKey.Key)?.Open(actor.PlayerSession); args.Handled = true; } private void OnCanisterInteractHand(EntityUid uid, GasCanisterComponent component, InteractHandEvent args) { if (!EntityManager.TryGetComponent(args.User, out ActorComponent? actor)) return; _userInterfaceSystem.GetUiOrNull(uid, GasCanisterUiKey.Key)?.Open(actor.PlayerSession); args.Handled = true; } private void OnCanisterInteractUsing(EntityUid canister, GasCanisterComponent component, InteractUsingEvent args) { var container = canister.EnsureContainer(component.ContainerName); // Container full. if (container.ContainedEntity != null) return; // Check the used item is valid... if (!EntityManager.TryGetComponent(args.Used, out GasTankComponent? _)) return; // Check the user has hands. if (!EntityManager.TryGetComponent(args.User, out HandsComponent? hands)) return; if (!args.User.InRangeUnobstructed(canister, SharedInteractionSystem.InteractionRange, popup: true)) return; if (!hands.Drop(args.Used, container)) return; _adminLogSystem.Add(LogType.CanisterTankInserted, LogImpact.Medium, $"Player {ToPrettyString(args.User):player} inserted tank {ToPrettyString(container.ContainedEntities[0]):tank} into {ToPrettyString(canister):canister}"); args.Handled = true; } private void OnCanisterContainerInserted(EntityUid uid, GasCanisterComponent component, EntInsertedIntoContainerMessage args) { if (args.Container.ID != component.ContainerName) return; DirtyUI(uid, component); if (!EntityManager.TryGetComponent(uid, out AppearanceComponent? appearance)) return; appearance.SetData(GasCanisterVisuals.TankInserted, true); } private void OnCanisterContainerRemoved(EntityUid uid, GasCanisterComponent component, EntRemovedFromContainerMessage args) { if (args.Container.ID != component.ContainerName) return; DirtyUI(uid, component); if (!EntityManager.TryGetComponent(uid, out AppearanceComponent? appearance)) return; appearance.SetData(GasCanisterVisuals.TankInserted, false); } } }