#nullable enable using System.Collections.Generic; using System.Threading.Tasks; using Content.Server.Atmos.EntitySystems; using Content.Server.Hands.Components; using Content.Server.UserInterface; using Content.Shared.Atmos; using Content.Shared.Atmos.Components; using Content.Shared.DragDrop; using Content.Shared.Interaction; using Content.Shared.Notification.Managers; using Robust.Server.GameObjects; using Robust.Server.Player; using Robust.Shared.GameObjects; using Robust.Shared.Localization; using Robust.Shared.Map; using Robust.Shared.Players; using Robust.Shared.ViewVariables; namespace Content.Server.Atmos.Components { [RegisterComponent] public class GasAnalyzerComponent : SharedGasAnalyzerComponent, IAfterInteract, IDropped, IUse { private GasAnalyzerDanger _pressureDanger; private float _timeSinceSync; private const float TimeBetweenSyncs = 2f; private bool _checkPlayer = false; // Check at the player pos or at some other tile? private EntityCoordinates? _position; // The tile that we scanned private AppearanceComponent? _appearance; [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(GasAnalyzerUiKey.Key); public override void Initialize() { base.Initialize(); if (UserInterface != null) { UserInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage; UserInterface.OnClosed += UserInterfaceOnClose; } Owner.TryGetComponent(out _appearance); } public override ComponentState GetComponentState(ICommonSession player) { return new GasAnalyzerComponentState(_pressureDanger); } /// /// Call this from other components to open the gas analyzer UI. /// Uses the player position. /// /// The session to open the ui for public void OpenInterface(IPlayerSession session) { _checkPlayer = true; _position = null; UserInterface?.Open(session); UpdateUserInterface(); UpdateAppearance(true); Resync(); } /// /// Call this from other components to open the gas analyzer UI. /// Uses a given position. /// /// The session to open the ui for /// The position to analyze the gas public void OpenInterface(IPlayerSession session, EntityCoordinates pos) { _checkPlayer = false; _position = pos; UserInterface?.Open(session); UpdateUserInterface(); UpdateAppearance(true); Resync(); } public void ToggleInterface(IPlayerSession session) { if (UserInterface == null) return; if (UserInterface.SessionHasOpen(session)) CloseInterface(session); else OpenInterface(session); } public void CloseInterface(IPlayerSession session) { _position = null; UserInterface?.Close(session); // Our OnClose will do the appearance stuff Resync(); } private void UserInterfaceOnClose(IPlayerSession obj) { UpdateAppearance(false); } private void UpdateAppearance(bool open) { _appearance?.SetData(GasAnalyzerVisuals.VisualState, open ? GasAnalyzerVisualState.Working : GasAnalyzerVisualState.Off); } public void Update(float frameTime) { _timeSinceSync += frameTime; if (_timeSinceSync > TimeBetweenSyncs) { Resync(); UpdateUserInterface(); } } private void Resync() { // Already get the pressure before Dirty(), because we can't get the EntitySystem in that thread or smth var pressure = 0f; var gam = EntitySystem.Get().GetGridAtmosphere(Owner.Transform.Coordinates); var tile = gam?.GetTile(Owner.Transform.Coordinates)?.Air; if (tile != null) { pressure = tile.Pressure; } if (pressure >= Atmospherics.HazardHighPressure || pressure <= Atmospherics.HazardLowPressure) { _pressureDanger = GasAnalyzerDanger.Hazard; } else if (pressure >= Atmospherics.WarningHighPressure || pressure <= Atmospherics.WarningLowPressure) { _pressureDanger = GasAnalyzerDanger.Warning; } else { _pressureDanger = GasAnalyzerDanger.Nominal; } Dirty(); _timeSinceSync = 0f; } private void UpdateUserInterface() { if (UserInterface == null) { return; } string? error = null; // Check if the player is still holding the gas analyzer => if not, don't update foreach (var session in UserInterface.SubscribedSessions) { if (session.AttachedEntity == null) return; if (!session.AttachedEntity.TryGetComponent(out IHandsComponent? handsComponent)) return; var activeHandEntity = handsComponent?.GetActiveHand?.Owner; if (activeHandEntity == null || !activeHandEntity.TryGetComponent(out GasAnalyzerComponent? gasAnalyzer)) { return; } } var pos = Owner.Transform.Coordinates; if (!_checkPlayer && _position.HasValue) { // Check if position is out of range => don't update if (!_position.Value.InRange(Owner.EntityManager, pos, SharedInteractionSystem.InteractionRange)) return; pos = _position.Value; } var atmosSystem = EntitySystem.Get(); var gam = atmosSystem.GetGridAtmosphere(pos); var tile = gam.GetTile(pos)?.Air; if (tile == null) { error = "No Atmosphere!"; UserInterface.SetState( new GasAnalyzerBoundUserInterfaceState( 0, 0, null, error)); return; } var gases = new List(); for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++) { var gas = atmosSystem.GetGas(i); if (tile.Gases[i] <= Atmospherics.GasMinMoles) continue; gases.Add(new GasEntry(gas.Name, tile.Gases[i], gas.Color)); } UserInterface.SetState( new GasAnalyzerBoundUserInterfaceState( tile.Pressure, tile.Temperature, gases.ToArray(), error)); } private void UserInterfaceOnReceiveMessage(ServerBoundUserInterfaceMessage serverMsg) { var message = serverMsg.Message; switch (message) { case GasAnalyzerRefreshMessage msg: var player = serverMsg.Session.AttachedEntity; if (player == null) { return; } if (!player.TryGetComponent(out IHandsComponent? handsComponent)) { Owner.PopupMessage(player, Loc.GetString("You have no hands.")); return; } var activeHandEntity = handsComponent.GetActiveHand?.Owner; if (activeHandEntity == null || !activeHandEntity.TryGetComponent(out GasAnalyzerComponent? gasAnalyzer)) { serverMsg.Session.AttachedEntity?.PopupMessage(Loc.GetString("You need a Gas Analyzer in your hand!")); return; } UpdateUserInterface(); Resync(); break; } } async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) { if (!eventArgs.CanReach) { eventArgs.User.PopupMessage(Loc.GetString("You can't reach there!")); return true; } if (eventArgs.User.TryGetComponent(out ActorComponent? actor)) { OpenInterface(actor.PlayerSession, eventArgs.ClickLocation); } return true; } void IDropped.Dropped(DroppedEventArgs eventArgs) { if (eventArgs.User.TryGetComponent(out ActorComponent? actor)) { CloseInterface(actor.PlayerSession); } } bool IUse.UseEntity(UseEntityEventArgs eventArgs) { if (eventArgs.User.TryGetComponent(out ActorComponent? actor)) { ToggleInterface(actor.PlayerSession); return true; } return false; } } }