]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Greatly improve the usability of the Gas Analyzer. (#30763)
authorMervill <mervills.email@gmail.com>
Thu, 15 Aug 2024 14:45:13 +0000 (07:45 -0700)
committerGitHub <noreply@github.com>
Thu, 15 Aug 2024 14:45:13 +0000 (10:45 -0400)
* greatly improve how the gas analyzer behaves

* don't close the analyzer when the object goes out of range

* cleanup

* always switch to the device tab when a new device is analyzed

* modern api part one

* modern api part 2

* modern api part three

* file scope namespace

Content.Client/Atmos/UI/GasAnalyzerWindow.xaml.cs
Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs
Content.Server/Singularity/EntitySystems/RadiationCollectorSystem.cs
Content.Shared/Atmos/Components/GasAnalyzerComponent.cs
Resources/Locale/en-US/atmos/gas-analyzer-component.ftl

index b54af3a5871f0a1001580b95d7b28fb9a3915762..bb24da44e132abc5908cfe11ac389931c170dc9a 100644 (file)
@@ -16,6 +16,8 @@ namespace Content.Client.Atmos.UI
     [GenerateTypedNameReferences]
     public sealed partial class GasAnalyzerWindow : DefaultWindow
     {
+        private NetEntity _currentEntity = NetEntity.Invalid;
+
         public GasAnalyzerWindow()
         {
             RobustXamlLoader.Load(this);
@@ -55,6 +57,13 @@ namespace Content.Client.Atmos.UI
             // Device Tab
             if (msg.NodeGasMixes.Length > 1)
             {
+                if (_currentEntity != msg.DeviceUid)
+                {
+                    // when we get new device data switch to the device tab
+                    CTabContainer.CurrentTab = 0;
+                    _currentEntity = msg.DeviceUid;
+                }
+
                 CTabContainer.SetTabVisible(0, true);
                 CTabContainer.SetTabTitle(0, Loc.GetString("gas-analyzer-window-tab-title-capitalized", ("title", msg.DeviceName)));
                 // Set up Grid
@@ -143,6 +152,7 @@ namespace Content.Client.Atmos.UI
                 CTabContainer.SetTabVisible(0, false);
                 CTabContainer.CurrentTab = 1;
                 minSize = new Vector2(CEnvironmentMix.DesiredSize.X + 40, MinSize.Y);
+                _currentEntity = NetEntity.Invalid;
             }
 
             MinSize = minSize;
index 0f4490cd7ebad868c370ded20ed8f821225e9477..81f0b96d0255adcc4e96595569a8d4a8f0b091f6 100644 (file)
@@ -1,5 +1,4 @@
 using System.Linq;
-using Content.Server.Atmos;
 using Content.Server.Atmos.Components;
 using Content.Server.NodeContainer;
 using Content.Server.NodeContainer.Nodes;
@@ -10,274 +9,266 @@ using Content.Shared.Interaction;
 using Content.Shared.Interaction.Events;
 using JetBrains.Annotations;
 using Robust.Server.GameObjects;
-using Robust.Shared.Player;
 using static Content.Shared.Atmos.Components.GasAnalyzerComponent;
 
-namespace Content.Server.Atmos.EntitySystems
+namespace Content.Server.Atmos.EntitySystems;
+
+[UsedImplicitly]
+public sealed class GasAnalyzerSystem : EntitySystem
 {
-    [UsedImplicitly]
-    public sealed class GasAnalyzerSystem : EntitySystem
+    [Dependency] private readonly PopupSystem _popup = default!;
+    [Dependency] private readonly AtmosphereSystem _atmo = default!;
+    [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+    [Dependency] private readonly UserInterfaceSystem _userInterface = default!;
+    [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
+
+    /// <summary>
+    /// Minimum moles of a gas to be sent to the client.
+    /// </summary>
+    private const float UIMinMoles = 0.01f;
+
+    public override void Initialize()
     {
-        [Dependency] private readonly PopupSystem _popup = default!;
-        [Dependency] private readonly AtmosphereSystem _atmo = default!;
-        [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
-        [Dependency] private readonly UserInterfaceSystem _userInterface = default!;
-        [Dependency] private readonly TransformSystem _transform = default!;
-
-        /// <summary>
-        /// Minimum moles of a gas to be sent to the client.
-        /// </summary>
-        private const float UIMinMoles = 0.01f;
-
-        public override void Initialize()
-        {
-            base.Initialize();
+        base.Initialize();
 
-            SubscribeLocalEvent<GasAnalyzerComponent, AfterInteractEvent>(OnAfterInteract);
-            SubscribeLocalEvent<GasAnalyzerComponent, GasAnalyzerDisableMessage>(OnDisabledMessage);
-            SubscribeLocalEvent<GasAnalyzerComponent, DroppedEvent>(OnDropped);
-            SubscribeLocalEvent<GasAnalyzerComponent, UseInHandEvent>(OnUseInHand);
-        }
+        SubscribeLocalEvent<GasAnalyzerComponent, AfterInteractEvent>(OnAfterInteract);
+        SubscribeLocalEvent<GasAnalyzerComponent, GasAnalyzerDisableMessage>(OnDisabledMessage);
+        SubscribeLocalEvent<GasAnalyzerComponent, DroppedEvent>(OnDropped);
+        SubscribeLocalEvent<GasAnalyzerComponent, UseInHandEvent>(OnUseInHand);
+    }
 
-        public override void Update(float frameTime)
+    public override void Update(float frameTime)
+    {
+        var query = EntityQueryEnumerator<ActiveGasAnalyzerComponent>();
+        while (query.MoveNext(out var uid, out var analyzer))
         {
-            var query = EntityQueryEnumerator<ActiveGasAnalyzerComponent>();
-            while (query.MoveNext(out var uid, out var analyzer))
-            {
-                // Don't update every tick
-                analyzer.AccumulatedFrametime += frameTime;
+            // Don't update every tick
+            analyzer.AccumulatedFrametime += frameTime;
 
-                if (analyzer.AccumulatedFrametime < analyzer.UpdateInterval)
-                    continue;
+            if (analyzer.AccumulatedFrametime < analyzer.UpdateInterval)
+                continue;
 
-                analyzer.AccumulatedFrametime -= analyzer.UpdateInterval;
+            analyzer.AccumulatedFrametime -= analyzer.UpdateInterval;
 
-                if (!UpdateAnalyzer(uid))
-                    RemCompDeferred<ActiveGasAnalyzerComponent>(uid);
-            }
+            if (!UpdateAnalyzer(uid))
+                RemCompDeferred<ActiveGasAnalyzerComponent>(uid);
         }
+    }
 
-        /// <summary>
-        /// Activates the analyzer when used in the world, scanning either the target entity or the tile clicked
-        /// </summary>
-        private void OnAfterInteract(EntityUid uid, GasAnalyzerComponent component, AfterInteractEvent args)
+    /// <summary>
+    /// Activates the analyzer when used in the world, scanning the target entity (if it exists) and the tile the analyzer is in
+    /// </summary>
+    private void OnAfterInteract(Entity<GasAnalyzerComponent> entity, ref AfterInteractEvent args)
+    {
+        var target = args.Target;
+        if (target != null && !_interactionSystem.InRangeUnobstructed((args.User, null), (target.Value, null)))
         {
-            if (!args.CanReach)
-            {
-                _popup.PopupEntity(Loc.GetString("gas-analyzer-component-player-cannot-reach-message"), args.User, args.User);
-                return;
-            }
-            ActivateAnalyzer(uid, component, args.User, args.Target);
-            args.Handled = true;
+            target = null; // if the target is out of reach, invalidate it
         }
+        // always run the analyzer, regardless of weather or not there is a target
+        // since we can always show the local environment.
+        ActivateAnalyzer(entity, args.User, target);
+        args.Handled = true;
+    }
 
-        /// <summary>
-        /// Activates the analyzer with no target, so it only scans the tile the user was on when activated
-        /// </summary>
-        private void OnUseInHand(EntityUid uid, GasAnalyzerComponent component, UseInHandEvent args)
+    /// <summary>
+    /// Activates the analyzer with no target, so it only scans the tile the user was on when activated
+    /// </summary>
+    private void OnUseInHand(Entity<GasAnalyzerComponent> entity, ref UseInHandEvent args)
+    {
+        if (!entity.Comp.Enabled)
         {
-            ActivateAnalyzer(uid, component, args.User);
-            args.Handled = true;
+            ActivateAnalyzer(entity, args.User);
         }
-
-        /// <summary>
-        /// Handles analyzer activation logic
-        /// </summary>
-        private void ActivateAnalyzer(EntityUid uid, GasAnalyzerComponent component, EntityUid user, EntityUid? target = null)
+        else
         {
-            if (!TryOpenUserInterface(uid, user, component))
-                return;
-
-            component.Target = target;
-            component.User = user;
-            if (target != null)
-                component.LastPosition = Transform(target.Value).Coordinates;
-            else
-                component.LastPosition = null;
-            component.Enabled = true;
-            Dirty(uid, component);
-            UpdateAppearance(uid, component);
-            EnsureComp<ActiveGasAnalyzerComponent>(uid);
-            UpdateAnalyzer(uid, component);
+            DisableAnalyzer(entity, args.User);
         }
+        args.Handled = true;
+    }
 
-        /// <summary>
-        /// Close the UI, turn the analyzer off, and don't update when it's dropped
-        /// </summary>
-        private void OnDropped(EntityUid uid, GasAnalyzerComponent component, DroppedEvent args)
-        {
-            if (args.User is var userId && component.Enabled)
-                _popup.PopupEntity(Loc.GetString("gas-analyzer-shutoff"), userId, userId);
-            DisableAnalyzer(uid, component, args.User);
-        }
+    /// <summary>
+    /// Handles analyzer activation logic
+    /// </summary>
+    private void ActivateAnalyzer(Entity<GasAnalyzerComponent> entity, EntityUid user, EntityUid? target = null)
+    {
+        if (!_userInterface.TryOpenUi(entity.Owner, GasAnalyzerUiKey.Key, user))
+            return;
+
+        entity.Comp.Target = target;
+        entity.Comp.User = user;
+        entity.Comp.Enabled = true;
+        Dirty(entity);
+        _appearance.SetData(entity.Owner, GasAnalyzerVisuals.Enabled, entity.Comp.Enabled);
+        EnsureComp<ActiveGasAnalyzerComponent>(entity.Owner);
+        UpdateAnalyzer(entity.Owner, entity.Comp);
+    }
 
-        /// <summary>
-        /// Closes the UI, sets the icon to off, and removes it from the update list
-        /// </summary>
-        private void DisableAnalyzer(EntityUid uid, GasAnalyzerComponent? component = null, EntityUid? user = null)
-        {
-            if (!Resolve(uid, ref component))
-                return;
+    /// <summary>
+    /// Close the UI, turn the analyzer off, and don't update when it's dropped
+    /// </summary>
+    private void OnDropped(Entity<GasAnalyzerComponent> entity, ref DroppedEvent args)
+    {
+        if (args.User is var userId && entity.Comp.Enabled)
+            _popup.PopupEntity(Loc.GetString("gas-analyzer-shutoff"), userId, userId);
+        DisableAnalyzer(entity, args.User);
+    }
+
+    /// <summary>
+    /// Closes the UI, sets the icon to off, and removes it from the update list
+    /// </summary>
+    private void DisableAnalyzer(Entity<GasAnalyzerComponent> entity, EntityUid? user = null)
+    {
+        _userInterface.CloseUi(entity.Owner, GasAnalyzerUiKey.Key, user);
 
-            _userInterface.CloseUi(uid, GasAnalyzerUiKey.Key, user);
+        entity.Comp.Enabled = false;
+        Dirty(entity);
+        _appearance.SetData(entity.Owner, GasAnalyzerVisuals.Enabled, entity.Comp.Enabled);
+        RemCompDeferred<ActiveGasAnalyzerComponent>(entity.Owner);
+    }
 
-            component.Enabled = false;
-            Dirty(uid, component);
-            UpdateAppearance(uid, component);
-            RemCompDeferred<ActiveGasAnalyzerComponent>(uid);
-        }
+    /// <summary>
+    /// Disables the analyzer when the user closes the UI
+    /// </summary>
+    private void OnDisabledMessage(Entity<GasAnalyzerComponent> entity, ref GasAnalyzerDisableMessage message)
+    {
+        DisableAnalyzer(entity);
+    }
 
-        /// <summary>
-        /// Disables the analyzer when the user closes the UI
-        /// </summary>
-        private void OnDisabledMessage(EntityUid uid, GasAnalyzerComponent component, GasAnalyzerDisableMessage message)
-        {
-            DisableAnalyzer(uid, component);
-        }
+    /// <summary>
+    /// Fetches fresh data for the analyzer. Should only be called by Update or when the user requests an update via refresh button
+    /// </summary>
+    private bool UpdateAnalyzer(EntityUid uid, GasAnalyzerComponent? component = null)
+    {
+        if (!Resolve(uid, ref component))
+            return false;
 
-        private bool TryOpenUserInterface(EntityUid uid, EntityUid user, GasAnalyzerComponent? component = null)
+        // check if the user has walked away from what they scanned
+        if (component.Target.HasValue)
         {
-            if (!Resolve(uid, ref component, false))
-                return false;
+            // Listen! Even if you don't want the Gas Analyzer to work on moving targets, you should use
+            // this code to determine if the object is still generally in range so that the check is consistent with the code
+            // in OnAfterInteract() and also consistent with interaction code in general.
+            if (!_interactionSystem.InRangeUnobstructed((component.User, null), (component.Target.Value, null)))
+            {
+                if (component.User is { } userId && component.Enabled)
+                    _popup.PopupEntity(Loc.GetString("gas-analyzer-object-out-of-range"), userId, userId);
 
-            return _userInterface.TryOpenUi(uid, GasAnalyzerUiKey.Key, user);
+                component.Target = null;
+            }
         }
 
-        /// <summary>
-        /// Fetches fresh data for the analyzer. Should only be called by Update or when the user requests an update via refresh button
-        /// </summary>
-        private bool UpdateAnalyzer(EntityUid uid, GasAnalyzerComponent? component = null)
+        var gasMixList = new List<GasMixEntry>();
+
+        // Fetch the environmental atmosphere around the scanner. This must be the first entry
+        var tileMixture = _atmo.GetContainingMixture(uid, true);
+        if (tileMixture != null)
         {
-            if (!Resolve(uid, ref component))
-                return false;
+            gasMixList.Add(new GasMixEntry(Loc.GetString("gas-analyzer-window-environment-tab-label"), tileMixture.Volume, tileMixture.Pressure, tileMixture.Temperature,
+                GenerateGasEntryArray(tileMixture)));
+        }
+        else
+        {
+            // No gases were found
+            gasMixList.Add(new GasMixEntry(Loc.GetString("gas-analyzer-window-environment-tab-label"), 0f, 0f, 0f));
+        }
 
-            if (!TryComp(component.User, out TransformComponent? xform))
+        var deviceFlipped = false;
+        if (component.Target != null)
+        {
+            if (Deleted(component.Target))
             {
-                DisableAnalyzer(uid, component);
+                component.Target = null;
+                DisableAnalyzer((uid, component), component.User);
                 return false;
             }
 
-            // check if the user has walked away from what they scanned
-            var userPos = xform.Coordinates;
-            if (component.LastPosition.HasValue)
-            {
-                // Check if position is out of range => don't update and disable
-                if (!_transform.InRange(component.LastPosition.Value, userPos, SharedInteractionSystem.InteractionRange))
-                {
-                    if (component.User is { } userId && component.Enabled)
-                        _popup.PopupEntity(Loc.GetString("gas-analyzer-shutoff"), userId, userId);
-                    DisableAnalyzer(uid, component, component.User);
-                    return false;
-                }
-            }
-
-            var gasMixList = new List<GasMixEntry>();
+            var validTarget = false;
 
-            // Fetch the environmental atmosphere around the scanner. This must be the first entry
-            var tileMixture = _atmo.GetContainingMixture(uid, true);
-            if (tileMixture != null)
-            {
-                gasMixList.Add(new GasMixEntry(Loc.GetString("gas-analyzer-window-environment-tab-label"), tileMixture.Volume, tileMixture.Pressure, tileMixture.Temperature,
-                    GenerateGasEntryArray(tileMixture)));
-            }
-            else
-            {
-                // No gases were found
-                gasMixList.Add(new GasMixEntry(Loc.GetString("gas-analyzer-window-environment-tab-label"), 0f, 0f, 0f));
-            }
+            // gas analyzed was used on an entity, try to request gas data via event for override
+            var ev = new GasAnalyzerScanEvent();
+            RaiseLocalEvent(component.Target.Value, ev);
 
-            var deviceFlipped = false;
-            if (component.Target != null)
+            if (ev.GasMixtures != null)
             {
-                if (Deleted(component.Target))
-                {
-                    component.Target = null;
-                    DisableAnalyzer(uid, component, component.User);
-                    return false;
-                }
-
-                // gas analyzed was used on an entity, try to request gas data via event for override
-                var ev = new GasAnalyzerScanEvent();
-                RaiseLocalEvent(component.Target.Value, ev);
-
-                if (ev.GasMixtures != null)
+                foreach (var mixes in ev.GasMixtures)
                 {
-                    foreach (var mixes in ev.GasMixtures)
+                    if (mixes.Item2 != null)
                     {
-                        if (mixes.Item2 != null)
-                            gasMixList.Add(new GasMixEntry(mixes.Item1, mixes.Item2.Volume, mixes.Item2.Pressure, mixes.Item2.Temperature, GenerateGasEntryArray(mixes.Item2)));
+                        gasMixList.Add(new GasMixEntry(mixes.Item1, mixes.Item2.Volume, mixes.Item2.Pressure, mixes.Item2.Temperature, GenerateGasEntryArray(mixes.Item2)));
+                        validTarget = true;
                     }
-
-                    deviceFlipped = ev.DeviceFlipped;
                 }
-                else
+
+                deviceFlipped = ev.DeviceFlipped;
+            }
+            else
+            {
+                // No override, fetch manually, to handle flippable devices you must subscribe to GasAnalyzerScanEvent
+                if (TryComp(component.Target, out NodeContainerComponent? node))
                 {
-                    // No override, fetch manually, to handle flippable devices you must subscribe to GasAnalyzerScanEvent
-                    if (TryComp(component.Target, out NodeContainerComponent? node))
+                    foreach (var pair in node.Nodes)
                     {
-                        foreach (var pair in node.Nodes)
+                        if (pair.Value is PipeNode pipeNode)
                         {
-                            if (pair.Value is PipeNode pipeNode)
-                            {
-                                // check if the volume is zero for some reason so we don't divide by zero
-                                if (pipeNode.Air.Volume == 0f)
-                                    continue;
-                                // only display the gas in the analyzed pipe element, not the whole system
-                                var pipeAir = pipeNode.Air.Clone();
-                                pipeAir.Multiply(pipeNode.Volume / pipeNode.Air.Volume);
-                                pipeAir.Volume = pipeNode.Volume;
-                                gasMixList.Add(new GasMixEntry(pair.Key, pipeAir.Volume, pipeAir.Pressure, pipeAir.Temperature, GenerateGasEntryArray(pipeAir)));
-                            }
+                            // check if the volume is zero for some reason so we don't divide by zero
+                            if (pipeNode.Air.Volume == 0f)
+                                continue;
+                            // only display the gas in the analyzed pipe element, not the whole system
+                            var pipeAir = pipeNode.Air.Clone();
+                            pipeAir.Multiply(pipeNode.Volume / pipeNode.Air.Volume);
+                            pipeAir.Volume = pipeNode.Volume;
+                            gasMixList.Add(new GasMixEntry(pair.Key, pipeAir.Volume, pipeAir.Pressure, pipeAir.Temperature, GenerateGasEntryArray(pipeAir)));
+                            validTarget = true;
                         }
                     }
                 }
             }
 
-            // Don't bother sending a UI message with no content, and stop updating I guess?
-            if (gasMixList.Count == 0)
-                return false;
-
-            _userInterface.ServerSendUiMessage(uid, GasAnalyzerUiKey.Key,
-                new GasAnalyzerUserMessage(gasMixList.ToArray(),
-                    component.Target != null ? Name(component.Target.Value) : string.Empty,
-                    GetNetEntity(component.Target) ?? NetEntity.Invalid,
-                    deviceFlipped));
-            return true;
+            // If the target doesn't actually have any gas mixes to add,
+            // invalidate it as the target
+            if (!validTarget)
+            {
+                component.Target = null;
+            }
         }
 
-        /// <summary>
-        /// Sets the appearance based on the analyzers Enabled state
-        /// </summary>
-        private void UpdateAppearance(EntityUid uid, GasAnalyzerComponent analyzer)
-        {
-            _appearance.SetData(uid, GasAnalyzerVisuals.Enabled, analyzer.Enabled);
-        }
+        // Don't bother sending a UI message with no content, and stop updating I guess?
+        if (gasMixList.Count == 0)
+            return false;
 
-        /// <summary>
-        /// Generates a GasEntry array for a given GasMixture
-        /// </summary>
-        private GasEntry[] GenerateGasEntryArray(GasMixture? mixture)
-        {
-            var gases = new List<GasEntry>();
+        _userInterface.ServerSendUiMessage(uid, GasAnalyzerUiKey.Key,
+            new GasAnalyzerUserMessage(gasMixList.ToArray(),
+                component.Target != null ? Name(component.Target.Value) : string.Empty,
+                GetNetEntity(component.Target) ?? NetEntity.Invalid,
+                deviceFlipped));
+        return true;
+    }
 
-            for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
-            {
-                var gas = _atmo.GetGas(i);
+    /// <summary>
+    /// Generates a GasEntry array for a given GasMixture
+    /// </summary>
+    private GasEntry[] GenerateGasEntryArray(GasMixture? mixture)
+    {
+        var gases = new List<GasEntry>();
+
+        for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
+        {
+            var gas = _atmo.GetGas(i);
 
-                if (mixture?[i] <= UIMinMoles)
-                    continue;
+            if (mixture?[i] <= UIMinMoles)
+                continue;
 
-                if (mixture != null)
-                {
-                    var gasName = Loc.GetString(gas.Name);
-                    gases.Add(new GasEntry(gasName, mixture[i], gas.Color));
-                }
+            if (mixture != null)
+            {
+                var gasName = Loc.GetString(gas.Name);
+                gases.Add(new GasEntry(gasName, mixture[i], gas.Color));
             }
+        }
 
-            var gasesOrdered = gases.OrderByDescending(gas => gas.Amount);
+        var gasesOrdered = gases.OrderByDescending(gas => gas.Amount);
 
-            return gasesOrdered.ToArray();
-        }
+        return gasesOrdered.ToArray();
     }
 }
 
index c262988c869dfeafa8b997fdf91115219723932e..3fab18b1b75b26b279f1a41a2a5ad7c84762f79d 100644 (file)
@@ -1,6 +1,6 @@
 using System.Diagnostics.CodeAnalysis;
 using System.Linq;
-using Content.Server.Atmos;
+using Content.Server.Atmos.EntitySystems;
 using Content.Server.Atmos.Components;
 using Content.Server.Popups;
 using Content.Server.Power.Components;
index dec9516c0136f5052dc2642ede42bff0152cc14d..c143e8cf85130557f7b6429cdefb1193b0d778f3 100644 (file)
@@ -13,9 +13,6 @@ public sealed partial class GasAnalyzerComponent : Component
     [ViewVariables]
     public EntityUid User;
 
-    [ViewVariables(VVAccess.ReadWrite)]
-    public EntityCoordinates? LastPosition;
-
     [DataField("enabled"), ViewVariables(VVAccess.ReadWrite)]
     public bool Enabled;
 
index 652bb19cb5b078c081d84aa9cf9aed7bd31e6dac..a2cb5301b2154483665f6dd03429b6e15644bdf7 100644 (file)
@@ -1,6 +1,6 @@
 ## Entity
 
-gas-analyzer-component-player-cannot-reach-message = You can't reach there.
+gas-analyzer-object-out-of-range = The object went out of range.
 gas-analyzer-shutoff = The gas analyzer shuts off.
 
 ## UI