]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Predict gasthermomachines (#33837)
authormetalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Tue, 13 May 2025 11:49:43 +0000 (21:49 +1000)
committerGitHub <noreply@github.com>
Tue, 13 May 2025 11:49:43 +0000 (21:49 +1000)
* Predict gasthermomachines

* despawn

* smellby

16 files changed:
Content.Client/Atmos/Piping/Unary/Systems/GasThermoMachineSystem.cs [new file with mode: 0644]
Content.Client/Atmos/UI/GasThermomachineBoundUserInterface.cs
Content.Client/Atmos/UI/GasThermomachineWindow.xaml
Content.Client/Atmos/UI/GasThermomachineWindow.xaml.cs
Content.Client/NodeContainer/NodeContainerSystem.cs [new file with mode: 0644]
Content.Client/Power/EntitySystems/PowerNetSystem.cs
Content.Server/Atmos/Piping/Unary/EntitySystems/GasThermoMachineSystem.cs
Content.Server/Atmos/Portable/SpaceHeaterSystem.cs
Content.Server/NodeContainer/EntitySystems/NodeContainerSystem.cs
Content.Server/Power/EntitySystems/PowerNetSystem.cs
Content.Shared/Atmos/Piping/Unary/Components/GasThermoMachineComponent.cs [moved from Content.Server/Atmos/Piping/Unary/Components/GasThermoMachineComponent.cs with 77% similarity]
Content.Shared/Atmos/Piping/Unary/Components/SharedGasThermomachineComponent.cs
Content.Shared/Atmos/Piping/Unary/Systems/SharedGasThermoMachineSystem.cs [new file with mode: 0644]
Content.Shared/NodeContainer/SharedNodeContainerSystem.cs [new file with mode: 0644]
Content.Shared/Power/EntitySystems/SharedPowerNetSystem.cs
Content.Shared/Power/EntitySystems/SharedPowerReceiverSystem.cs

diff --git a/Content.Client/Atmos/Piping/Unary/Systems/GasThermoMachineSystem.cs b/Content.Client/Atmos/Piping/Unary/Systems/GasThermoMachineSystem.cs
new file mode 100644 (file)
index 0000000..bd75fa0
--- /dev/null
@@ -0,0 +1,29 @@
+using Content.Client.Atmos.UI;
+using Content.Shared.Atmos.Piping.Unary.Components;
+using Content.Shared.Atmos.Piping.Unary.Systems;
+
+namespace Content.Client.Atmos.Piping.Unary.Systems;
+
+public sealed class GasThermoMachineSystem : SharedGasThermoMachineSystem
+{
+    [Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+        SubscribeLocalEvent<GasThermoMachineComponent, AfterAutoHandleStateEvent>(OnGasAfterState);
+    }
+
+    private void OnGasAfterState(Entity<GasThermoMachineComponent> ent, ref AfterAutoHandleStateEvent args)
+    {
+        DirtyUI(ent.Owner, ent.Comp);
+    }
+
+    protected override void DirtyUI(EntityUid uid, GasThermoMachineComponent? thermoMachine, UserInterfaceComponent? ui = null)
+    {
+        if (_ui.TryGetOpenUi<GasThermomachineBoundUserInterface>(uid, ThermomachineUiKey.Key, out var bui))
+        {
+            bui.Update();
+        }
+    }
+}
index d62be8f4bb4243ba130c48f9ddf7b03367e7e2ef..f9e1caf8f3f6614ba97caddf3cf72ff50a9a27fc 100644 (file)
@@ -1,5 +1,8 @@
-using Content.Shared.Atmos;
+using Content.Client.Power.EntitySystems;
+using Content.Shared.Atmos;
 using Content.Shared.Atmos.Piping.Unary.Components;
+using Content.Shared.Atmos.Piping.Unary.Systems;
+using Content.Shared.Power.Components;
 using JetBrains.Annotations;
 using Robust.Client.GameObjects;
 using Robust.Client.UserInterface;
@@ -36,6 +39,8 @@ namespace Content.Client.Atmos.UI
 
             _window.ToggleStatusButton.OnPressed += _ => OnToggleStatusButtonPressed();
             _window.TemperatureSpinbox.OnValueChanged += _ => OnTemperatureChanged(_window.TemperatureSpinbox.Value);
+            _window.Entity = Owner;
+            Update();
         }
 
         private void OnToggleStatusButtonPressed()
