]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Add space heaters (#25250)
authorMenshin <Menshin@users.noreply.github.com>
Wed, 28 Feb 2024 18:27:29 +0000 (19:27 +0100)
committerGitHub <noreply@github.com>
Wed, 28 Feb 2024 18:27:29 +0000 (10:27 -0800)
21 files changed:
Content.Client/Atmos/UI/SpaceHeaterBoundUserInterface.cs [new file with mode: 0644]
Content.Client/Atmos/UI/SpaceHeaterWindow.xaml [new file with mode: 0644]
Content.Client/Atmos/UI/SpaceHeaterWindow.xaml.cs [new file with mode: 0644]
Content.Server/Atmos/Piping/Unary/EntitySystems/GasThermoMachineSystem.cs
Content.Server/Atmos/Portable/SpaceHeaterComponent.cs [new file with mode: 0644]
Content.Server/Atmos/Portable/SpaceHeaterSystem.cs [new file with mode: 0644]
Content.Shared/Atmos/Portable/Components/SharedSpaceHeaterComponent.cs [new file with mode: 0644]
Content.Shared/Atmos/Visuals/SpaceHeaterVisuals.cs [new file with mode: 0644]
Resources/Locale/en-US/components/space-heater-component.ftl [new file with mode: 0644]
Resources/Locale/en-US/wires/wire-names.ftl
Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml
Resources/Prototypes/Entities/Structures/Machines/lathe.yml
Resources/Prototypes/Entities/Structures/Piping/Atmospherics/portable.yml
Resources/Prototypes/Recipes/Lathes/electronics.yml
Resources/Prototypes/Research/industrial.yml
Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_sheater.rsi/meta.json [new file with mode: 0644]
Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_sheater.rsi/sheaterCool.png [new file with mode: 0644]
Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_sheater.rsi/sheaterHeat.png [new file with mode: 0644]
Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_sheater.rsi/sheaterOff.png [new file with mode: 0644]
Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_sheater.rsi/sheaterPanelOpen.png [new file with mode: 0644]
Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_sheater.rsi/sheaterStandby.png [new file with mode: 0644]

diff --git a/Content.Client/Atmos/UI/SpaceHeaterBoundUserInterface.cs b/Content.Client/Atmos/UI/SpaceHeaterBoundUserInterface.cs
new file mode 100644 (file)
index 0000000..4d8d119
--- /dev/null
@@ -0,0 +1,90 @@
+using Content.Shared.Atmos.Piping.Portable.Components;
+using JetBrains.Annotations;
+using Robust.Client.UserInterface.Controls;
+
+namespace Content.Client.Atmos.UI;
+
+/// <summary>
+///     Initializes a <see cref="SpaceHeaterWindow"/> and updates it when new server messages are received.
+/// </summary>
+[UsedImplicitly]
+public sealed class SpaceHeaterBoundUserInterface : BoundUserInterface
+{
+    [ViewVariables]
+    private SpaceHeaterWindow? _window;
+
+    public SpaceHeaterBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
+    {
+    }
+
+    protected override void Open()
+    {
+        base.Open();
+
+        _window = new SpaceHeaterWindow();
+
+        if (State != null)
+            UpdateState(State);
+
+        _window.OpenCentered();
+
+        _window.OnClose += Close;
+
+        _window.ToggleStatusButton.OnPressed += _ => OnToggleStatusButtonPressed();
+        _window.IncreaseTempRange.OnPressed += _ => OnTemperatureRangeChanged(_window.TemperatureChangeDelta);
+        _window.DecreaseTempRange.OnPressed += _ => OnTemperatureRangeChanged(-_window.TemperatureChangeDelta);
+        _window.ModeSelector.OnItemSelected += OnModeChanged;
+
+        _window.PowerLevelSelector.OnItemSelected += OnPowerLevelChange;
+    }
+
+    private void OnToggleStatusButtonPressed()
+    {
+        _window?.SetActive(!_window.Active);
+        SendMessage(new SpaceHeaterToggleMessage());
+    }
+
+    private void OnTemperatureRangeChanged(float changeAmount)
+    {
+        SendMessage(new SpaceHeaterChangeTemperatureMessage(changeAmount));
+    }
+
+    private void OnModeChanged(OptionButton.ItemSelectedEventArgs args)
+    {
+        _window?.ModeSelector.SelectId(args.Id);
+        SendMessage(new SpaceHeaterChangeModeMessage((SpaceHeaterMode)args.Id));
+    }
+
+    private void OnPowerLevelChange(RadioOptionItemSelectedEventArgs<int> args)
+    {
+        _window?.PowerLevelSelector.Select(args.Id);
+        SendMessage(new SpaceHeaterChangePowerLevelMessage((SpaceHeaterPowerLevel)args.Id));
+    }
+
+    /// <summary>
+    ///     Update the UI state based on server-sent info
+    /// </summary>
+    /// <param name="state"></param>
+    protected override void UpdateState(BoundUserInterfaceState state)
+    {
+        base.UpdateState(state);
+        if (_window == null || state is not SpaceHeaterBoundUserInterfaceState cast)
+            return;
+
+        _window.SetActive(cast.Enabled);
+        _window.ModeSelector.SelectId((int)cast.Mode);
+        _window.PowerLevelSelector.Select((int)cast.PowerLevel);
+
+        _window.MinTemp = cast.MinTemperature;
+        _window.MaxTemp = cast.MaxTemperature;
+        _window.SetTemperature(cast.TargetTemperature);
+    }
+
+    protected override void Dispose(bool disposing)
+    {
+        base.Dispose(disposing);
+        if (!disposing)
+            return;
+        _window?.Dispose();
+    }
+}
diff --git a/Content.Client/Atmos/UI/SpaceHeaterWindow.xaml b/Content.Client/Atmos/UI/SpaceHeaterWindow.xaml
new file mode 100644 (file)
index 0000000..1b7bd49
--- /dev/null
@@ -0,0 +1,34 @@
+<DefaultWindow xmlns="https://spacestation14.io"
+               xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+               MinSize="280 160" Title="Temperature Control Unit">
+
+    <BoxContainer Name="VboxContainer" Orientation="Vertical" Margin="5 5 5 5" SeparationOverride="10">
+
+        <BoxContainer Orientation="Horizontal" HorizontalExpand="True">
+            <BoxContainer Orientation="Horizontal" HorizontalExpand="True">
+                <Button Text="{Loc comp-space-heater-ui-status-disabled}" Access="Public" Name="ToggleStatusButton"/>
+            </BoxContainer>
+            <BoxContainer Orientation="Horizontal" SeparationOverride="5">
+                <Label Text="{Loc comp-space-heater-ui-mode}"/>
+                <OptionButton Access="Public" Name="ModeSelector"/>
+            </BoxContainer>
+        </BoxContainer>
+
+        <BoxContainer Orientation="Horizontal" HorizontalExpand="True" SeparationOverride="5">
+            <BoxContainer Orientation="Horizontal" HorizontalExpand="True">
+                <Label Text="{Loc comp-space-heater-ui-thermostat}"/>
+            </BoxContainer>
+            <BoxContainer Orientation="Horizontal" HorizontalExpand="True" HorizontalAlignment="Right">
+                <Button Text="{Loc comp-space-heater-ui-decrease-temperature-range}" Access="Public" Name="DecreaseTempRange" StyleClasses="OpenRight"/>
+                <LineEdit Name ="Thermostat" MinSize="55 0"></LineEdit>
+                <Button Text="{Loc comp-space-heater-ui-increase-temperature-range}" Access="Public" Name="IncreaseTempRange" StyleClasses="OpenLeft"/>
+            </BoxContainer>
+        </BoxContainer>
+
+        <BoxContainer Orientation="Horizontal" HorizontalExpand="True" SeparationOverride="5">
+            <Label Text="{Loc comp-space-heater-ui-power-consumption}"/>
+            <BoxContainer Name="PowerLevelSelectorHBox" Access="Public" SeparationOverride="2"/>
+        </BoxContainer>
+
+    </BoxContainer>
+</DefaultWindow>
diff --git a/Content.Client/Atmos/UI/SpaceHeaterWindow.xaml.cs b/Content.Client/Atmos/UI/SpaceHeaterWindow.xaml.cs
new file mode 100644 (file)
index 0000000..097601a
--- /dev/null
@@ -0,0 +1,73 @@
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Client.UserInterface.XAML;
+using Content.Shared.Atmos;
+using Content.Shared.Atmos.Piping.Portable.Components;
+
+namespace Content.Client.Atmos.UI;
+
+/// <summary>
+///     Client-side UI used to control a space heater.
+/// </summary>
+[GenerateTypedNameReferences]
+public sealed partial class SpaceHeaterWindow : DefaultWindow
+{
+    // To account for a minimum delta temperature for atmos equalization to trigger we use a fixed step for target temperature increment/decrement
+    public int TemperatureChangeDelta = 5;
+    public bool Active;
+
+    // Temperatures range bounds in Kelvin (K)
+    public float MinTemp;
+    public float MaxTemp;
+
+    public RadioOptions<int> PowerLevelSelector;
+
+    public SpaceHeaterWindow()
+    {
+        RobustXamlLoader.Load(this);
+
+        // Add the Mode selector list
+        foreach (var value in Enum.GetValues<SpaceHeaterMode>())
+        {
+            ModeSelector.AddItem(Loc.GetString($"comp-space-heater-mode-{value}"), (int)value);
+        }
+
+        // Add the Power level radio buttons
+        PowerLevelSelectorHBox.AddChild(PowerLevelSelector = new RadioOptions<int>(RadioOptionsLayout.Horizontal));
+        PowerLevelSelector.FirstButtonStyle = "OpenRight";
+        PowerLevelSelector.LastButtonStyle = "OpenLeft";
+        PowerLevelSelector.ButtonStyle = "OpenBoth";
+        foreach (var value in Enum.GetValues<SpaceHeaterPowerLevel>())
+        {
+            PowerLevelSelector.AddItem(Loc.GetString($"comp-space-heater-ui-{value}-power-consumption"), (int)value);
+        }
+
+        // Only allow temperature increment/decrement of TemperatureChangeDelta
+        Thermostat.Editable = false;
+    }
+
+    public void SetActive(bool active)
+    {
+        Active = active;
+        ToggleStatusButton.Pressed = active;
+
+        if (active)
+        {
+            ToggleStatusButton.Text = Loc.GetString("comp-space-heater-ui-status-enabled");
+        }
+        else
+        {
+            ToggleStatusButton.Text = Loc.GetString("comp-space-heater-ui-status-disabled");
+        }
+    }
+
+    public void SetTemperature(float targetTemperature)
+    {
+        Thermostat.SetText($"{targetTemperature - Atmospherics.T0C} °C");
+
+        IncreaseTempRange.Disabled = targetTemperature + TemperatureChangeDelta > MaxTemp;
+        DecreaseTempRange.Disabled = targetTemperature - TemperatureChangeDelta < MinTemp;
+    }
+}
+
index 2900082623221315e2d6d80399df3e1842262242..d376c6d9d6ef05b55039973856194b495b2f4810 100644 (file)
@@ -55,6 +55,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
 
         private void OnThermoMachineUpdated(EntityUid uid, GasThermoMachineComponent thermoMachine, ref AtmosDeviceUpdateEvent args)
         {
+            thermoMachine.LastEnergyDelta = 0f;
             if (!(_power.IsPowered(uid) && TryComp<ApcPowerReceiverComponent>(uid, out var receiver)))
                 return;
 
@@ -100,12 +101,14 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
             if (thermoMachine.Atmospheric)
             {
                 _atmosphereSystem.AddHeat(heatExchangeGasMixture, dQActual);
+                thermoMachine.LastEnergyDelta = dQActual;
             }
             else
             {
                 float dQLeak = dQActual * thermoMachine.EnergyLeakPercentage;
                 float dQPipe = dQActual - dQLeak;
                 _atmosphereSystem.AddHeat(heatExchangeGasMixture, dQPipe);
+                thermoMachine.LastEnergyDelta = dQPipe;
 
                 if (dQLeak != 0f && _atmosphereSystem.GetContainingMixture(uid) is { } containingMixture)
                     _atmosphereSystem.AddHeat(containingMixture, dQLeak);
diff --git a/Content.Server/Atmos/Portable/SpaceHeaterComponent.cs b/Content.Server/Atmos/Portable/SpaceHeaterComponent.cs
new file mode 100644 (file)
index 0000000..e490ab3
--- /dev/null
@@ -0,0 +1,58 @@
+using Content.Shared.Atmos;
+using Content.Shared.Atmos.Piping.Portable.Components;
+using Content.Shared.Atmos.Visuals;
+
+namespace Content.Server.Atmos.Portable;
+
+[RegisterComponent]
+public sealed partial class SpaceHeaterComponent : Component
+{
+    /// <summary>
+    ///     Current mode the space heater is in. Possible values : Auto, Heat and Cool
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public SpaceHeaterMode Mode = SpaceHeaterMode.Auto;
+
+    /// <summary>
+    ///     The power level the space heater is currently set to. Possible values : Low, Medium, High
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public SpaceHeaterPowerLevel PowerLevel = SpaceHeaterPowerLevel.Medium;
+
+    /// <summary>
+    ///     Maximum target temperature the device can be set to
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public float MaxTemperature = Atmospherics.T20C + 20;
+
+    /// <summary>
+    ///     Minimal target temperature the device can be set to
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public float MinTemperature = Atmospherics.T0C - 10;
+
+    /// <summary>
+    ///     Coefficient of performance. Output power / input power.
+    ///     Positive for heaters, negative for freezers.
+    /// </summary>
+    [DataField("heatingCoefficientOfPerformance")]
+    [ViewVariables(VVAccess.ReadWrite)]
+    public float HeatingCp = 1f;
+
+    [DataField("coolingCoefficientOfPerformance")]
+    [ViewVariables(VVAccess.ReadWrite)]
+    public float CoolingCp = -0.9f;
+
+    /// <summary>
+    ///     The delta from the target temperature after which the space heater switch mode while in Auto. Value should account for the thermomachine temperature tolerance.
+    /// </summary>
+    [DataField]
+    [ViewVariables(VVAccess.ReadWrite)]
+    public float AutoModeSwitchThreshold = 0.8f;
+
+    /// <summary>
+    ///     Current electrical power consumption, in watts, of the space heater at medium power level. Passed to the thermomachine component.
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public float PowerConsumption = 3500f;
+}
diff --git a/Content.Server/Atmos/Portable/SpaceHeaterSystem.cs b/Content.Server/Atmos/Portable/SpaceHeaterSystem.cs
new file mode 100644 (file)
index 0000000..b7336a7
--- /dev/null
@@ -0,0 +1,191 @@
+using Content.Server.Atmos.EntitySystems;
+using Content.Server.Atmos.Piping.Components;
+using Content.Server.Atmos.Piping.Unary.Components;
+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.Visuals;
+using Content.Shared.UserInterface;
+using Robust.Server.GameObjects;
+
+namespace Content.Server.Atmos.Portable;
+
+public sealed class SpaceHeaterSystem : EntitySystem
+{
+    [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
+    [Dependency] private readonly PopupSystem _popup = default!;
+    [Dependency] private readonly PowerReceiverSystem _power = default!;
+    [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+    [Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<SpaceHeaterComponent, ActivatableUIOpenAttemptEvent>(OnUIActivationAttempt);
+        SubscribeLocalEvent<SpaceHeaterComponent, BeforeActivatableUIOpenEvent>(OnBeforeOpened);
+
+        SubscribeLocalEvent<SpaceHeaterComponent, AtmosDeviceUpdateEvent>(OnDeviceUpdated);
+        SubscribeLocalEvent<SpaceHeaterComponent, MapInitEvent>(OnInit);
+        SubscribeLocalEvent<SpaceHeaterComponent, PowerChangedEvent>(OnPowerChanged);
+
+        SubscribeLocalEvent<SpaceHeaterComponent, SpaceHeaterChangeModeMessage>(OnModeChanged);
+        SubscribeLocalEvent<SpaceHeaterComponent, SpaceHeaterChangePowerLevelMessage>(OnPowerLevelChanged);
+        SubscribeLocalEvent<SpaceHeaterComponent, SpaceHeaterChangeTemperatureMessage>(OnTemperatureChanged);
+        SubscribeLocalEvent<SpaceHeaterComponent, SpaceHeaterToggleMessage>(OnToggle);
+    }
+
+    private void OnInit(EntityUid uid, SpaceHeaterComponent spaceHeater, MapInitEvent args)
+    {
+        if (!TryComp<GasThermoMachineComponent>(uid, out var thermoMachine))
+            return;
+        thermoMachine.Cp = spaceHeater.HeatingCp;
+        thermoMachine.HeatCapacity = spaceHeater.PowerConsumption;
+    }
+
+    private void OnBeforeOpened(EntityUid uid, SpaceHeaterComponent spaceHeater, BeforeActivatableUIOpenEvent args)
+    {
+        DirtyUI(uid, spaceHeater);
+    }
+
+    private void OnUIActivationAttempt(EntityUid uid, SpaceHeaterComponent spaceHeater, ActivatableUIOpenAttemptEvent args)
+    {
+        if (!Comp<TransformComponent>(uid).Anchored)
+        {
+            _popup.PopupEntity(Loc.GetString("comp-space-heater-unanchored", ("device", Loc.GetString("comp-space-heater-device-name"))), uid, args.User);
+            args.Cancel();
+        }
+    }
+
+    private void OnDeviceUpdated(EntityUid uid, SpaceHeaterComponent spaceHeater, ref AtmosDeviceUpdateEvent args)
+    {
+        if (!_power.IsPowered(uid)
+            || !TryComp<GasThermoMachineComponent>(uid, out var thermoMachine))
+        {
+            return;
+        }
+
+        UpdateAppearance(uid);
+
+        // If in automatic temperature mode, check if we need to adjust the heat exchange direction
+        if (spaceHeater.Mode == SpaceHeaterMode.Auto)
+        {
+            var environment = _atmosphereSystem.GetContainingMixture(uid);
+            if (environment == null)
+                return;
+
+            if (environment.Temperature <= thermoMachine.TargetTemperature - (thermoMachine.TemperatureTolerance + spaceHeater.AutoModeSwitchThreshold))
+            {
+                thermoMachine.Cp = spaceHeater.HeatingCp;
+            }
+            else if (environment.Temperature >= thermoMachine.TargetTemperature + (thermoMachine.TemperatureTolerance + spaceHeater.AutoModeSwitchThreshold))
+            {
+                thermoMachine.Cp = spaceHeater.CoolingCp;
+            }
+        }
+    }
+
+    private void OnPowerChanged(EntityUid uid, SpaceHeaterComponent spaceHeater, ref PowerChangedEvent args)
+    {
+        UpdateAppearance(uid);
+        DirtyUI(uid, spaceHeater);
+    }
+
+    private void OnToggle(EntityUid uid, SpaceHeaterComponent spaceHeater, SpaceHeaterToggleMessage args)
+    {
+        ApcPowerReceiverComponent? powerReceiver = null;
+        if (!Resolve(uid, ref powerReceiver))
+            return;
+
+        _power.TogglePower(uid);
+
+        UpdateAppearance(uid);
+        DirtyUI(uid, spaceHeater);
+    }
+
+    private void OnTemperatureChanged(EntityUid uid, SpaceHeaterComponent spaceHeater, SpaceHeaterChangeTemperatureMessage args)
+    {
+        if (!TryComp<GasThermoMachineComponent>(uid, out var thermoMachine))
+            return;
+
+        thermoMachine.TargetTemperature += args.Temperature;
+
+        UpdateAppearance(uid);
+        DirtyUI(uid, spaceHeater);
+    }
+
+    private void OnModeChanged(EntityUid uid, SpaceHeaterComponent spaceHeater, SpaceHeaterChangeModeMessage args)
+    {
+        if (!TryComp<GasThermoMachineComponent>(uid, out var thermoMachine))
+            return;
+
+        spaceHeater.Mode = args.Mode;
+
+        if (spaceHeater.Mode == SpaceHeaterMode.Heat)
+            thermoMachine.Cp = spaceHeater.HeatingCp;
+        else if (spaceHeater.Mode == SpaceHeaterMode.Cool)
+            thermoMachine.Cp = spaceHeater.CoolingCp;
+
+        DirtyUI(uid, spaceHeater);
+    }
+
+    private void OnPowerLevelChanged(EntityUid uid, SpaceHeaterComponent spaceHeater, SpaceHeaterChangePowerLevelMessage args)
+    {
+        if (!TryComp<GasThermoMachineComponent>(uid, out var thermoMachine))
+            return;
+
+        spaceHeater.PowerLevel = args.PowerLevel;
+
+        switch (spaceHeater.PowerLevel)
+        {
+            case SpaceHeaterPowerLevel.Low:
+                thermoMachine.HeatCapacity = spaceHeater.PowerConsumption / 2;
+                break;
+
+            case SpaceHeaterPowerLevel.Medium:
+                thermoMachine.HeatCapacity = spaceHeater.PowerConsumption;
+                break;
+
+            case SpaceHeaterPowerLevel.High:
+                thermoMachine.HeatCapacity = spaceHeater.PowerConsumption * 2;
+                break;
+        }
+
+        DirtyUI(uid, spaceHeater);
+    }
+
+    private void DirtyUI(EntityUid uid, SpaceHeaterComponent? spaceHeater)
+    {
+        if (!Resolve(uid, ref spaceHeater)
+            || !TryComp<GasThermoMachineComponent>(uid, out var thermoMachine)
+            || !TryComp<ApcPowerReceiverComponent>(uid, out var powerReceiver))
+        {
+            return;
+        }
+        _userInterfaceSystem.TrySetUiState(uid, SpaceHeaterUiKey.Key,
+            new SpaceHeaterBoundUserInterfaceState(spaceHeater.MinTemperature, spaceHeater.MaxTemperature, thermoMachine.TargetTemperature, !powerReceiver.PowerDisabled, spaceHeater.Mode, spaceHeater.PowerLevel));
+    }
+
+    private void UpdateAppearance(EntityUid uid)
+    {
+        if (!_power.IsPowered(uid) || !TryComp<GasThermoMachineComponent>(uid, out var thermoMachine))
+        {
+            _appearance.SetData(uid, SpaceHeaterVisuals.State, SpaceHeaterState.Off);
+            return;
+        }
+
+        if (thermoMachine.LastEnergyDelta > 0)
+        {
+            _appearance.SetData(uid, SpaceHeaterVisuals.State, SpaceHeaterState.Heating);
+        }
+        else if (thermoMachine.LastEnergyDelta < 0)
+        {
+            _appearance.SetData(uid, SpaceHeaterVisuals.State, SpaceHeaterState.Cooling);
+        }
+        else
+        {
+            _appearance.SetData(uid, SpaceHeaterVisuals.State, SpaceHeaterState.StandBy);
+        }
+    }
+}
diff --git a/Content.Shared/Atmos/Portable/Components/SharedSpaceHeaterComponent.cs b/Content.Shared/Atmos/Portable/Components/SharedSpaceHeaterComponent.cs
new file mode 100644 (file)
index 0000000..731631b
--- /dev/null
@@ -0,0 +1,90 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Atmos.Piping.Portable.Components;
+
+[Serializable]
+[NetSerializable]
+public enum SpaceHeaterUiKey
+{
+    Key
+}
+
+[Serializable]
+[NetSerializable]
+public sealed class SpaceHeaterToggleMessage : BoundUserInterfaceMessage
+{
+}
+
+[Serializable]
+[NetSerializable]
+public sealed class SpaceHeaterChangeTemperatureMessage : BoundUserInterfaceMessage
+{
+    public float Temperature { get; }
+
+    public SpaceHeaterChangeTemperatureMessage(float temperature)
+    {
+        Temperature = temperature;
+    }
+}
+
+[Serializable]
+[NetSerializable]
+public sealed class SpaceHeaterChangePowerLevelMessage : BoundUserInterfaceMessage
+{
+    public SpaceHeaterPowerLevel PowerLevel { get; }
+
+    public SpaceHeaterChangePowerLevelMessage(SpaceHeaterPowerLevel powerLevel)
+    {
+        PowerLevel = powerLevel;
+    }
+}
+
+[Serializable]
+[NetSerializable]
+public sealed class SpaceHeaterChangeModeMessage : BoundUserInterfaceMessage
+{
+    public SpaceHeaterMode Mode { get; }
+
+    public SpaceHeaterChangeModeMessage(SpaceHeaterMode mode)
+    {
+        Mode = mode;
+    }
+}
+
+[Serializable]
+[NetSerializable]
+public sealed class SpaceHeaterBoundUserInterfaceState : BoundUserInterfaceState
+{
+    public float MinTemperature { get; }
+    public float MaxTemperature { get; }
+    public float TargetTemperature { get; }
+    public bool Enabled { get; }
+    public SpaceHeaterMode Mode { get; }
+    public SpaceHeaterPowerLevel PowerLevel { get; }
+
+    public SpaceHeaterBoundUserInterfaceState(float minTemperature, float maxTemperature, float temperature, bool enabled, SpaceHeaterMode mode, SpaceHeaterPowerLevel powerLevel)
+    {
+        MinTemperature = minTemperature;
+        MaxTemperature = maxTemperature;
+        TargetTemperature = temperature;
+        Enabled = enabled;
+        Mode = mode;
+        PowerLevel = powerLevel;
+    }
+}
+
+[Serializable, NetSerializable]
+public enum SpaceHeaterMode : byte
+{
+    Auto,
+    Heat,
+    Cool
+}
+
+[Serializable, NetSerializable]
+public enum SpaceHeaterPowerLevel : byte
+{
+    Low,
+    Medium,
+    High
+}
diff --git a/Content.Shared/Atmos/Visuals/SpaceHeaterVisuals.cs b/Content.Shared/Atmos/Visuals/SpaceHeaterVisuals.cs
new file mode 100644 (file)
index 0000000..0cbdb91
--- /dev/null
@@ -0,0 +1,27 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Atmos.Visuals;
+
+/// <summary>
+///     Used for the visualizer
+/// </summary>
+[Serializable, NetSerializable]
+public enum SpaceHeaterVisualLayers : byte
+{
+    Main
+}
+
+[Serializable, NetSerializable]
+public enum SpaceHeaterVisuals : byte
+{
+    State,
+}
+
+[Serializable, NetSerializable]
+public enum SpaceHeaterState : byte
+{
+    Off,
+    StandBy,
+    Heating,
+    Cooling,
+}
diff --git a/Resources/Locale/en-US/components/space-heater-component.ftl b/Resources/Locale/en-US/components/space-heater-component.ftl
new file mode 100644 (file)
index 0000000..48cd17c
--- /dev/null
@@ -0,0 +1,18 @@
+comp-space-heater-ui-thermostat = Thermostat:
+comp-space-heater-ui-mode = Mode
+comp-space-heater-ui-status-disabled = Off
+comp-space-heater-ui-status-enabled = On
+comp-space-heater-ui-increase-temperature-range = +
+comp-space-heater-ui-decrease-temperature-range = -
+
+comp-space-heater-mode-Auto = Auto
+comp-space-heater-mode-Heat = Heat
+comp-space-heater-mode-Cool = Cool
+
+comp-space-heater-ui-power-consumption = Power level:
+comp-space-heater-ui-Low-power-consumption = Low
+comp-space-heater-ui-Medium-power-consumption = Medium
+comp-space-heater-ui-High-power-consumption = High
+
+comp-space-heater-device-name = space heater
+comp-space-heater-unanchored = The {$device} is not anchored.
index 8b760ca60f04e9196c002e72bbfe30ee6a01ea2c..851241f85c1b6c8261c00e8f43cfe8cc9dbbd3b4 100644 (file)
@@ -38,6 +38,7 @@ wires-board-name-windoor = Windoor Control
 wires-board-name-mech = Mech
 wires-board-name-fatextractor = FatExtractor
 wires-board-name-flatpacker = Flatpacker
+wires-board-name-spaceheater = Space Heater
 
 # names that get displayed in the wire hacking hud & admin logs.
 
index 6581fecbac8e5d32921d8f27829c37e7d460027e..0d223a8bc92502d1d687e1c2ec4eea5fa16c3e4c 100644 (file)
   id: PortableScrubberMachineCircuitBoard
   parent: BaseMachineCircuitboard
   name: portable scrubber machine board
-  description: A PCB for a portable scrubber.
+  description: A machine printed circuit board for a portable scrubber.
   components:
   - type: Sprite
     state: engineering
       Cable: 5
       Glass: 2
 
+- type: entity
+  id: SpaceHeaterMachineCircuitBoard
+  parent: BaseMachineCircuitboard
+  name: space heater machine board
+  description: A machine printed circuit board for a space heater.
+  components:
+  - type: Sprite
+    state: engineering
+  - type: MachineBoard
+    prototype: SpaceHeater
+    requirements:
+      MatterBin: 1
+      Capacitor: 2
+    materialRequirements:
+      Cable: 5
+
 - type: entity
   id: CloningPodMachineCircuitboard
   parent: BaseMachineCircuitboard
index a1f00f6ffaa22698a62f9e6e720380aac91fd6d2..b42d9df5bb571329aa248dff7ada3e7896a38692 100644 (file)
       - ThermomachineFreezerMachineCircuitBoard
       - HellfireFreezerMachineCircuitBoard
       - PortableScrubberMachineCircuitBoard
+      - SpaceHeaterMachineCircuitBoard
       - CloningPodMachineCircuitboard
       - MedicalScannerMachineCircuitboard
       - CryoPodMachineCircuitboard
index e3ae06fa509788e27be853b431efc2e3eb203904..0e2a5f6fe5d4a550088209aad54ee779870de1f8 100644 (file)
@@ -1,15 +1,15 @@
 - type: entity
   id: PortableScrubber
-  parent: BaseStructureDynamic
+  parent: [BaseMachinePowered, ConstructibleMachine]
   name: portable scrubber
   description: It scrubs, portably!
   components:
   - type: Transform
-    noRot: true
-  - type: InteractionOutline
+    anchored: false
   - type: Physics
     bodyType: Dynamic
-    canCollide: false
+  - type: AtmosDevice
+    joinSystem: true
   - type: Fixtures
     fixtures:
       fix1:
@@ -34,9 +34,6 @@
       shader: unshaded
       visible: false
       map: ["enum.PortableScrubberVisualLayers.IsDraining"]
-  - type: Pullable
-  - type: AtmosDevice
-    joinSystem: true
   - type: PortableScrubber
     gasMixture:
       volume: 1250
@@ -49,7 +46,6 @@
         volume: 1
   - type: ApcPowerReceiver
     powerLoad: 2000
-  - type: ExtensionCableReceiver
   - type: Appearance
   - type: GenericVisualizer
     visuals:
                 min: 1
                 max: 3
               SheetGlass1:
+                min: 1
+                max: 2
+          - !type:DoActsBehavior
+            acts: [ "Destruction" ]
+
+- type: entity
+  id: SpaceHeater
+  parent: [BaseMachinePowered, ConstructibleMachine]
+  name: space heater
+  description: A bluespace technology device that alters local temperature. Commonly referred to as a "Space Heater".
+  suffix: Unanchored
+  components:
+  - type: Transform
+    anchored: false
+  - type: Physics
+    bodyType: Dynamic
+  - type: AtmosDevice
+    joinSystem: true
+  - type: Fixtures
+    fixtures:
+      fix1:
+        shape:
+          !type:PhysShapeAabb
+          bounds: "-0.15,-0.35,0.15,0.5"
+        density: 100
+        mask:
+        - MachineMask
+        layer:
+        - MachineLayer
+  - type: ApcPowerReceiver
+    powerDisabled: true #starts off 
+  - type: Sprite
+    sprite: Structures/Piping/Atmospherics/Portable/portable_sheater.rsi
+    noRot: true
+    layers:
+    - state: sheaterOff
+      map: ["enum.SpaceHeaterVisualLayers.Main"]
+    - state: sheaterPanelOpen
+      map: [ "enum.WiresVisualLayers.MaintenancePanel" ]
+  - type: Appearance
+  - type: GenericVisualizer
+    visuals:
+      enum.SpaceHeaterVisuals.State:
+        SpaceHeaterVisualLayers.Main:
+          Off: { state: sheaterOff }
+          StandBy: { state: sheaterStandby }
+          Heating: { state: sheaterHeat }
+          Cooling: { state: sheaterCool }
+  - type: Machine
+    board: SpaceHeaterMachineCircuitBoard
+  - type: WiresPanel
+  - type: WiresVisuals
+  - type: UserInterface
+    interfaces:
+    - key: enum.SpaceHeaterUiKey.Key
+      type: SpaceHeaterBoundUserInterface
+  - type: ActivatableUI
+    inHandsOnly: false
+    key: enum.SpaceHeaterUiKey.Key
+  - type: SpaceHeater
+  - type: GasThermoMachine
+    temperatureTolerance: 0.2
+    atmospheric: true
+  - type: Damageable
+    damageContainer: Inorganic
+    damageModifierSet: Metallic
+  - type: Destructible
+    thresholds:
+      - trigger:
+          !type:DamageTrigger
+          damage: 600
+        behaviors:
+          - !type:DoActsBehavior
+            acts: [ "Destruction" ]
+      - trigger:
+          !type:DamageTrigger
+          damage: 300
+        behaviors:
+          - !type:PlaySoundBehavior
+            sound:
+              collection: MetalBreak
+          - !type:SpawnEntitiesBehavior
+            spawn:
+              SheetSteel1:
                 min: 1
                 max: 3
           - !type:DoActsBehavior
             acts: [ "Destruction" ]
-  - type: CollideOnAnchor
-    enable: true
-  - type: ContainerContainer
-    containers:
-      machine_board: !type:Container
-      machine_parts: !type:Container
+
+- type: entity
+  parent: SpaceHeater
+  id: SpaceHeaterAnchored
+  suffix: Anchored
+  components:
+  - type: Transform
+    anchored: true
+  - type: Physics
+    bodyType: Static
+
+- type: entity
+  parent: SpaceHeaterAnchored
+  id: SpaceHeaterEnabled
+  suffix: Anchored, Enabled
+  components:
+  - type: ApcPowerReceiver
+    powerDisabled: false
\ No newline at end of file
index 3af8eb4e5295a9438f42014717bcfb49cc0b1de0..f0c59a0bdf9151a811c3ff24ef14574624eb8966 100644 (file)
     Glass: 900
     Gold: 50
 
+- type: latheRecipe
+  id: SpaceHeaterMachineCircuitBoard
+  result: SpaceHeaterMachineCircuitBoard
+  category: Circuitry
+  completetime: 4
+  materials:
+    Steel: 150
+    Glass: 900
+    Gold: 50
+
 - type: latheRecipe
   id: MedicalScannerMachineCircuitboard
   result: MedicalScannerMachineCircuitboard
index fb38c8af945dbc51c6e010374ffd5c4015542b92..52772c51e0d3de6ef72a65372a695aefb94a5eae 100644 (file)
   recipeUnlocks:
   - HellfireFreezerMachineCircuitBoard
   - PortableScrubberMachineCircuitBoard
+  - SpaceHeaterMachineCircuitBoard
   - HolofanProjector
 
 - type: technology
diff --git a/Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_sheater.rsi/meta.json b/Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_sheater.rsi/meta.json
new file mode 100644 (file)
index 0000000..3b684ce
--- /dev/null
@@ -0,0 +1,50 @@
+{
+  "version": 1,
+  "license": "CC-BY-SA-3.0",
+  "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/ead7e05f990de05b7f5f93d39f9671498cb0aa01",
+  "size": {
+    "x": 32,
+    "y": 32
+  },
+  "states": [
+    {
+      "name": "sheaterOff"
+    },
+    {
+      "name": "sheaterCool",
+      "delays": [
+        [
+          0.2,
+                 0.2,
+                 0.2,
+                 0.2,
+                 0.2,
+                 0.2,
+                 0.2,
+                 0.2
+        ]
+      ]
+    },
+       {
+      "name": "sheaterHeat",
+      "delays": [
+        [
+          0.2,
+                 0.2,
+                 0.2,
+                 0.2,
+                 0.2,
+                 0.2,
+                 0.2,
+                 0.2
+        ]
+      ]
+    },
+    {
+      "name": "sheaterPanelOpen"
+    },
+    {
+      "name": "sheaterStandby"
+    }
+  ]
+}
diff --git a/Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_sheater.rsi/sheaterCool.png b/Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_sheater.rsi/sheaterCool.png
new file mode 100644 (file)
index 0000000..94682ca
Binary files /dev/null and b/Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_sheater.rsi/sheaterCool.png differ
diff --git a/Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_sheater.rsi/sheaterHeat.png b/Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_sheater.rsi/sheaterHeat.png
new file mode 100644 (file)
index 0000000..e33011d
Binary files /dev/null and b/Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_sheater.rsi/sheaterHeat.png differ
diff --git a/Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_sheater.rsi/sheaterOff.png b/Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_sheater.rsi/sheaterOff.png
new file mode 100644 (file)
index 0000000..3956908
Binary files /dev/null and b/Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_sheater.rsi/sheaterOff.png differ
diff --git a/Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_sheater.rsi/sheaterPanelOpen.png b/Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_sheater.rsi/sheaterPanelOpen.png
new file mode 100644 (file)
index 0000000..2ce6410
Binary files /dev/null and b/Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_sheater.rsi/sheaterPanelOpen.png differ
diff --git a/Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_sheater.rsi/sheaterStandby.png b/Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_sheater.rsi/sheaterStandby.png
new file mode 100644 (file)
index 0000000..e436eb1
Binary files /dev/null and b/Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_sheater.rsi/sheaterStandby.png differ