--- /dev/null
+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();
+ }
+ }
+}
-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;
_window.ToggleStatusButton.OnPressed += _ => OnToggleStatusButtonPressed();
_window.TemperatureSpinbox.OnValueChanged += _ => OnTemperatureChanged(_window.TemperatureSpinbox.Value);
+ _window.Entity = Owner;
+ Update();
}
private void OnToggleStatusButtonPressed()
if (_window is null) return;
_window.SetActive(!_window.Active);
- SendMessage(new GasThermomachineToggleMessage());
+ SendPredictedMessage(new GasThermomachineToggleMessage());
}
private void OnTemperatureChanged(float value)
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"),
-<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">
<Label Text="{Loc comp-gas-thermomachine-ui-temperature}"/>
</BoxContainer>
</BoxContainer>
-</DefaultWindow>
+</controls:FancyWindow>
+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(
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;
}
}
{
TemperatureSpinbox.Value = temperature;
}
+
+ protected override void FrameUpdate(FrameEventArgs args)
+ {
+ base.FrameUpdate(args);
+
+ if (_entManager.TryGetComponent(Entity, out ApcPowerReceiverComponent? receiver))
+ {
+ SetActive(!receiver.PowerDisabled);
+ }
+ }
}
--- /dev/null
+using Content.Shared.NodeContainer;
+
+namespace Content.Client.NodeContainer;
+
+public sealed class NodeContainerSystem : SharedNodeContainerSystem;
+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;
+ }
}
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;
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;
}
}
- 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)
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;
{
if (!TryComp<GasThermoMachineComponent>(uid, out var thermoMachine))
return;
+
thermoMachine.Cp = spaceHeater.HeatingCp;
thermoMachine.HeatCapacity = spaceHeater.PowerConsumption;
}
/// </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;
private readonly HashSet<ApcNet> _apcNetReconnectQueue = new();
private EntityQuery<ApcPowerReceiverBatteryComponent> _apcBatteryQuery;
- private EntityQuery<AppearanceComponent> _appearanceQuery;
private EntityQuery<BatteryComponent> _batteryQuery;
private BatteryRampPegSolver _solver = new();
base.Initialize();
_apcBatteryQuery = GetEntityQuery<ApcPowerReceiverBatteryComponent>();
- _appearanceQuery = GetEntityQuery<AppearanceComponent>();
_batteryQuery = GetEntityQuery<BatteryComponent>();
UpdatesAfter.Add(typeof(NodeGroupSystem));
_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;
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);
}
}
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")]
/// 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>
/// 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;
}
}
[Serializable]
[NetSerializable]
-public enum ThermomachineUiKey
+public enum ThermomachineUiKey : byte
{
Key
}
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;
- }
-}
--- /dev/null
+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) {}
+}
--- /dev/null
+namespace Content.Shared.NodeContainer;
+
+public abstract class SharedNodeContainerSystem : EntitySystem
+{
+
+}
+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);
+ }
}
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);
// 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;
}
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
}