@@ -43,7 +48,7 @@ namespace Content.Client.Atmos.UI
             if (_window is null) return;
 
             _window.SetActive(!_window.Active);
-            SendMessage(new GasThermomachineToggleMessage());
+            SendPredictedMessage(new GasThermomachineToggleMessage());
         }
 
         private void OnTemperatureChanged(float value)
@@ -60,25 +65,32 @@ namespace Content.Client.Atmos.UI
                 return;
             }
 
-            SendMessage(new GasThermomachineChangeTemperatureMessage(actual));
+            SendPredictedMessage(new GasThermomachineChangeTemperatureMessage(actual));
         }
 
-        /// <summary>
-        /// Update the UI state based on server-sent info
-        /// </summary>
-        /// <param name="state"></param>
-        protected override void UpdateState(BoundUserInterfaceState state)
+        public override void Update()
         {
-            base.UpdateState(state);
-            if (_window == null || state is not GasThermomachineBoundUserInterfaceState cast)
+            if (_window == null || !EntMan.TryGetComponent(Owner, out GasThermoMachineComponent? thermo))
                 return;
 
-            _minTemp = cast.MinTemperature;
-            _maxTemp = cast.MaxTemperature;
-            _isHeater = cast.IsHeater;
+            var system = EntMan.System<SharedGasThermoMachineSystem>();
+            _minTemp = thermo.MinTemperature;
+            _maxTemp = thermo.MaxTemperature;
+            _isHeater = system.IsHeater(thermo);
+
+            _window.SetTemperature(thermo.TargetTemperature);
+
+            var receiverSys = EntMan.System<PowerReceiverSystem>();
+            SharedApcPowerReceiverComponent? receiver = null;
+
+            receiverSys.ResolveApc(Owner, ref receiver);
+
+            // Also set in frameupdates.
+            if (receiver != null)
+            {
+                _window.SetActive(!receiver.PowerDisabled);
+            }
 
-            _window.SetTemperature(cast.Temperature);
-            _window.SetActive(cast.Enabled);
             _window.Title = _isHeater switch
             {
                 false => Loc.GetString("comp-gas-thermomachine-ui-title-freezer"),
index de3f8f803618d95215085cf8aac13cdac3b02c92..207c31f08779b39a40a0e9f225f595232f129f81 100644 (file)
@@ -1,5 +1,6 @@
-<DefaultWindow xmlns="https://spacestation14.io"
+<controls:FancyWindow xmlns="https://spacestation14.io"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+               xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
                MinSize="300 120" Title="{Loc comp-gas-thermomachine-ui-title-freezer}">
     <BoxContainer Name="VboxContainer" Orientation="Vertical" Margin="5 5 5 5" SeparationOverride="10">
         <BoxContainer Orientation="Horizontal" HorizontalExpand="True">
@@ -11,4 +12,4 @@
             <Label Text="{Loc comp-gas-thermomachine-ui-temperature}"/>
         </BoxContainer>
     </BoxContainer>
-</DefaultWindow>
+</controls:FancyWindow>
index bc8cb143364fe521db408941486b38cf4630bd55..00d53dc9b664bcce5e9b5d7dcdbe79a7e87d2ec6 100644 (file)
@@ -1,19 +1,26 @@
+using Content.Client.Power.Components;
+using Content.Client.UserInterface.Controls;
 using Robust.Client.AutoGenerated;
 using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.CustomControls;
 using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Timing;
 
 namespace Content.Client.Atmos.UI;
 
 [GenerateTypedNameReferences]
-public sealed partial class GasThermomachineWindow : DefaultWindow
+public sealed partial class GasThermomachineWindow : FancyWindow
 {
+    [Dependency] private readonly IEntityManager _entManager = default!;
+
     public bool Active = true;
 
     public FloatSpinBox TemperatureSpinbox;
 
+    public EntityUid Entity;
+
     public GasThermomachineWindow()
     {
+        IoCManager.InjectDependencies(this);
         RobustXamlLoader.Load(this);
 
         SpinboxHBox.AddChild(
@@ -27,12 +34,10 @@ public sealed partial class GasThermomachineWindow : DefaultWindow
         if (active)
         {
             ToggleStatusButton.Text = Loc.GetString("comp-gas-thermomachine-ui-status-enabled");
-            ToggleStatusButton.Pressed = true;
         }
         else
         {
             ToggleStatusButton.Text = Loc.GetString("comp-gas-thermomachine-ui-status-disabled");
-            ToggleStatusButton.Pressed = false;
         }
     }
 
@@ -40,4 +45,14 @@ public sealed partial class GasThermomachineWindow : DefaultWindow
     {
         TemperatureSpinbox.Value = temperature;
     }
+
+    protected override void FrameUpdate(FrameEventArgs args)
+    {
+        base.FrameUpdate(args);
+
+        if (_entManager.TryGetComponent(Entity, out ApcPowerReceiverComponent? receiver))
+        {
+            SetActive(!receiver.PowerDisabled);
+        }
+    }
 }
diff --git a/Content.Client/NodeContainer/NodeContainerSystem.cs b/Content.Client/NodeContainer/NodeContainerSystem.cs
new file mode 100644 (file)
index 0000000..512aa8f
--- /dev/null
@@ -0,0 +1,5 @@
+using Content.Shared.NodeContainer;
+
+namespace Content.Client.NodeContainer;
+
+public sealed class NodeContainerSystem : SharedNodeContainerSystem;
index 6fb9f482ce974c201cdf7610483f3b8fb0b5152d..58ff03b28d139fe9fb62d8f60c3c35717875649e 100644 (file)
@@ -1,8 +1,19 @@
+using Content.Client.Power.Components;
+using Content.Shared.Power.Components;
 using Content.Shared.Power.EntitySystems;
 
 namespace Content.Client.Power.EntitySystems;
 
 public sealed class PowerNetSystem : SharedPowerNetSystem
 {
+    public override bool IsPoweredCalculate(SharedApcPowerReceiverComponent comp)
+    {
+        return IsPoweredCalculate((ApcPowerReceiverComponent)comp);
+    }
 
+    private bool IsPoweredCalculate(ApcPowerReceiverComponent comp)
+    {
+        return !comp.PowerDisabled
+               && !comp.NeedsPower;
+    }
 }
index e4f5f99ed73bb82e4d378d738fbd4ac6f07edaa6..35ff89c726268562171bba4520adee4de9fd7877 100644 (file)
@@ -9,11 +9,8 @@ using Content.Server.Power.Components;
 using Content.Shared.Atmos;
 using Content.Shared.Atmos.Piping.Unary.Components;
 using JetBrains.Annotations;
-using Robust.Server.GameObjects;
 using Content.Server.Power.EntitySystems;
-using Content.Shared.UserInterface;
-using Content.Shared.Administration.Logs;
-using Content.Shared.Database;
+using Content.Shared.Atmos.Piping.Unary.Systems;
 using Content.Shared.DeviceNetwork;
 using Content.Shared.DeviceNetwork.Events;
 using Content.Shared.Examine;
@@ -22,36 +19,23 @@ using Content.Shared.DeviceNetwork.Components;
 namespace Content.Server.Atmos.Piping.Unary.EntitySystems
 {
     [UsedImplicitly]
-    public sealed class GasThermoMachineSystem : EntitySystem
+    public sealed class GasThermoMachineSystem : SharedGasThermoMachineSystem
     {
         [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
-        [Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
         [Dependency] private readonly PowerReceiverSystem _power = default!;
         [Dependency] private readonly NodeContainerSystem _nodeContainer = default!;
         [Dependency] private readonly DeviceNetworkSystem _deviceNetwork = default!;
-        [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
 
         public override void Initialize()
         {
             base.Initialize();
 
             SubscribeLocalEvent<GasThermoMachineComponent, AtmosDeviceUpdateEvent>(OnThermoMachineUpdated);
-            SubscribeLocalEvent<GasThermoMachineComponent, ExaminedEvent>(OnExamined);
-
-            // UI events
-            SubscribeLocalEvent<GasThermoMachineComponent, BeforeActivatableUIOpenEvent>(OnBeforeOpened);
-            SubscribeLocalEvent<GasThermoMachineComponent, GasThermomachineToggleMessage>(OnToggleMessage);
-            SubscribeLocalEvent<GasThermoMachineComponent, GasThermomachineChangeTemperatureMessage>(OnChangeTemperature);
 
             // Device network
             SubscribeLocalEvent<GasThermoMachineComponent, DeviceNetworkPacketEvent>(OnPacketRecv);
         }
 
-        private void OnBeforeOpened(Entity<GasThermoMachineComponent> ent, ref BeforeActivatableUIOpenEvent args)
-        {
-            DirtyUI(ent, ent.Comp);
-        }
-
         private void OnThermoMachineUpdated(EntityUid uid, GasThermoMachineComponent thermoMachine, ref AtmosDeviceUpdateEvent args)
         {
             thermoMachine.LastEnergyDelta = 0f;
@@ -135,56 +119,6 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
             }
         }
 
-        private bool IsHeater(GasThermoMachineComponent comp)
-        {
-            return comp.Cp >= 0;
-        }
-
-        private void OnToggleMessage(EntityUid uid, GasThermoMachineComponent thermoMachine, GasThermomachineToggleMessage args)
-        {
-            var powerState = _power.TogglePower(uid);
-            _adminLogger.Add(LogType.AtmosPowerChanged, $"{ToPrettyString(args.Actor)} turned {(powerState ? "On" : "Off")} {ToPrettyString(uid)}");
-            DirtyUI(uid, thermoMachine);
-        }
-
-        private void OnChangeTemperature(EntityUid uid, GasThermoMachineComponent thermoMachine, GasThermomachineChangeTemperatureMessage args)
-        {
-            if (IsHeater(thermoMachine))
-                thermoMachine.TargetTemperature = MathF.Min(args.Temperature, thermoMachine.MaxTemperature);
-            else
-                thermoMachine.TargetTemperature = MathF.Max(args.Temperature, thermoMachine.MinTemperature);
-            thermoMachine.TargetTemperature = MathF.Max(thermoMachine.TargetTemperature, Atmospherics.TCMB);
-            _adminLogger.Add(LogType.AtmosTemperatureChanged, $"{ToPrettyString(args.Actor)} set temperature on {ToPrettyString(uid)} to {thermoMachine.TargetTemperature}");
-            DirtyUI(uid, thermoMachine);
-        }
-
-        private void DirtyUI(EntityUid uid, GasThermoMachineComponent? thermoMachine, UserInterfaceComponent? ui=null)
-        {
-            if (!Resolve(uid, ref thermoMachine, ref ui, false))
-                return;
-
-            ApcPowerReceiverComponent? powerReceiver = null;
-            if (!Resolve(uid, ref powerReceiver))
-                return;
-
-            _userInterfaceSystem.SetUiState(uid, ThermomachineUiKey.Key,
-                new GasThermomachineBoundUserInterfaceState(thermoMachine.MinTemperature, thermoMachine.MaxTemperature, thermoMachine.TargetTemperature, !powerReceiver.PowerDisabled, IsHeater(thermoMachine)));
-        }
-
-        private void OnExamined(EntityUid uid, GasThermoMachineComponent thermoMachine, ExaminedEvent args)
-        {
-            if (!args.IsInDetailsRange)
-                return;
-
-            if (Loc.TryGetString("gas-thermomachine-system-examined", out var str,
-                        ("machineName", !IsHeater(thermoMachine) ? "freezer" : "heater"),
-                        ("tempColor", !IsHeater(thermoMachine) ? "deepskyblue" : "red"),
-                        ("temp", Math.Round(thermoMachine.TargetTemperature,2))
-               ))
-
-                args.PushMarkup(str);
-        }
-
         private void OnPacketRecv(EntityUid uid, GasThermoMachineComponent component, DeviceNetworkPacketEvent args)
         {
             if (!TryComp(uid, out DeviceNetworkComponent? netConn)
index 36ef59e743bce8f3c9ea0ec4da33c5ab7e409c62..1c05307c1589cefaec6eabeb5844262d1da39e3f 100644 (file)
@@ -5,6 +5,7 @@ using Content.Server.Popups;
 using Content.Server.Power.Components;
 using Content.Server.Power.EntitySystems;
 using Content.Shared.Atmos.Piping.Portable.Components;
+using Content.Shared.Atmos.Piping.Unary.Components;
 using Content.Shared.Atmos.Visuals;
 using Content.Shared.Power;
 using Content.Shared.UserInterface;
@@ -41,6 +42,7 @@ public sealed class SpaceHeaterSystem : EntitySystem
     {
         if (!TryComp<GasThermoMachineComponent>(uid, out var thermoMachine))
             return;
+
         thermoMachine.Cp = spaceHeater.HeatingCp;
         thermoMachine.HeatCapacity = spaceHeater.PowerConsumption;
     }
index ac818e08dcfef567140ecb9f3dd1022ac1b45309..62f0391cf50ef2f2f60dd687443c719a8e6bf9ee 100644 (file)
@@ -13,7 +13,7 @@ namespace Content.Server.NodeContainer.EntitySystems
     /// </summary>
     /// <seealso cref="NodeGroupSystem"/>
     [UsedImplicitly]
-    public sealed class NodeContainerSystem : EntitySystem
+    public sealed class NodeContainerSystem : SharedNodeContainerSystem
     {
         [Dependency] private readonly NodeGroupSystem _nodeGroupSystem = default!;
         private EntityQuery<NodeContainerComponent> _query;
index ab2e27600ab25941e67049fe16432314469c8084..9dc0c2c498eacb6bde09f07bbecedff09be3df5c 100644 (file)
@@ -31,7 +31,6 @@ namespace Content.Server.Power.EntitySystems
         private readonly HashSet<ApcNet> _apcNetReconnectQueue = new();
 
         private EntityQuery<ApcPowerReceiverBatteryComponent> _apcBatteryQuery;
-        private EntityQuery<AppearanceComponent> _appearanceQuery;
         private EntityQuery<BatteryComponent> _batteryQuery;
 
         private BatteryRampPegSolver _solver = new();
@@ -41,7 +40,6 @@ namespace Content.Server.Power.EntitySystems
             base.Initialize();
 
             _apcBatteryQuery = GetEntityQuery<ApcPowerReceiverBatteryComponent>();
-            _appearanceQuery = GetEntityQuery<AppearanceComponent>();
             _batteryQuery = GetEntityQuery<BatteryComponent>();
 
             UpdatesAfter.Add(typeof(NodeGroupSystem));
@@ -317,15 +315,25 @@ namespace Content.Server.Power.EntitySystems
             _powerNetReconnectQueue.Clear();
         }
 
+        private bool IsPoweredCalculate(ApcPowerReceiverComponent comp)
+        {
+            return !comp.PowerDisabled
+                   && (!comp.NeedsPower
+                       || MathHelper.CloseToPercent(comp.NetworkLoad.ReceivingPower,
+                           comp.Load));
+        }
+
+        public override bool IsPoweredCalculate(SharedApcPowerReceiverComponent comp)
+        {
+            return IsPoweredCalculate((ApcPowerReceiverComponent)comp);
+        }
+
         private void UpdateApcPowerReceiver(float frameTime)
         {
             var enumerator = AllEntityQuery<ApcPowerReceiverComponent>();
             while (enumerator.MoveNext(out var uid, out var apcReceiver))
             {
-                var powered = !apcReceiver.PowerDisabled
-                              && (!apcReceiver.NeedsPower
-                                  || MathHelper.CloseToPercent(apcReceiver.NetworkLoad.ReceivingPower,
-                                      apcReceiver.Load));
+                var powered = IsPoweredCalculate(apcReceiver);
 
                 MetaDataComponent? metadata = null;
 
@@ -381,9 +389,6 @@ namespace Content.Server.Power.EntitySystems
 
                 var ev = new PowerChangedEvent(powered, apcReceiver.NetworkLoad.ReceivingPower);
                 RaiseLocalEvent(uid, ref ev);
-
-                if (_appearanceQuery.TryComp(uid, out var appearance))
-                    _appearance.SetData(uid, PowerDeviceVisuals.Powered, powered, appearance);
             }
         }
 
similarity index 77%
rename from Content.Server/Atmos/Piping/Unary/Components/GasThermoMachineComponent.cs
rename to Content.Shared/Atmos/Piping/Unary/Components/GasThermoMachineComponent.cs
index f481443c9434277adc865d2ddbd800ed60c71327..9317594ad287a98f095b901ab701f4c4b671aa62 100644 (file)
@@ -1,9 +1,10 @@
 using Content.Shared.Atmos;
 using Content.Shared.Guidebook;
+using Robust.Shared.GameStates;
 
-namespace Content.Server.Atmos.Piping.Unary.Components
+namespace Content.Shared.Atmos.Piping.Unary.Components
 {
-    [RegisterComponent]
+    [RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
     public sealed partial class GasThermoMachineComponent : Component
     {
         [DataField("inlet")]
@@ -13,11 +14,11 @@ namespace Content.Server.Atmos.Piping.Unary.Components
         ///     Current electrical power consumption, in watts. Increasing power increases the ability of the
         ///     thermomachine to heat or cool air.
         /// </summary>
-        [DataField, ViewVariables(VVAccess.ReadWrite)]
+        [DataField]
         [GuidebookData]
         public float HeatCapacity = 5000;
 
-        [DataField, ViewVariables(VVAccess.ReadWrite)]
+        [DataField, AutoNetworkedField]
         public float TargetTemperature = Atmospherics.T20C;
 
         /// <summary>
@@ -39,42 +40,41 @@ namespace Content.Server.Atmos.Piping.Unary.Components
         ///     Positive for heaters, negative for freezers.
         /// </summary>
         [DataField("coefficientOfPerformance")]
-        [ViewVariables(VVAccess.ReadWrite)]
         public float Cp = 0.9f; // output power / input power, positive is heat
 
         /// <summary>
         ///     Current minimum temperature
         ///     Ignored if heater.
         /// </summary>
-        [DataField, ViewVariables(VVAccess.ReadWrite)]
-        [GuidebookData]
+        [DataField, AutoNetworkedField]
+               [GuidebookData]
         public float MinTemperature = 73.15f;
 
         /// <summary>
         ///     Current maximum temperature
         ///     Ignored if freezer.
         /// </summary>
-        [DataField, ViewVariables(VVAccess.ReadWrite)]
-        [GuidebookData]
+        [DataField, AutoNetworkedField]
+               [GuidebookData]
         public float MaxTemperature = 593.15f;
 
         /// <summary>
         /// Last amount of energy added/removed from the attached pipe network
         /// </summary>
-        [DataField, ViewVariables(VVAccess.ReadWrite)]
+        [DataField]
         public float LastEnergyDelta;
 
         /// <summary>
         /// An percentage of the energy change that is leaked into the surrounding environment rather than the inlet pipe.
         /// </summary>
-        [DataField, ViewVariables(VVAccess.ReadWrite)]
-        [GuidebookData]
-        public float EnergyLeakPercentage;
+        [DataField]
+               [GuidebookData]
+               public float EnergyLeakPercentage;
 
         /// <summary>
         /// If true, heat is exclusively exchanged with the local atmosphere instead of the inlet pipe air
         /// </summary>
-        [DataField, ViewVariables(VVAccess.ReadWrite)]
-        public bool Atmospheric = false;
+        [DataField]
+        public bool Atmospheric;
     }
 }
index 259ebf4eea1849777c550d8a86a450f422b7b0db..5d2beb9de5dffae2ba8ab460199bf5d7a741de9c 100644 (file)
@@ -7,7 +7,7 @@ public sealed record GasThermoMachineData(float EnergyDelta);
 
 [Serializable]
 [NetSerializable]
-public enum ThermomachineUiKey
+public enum ThermomachineUiKey : byte
 {
     Key
 }
@@ -29,23 +29,3 @@ public sealed class GasThermomachineChangeTemperatureMessage : BoundUserInterfac
         Temperature = temperature;
     }
 }
-
-[Serializable]
-[NetSerializable]
-public sealed class GasThermomachineBoundUserInterfaceState : BoundUserInterfaceState
-{
-    public float MinTemperature { get; }
-    public float MaxTemperature { get; }
-    public float Temperature { get; }
-    public bool Enabled { get; }
-    public bool IsHeater { get; }
-
-    public GasThermomachineBoundUserInterfaceState(float minTemperature, float maxTemperature, float temperature, bool enabled, bool isHeater)
-    {
-        MinTemperature = minTemperature;
-        MaxTemperature = maxTemperature;
-        Temperature = temperature;
-        Enabled = enabled;
-        IsHeater = isHeater;
-    }
-}
diff --git a/Content.Shared/Atmos/Piping/Unary/Systems/SharedGasThermoMachineSystem.cs b/Content.Shared/Atmos/Piping/Unary/Systems/SharedGasThermoMachineSystem.cs
new file mode 100644 (file)
index 0000000..724552f
--- /dev/null
@@ -0,0 +1,61 @@
+using Content.Shared.Administration.Logs;
+using Content.Shared.Atmos.Piping.Unary.Components;
+using Content.Shared.Database;
+using Content.Shared.Examine;
+using Content.Shared.Power.EntitySystems;
+
+namespace Content.Shared.Atmos.Piping.Unary.Systems;
+
+public abstract class SharedGasThermoMachineSystem : EntitySystem
+{
+    [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
+    [Dependency] private readonly SharedPowerReceiverSystem _receiver = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+        SubscribeLocalEvent<GasThermoMachineComponent, ExaminedEvent>(OnExamined);
+
+        SubscribeLocalEvent<GasThermoMachineComponent, GasThermomachineToggleMessage>(OnToggleMessage);
+        SubscribeLocalEvent<GasThermoMachineComponent, GasThermomachineChangeTemperatureMessage>(OnChangeTemperature);
+    }
+
+    private void OnExamined(EntityUid uid, GasThermoMachineComponent thermoMachine, ExaminedEvent args)
+    {
+        if (Loc.TryGetString("gas-thermomachine-system-examined",
+                out var str,
+                ("machineName", !IsHeater(thermoMachine) ? "freezer" : "heater"),
+                ("tempColor", !IsHeater(thermoMachine) ? "deepskyblue" : "red"),
+                ("temp", Math.Round(thermoMachine.TargetTemperature, 2))
+            ))
+        {
+            args.PushMarkup(str);
+        }
+    }
+
+    public bool IsHeater(GasThermoMachineComponent comp)
+    {
+        return comp.Cp >= 0;
+    }
+
+    private void OnToggleMessage(EntityUid uid, GasThermoMachineComponent thermoMachine, GasThermomachineToggleMessage args)
+    {
+        var powerState = _receiver.TogglePower(uid, user: args.Actor);
+        _adminLogger.Add(LogType.AtmosPowerChanged, $"{ToPrettyString(args.Actor)} turned {(powerState ? "On" : "Off")} {ToPrettyString(uid)}");
+        DirtyUI(uid, thermoMachine);
+    }
+
+    private void OnChangeTemperature(EntityUid uid, GasThermoMachineComponent thermoMachine, GasThermomachineChangeTemperatureMessage args)
+    {
+        if (IsHeater(thermoMachine))
+            thermoMachine.TargetTemperature = MathF.Min(args.Temperature, thermoMachine.MaxTemperature);
+        else
+            thermoMachine.TargetTemperature = MathF.Max(args.Temperature, thermoMachine.MinTemperature);
+        thermoMachine.TargetTemperature = MathF.Max(thermoMachine.TargetTemperature, Atmospherics.TCMB);
+        _adminLogger.Add(LogType.AtmosTemperatureChanged, $"{ToPrettyString(args.Actor)} set temperature on {ToPrettyString(uid)} to {thermoMachine.TargetTemperature}");
+        Dirty(uid, thermoMachine);
+        DirtyUI(uid, thermoMachine);
+    }
+
+    protected virtual void DirtyUI(EntityUid uid, GasThermoMachineComponent? thermoMachine, UserInterfaceComponent? ui=null) {}
+}
diff --git a/Content.Shared/NodeContainer/SharedNodeContainerSystem.cs b/Content.Shared/NodeContainer/SharedNodeContainerSystem.cs
new file mode 100644 (file)
index 0000000..e639af2
--- /dev/null
@@ -0,0 +1,6 @@
+namespace Content.Shared.NodeContainer;
+
+public abstract class SharedNodeContainerSystem : EntitySystem
+{
+
+}
index 28f74536c4a7663cfc0dfcc940f172f0eb8d9df0..7611074d8061cb42c1995e6125f6f095d6c954a1 100644 (file)
@@ -1,6 +1,21 @@
+using Content.Shared.Power.Components;
+
 namespace Content.Shared.Power.EntitySystems;
 
 public abstract class SharedPowerNetSystem : EntitySystem
 {
+    [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+
+    public abstract bool IsPoweredCalculate(SharedApcPowerReceiverComponent comp);
+
+    public override void Initialize()
+    {
+        base.Initialize();
+        SubscribeLocalEvent<AppearanceComponent, PowerChangedEvent>(OnPowerAppearance);
+    }
 
+    private void OnPowerAppearance(Entity<AppearanceComponent> ent, ref PowerChangedEvent args)
+    {
+        _appearance.SetData(ent, PowerDeviceVisuals.Powered, args.Powered, ent.Comp);
+    }
 }
index d86273974bbcabc2653eb1952892e0952b2a3707..4a66a6ea97c4c44839d33f414b00e05442d77f53 100644 (file)
@@ -4,13 +4,16 @@ using Content.Shared.Database;
 using Content.Shared.Power.Components;
 using Robust.Shared.Audio;
 using Robust.Shared.Audio.Systems;
+using Robust.Shared.Network;
 
 namespace Content.Shared.Power.EntitySystems;
 
 public abstract class SharedPowerReceiverSystem : EntitySystem
 {
+    [Dependency] private readonly INetManager _netMan = default!;
     [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
     [Dependency] private readonly SharedAudioSystem _audio = default!;
+    [Dependency] private readonly SharedPowerNetSystem _net = default!;
 
     public abstract bool ResolveApc(EntityUid entity, [NotNullWhen(true)] ref SharedApcPowerReceiverComponent? component);
 
@@ -44,6 +47,15 @@ public abstract class SharedPowerReceiverSystem : EntitySystem
         // it'll save a lot of confusion if 'always powered' means 'always powered'
         if (!receiver.NeedsPower)
         {
+            var powered = _net.IsPoweredCalculate(receiver);
+
+            // Server won't raise it here as it can raise the load event later with NeedsPower?
+            // This is mostly here for clientside predictions.
+            if (receiver.Powered != powered)
+            {
+                RaisePower((uid, receiver));
+            }
+
             SetPowerDisabled(uid, false, receiver);
             return true;
         }
@@ -59,6 +71,19 @@ public abstract class SharedPowerReceiverSystem : EntitySystem
                 AudioParams.Default.WithVolume(-2f));
         }
 
+        if (_netMan.IsClient && receiver.PowerDisabled)
+        {
+            var powered = _net.IsPoweredCalculate(receiver);
+
+            // Server won't raise it here as it can raise the load event later with NeedsPower?
+            // This is mostly here for clientside predictions.
+            if (receiver.Powered != powered)
+            {
+                receiver.Powered = powered;
+                RaisePower((uid, receiver));
+            }
+        }
+
         return !receiver.PowerDisabled; // i.e. PowerEnabled
     }