--- /dev/null
+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();
+ }
+}
--- /dev/null
+<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>
--- /dev/null
+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;
+ }
+}
+
private void OnThermoMachineUpdated(EntityUid uid, GasThermoMachineComponent thermoMachine, ref AtmosDeviceUpdateEvent args)
{
+ thermoMachine.LastEnergyDelta = 0f;
if (!(_power.IsPowered(uid) && TryComp<ApcPowerReceiverComponent>(uid, out var receiver)))
return;
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);
--- /dev/null
+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;
+}
--- /dev/null
+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);
+ }
+ }
+}
--- /dev/null
+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
+}
--- /dev/null
+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,
+}
--- /dev/null
+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.
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.
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
- ThermomachineFreezerMachineCircuitBoard
- HellfireFreezerMachineCircuitBoard
- PortableScrubberMachineCircuitBoard
+ - SpaceHeaterMachineCircuitBoard
- CloningPodMachineCircuitboard
- MedicalScannerMachineCircuitboard
- CryoPodMachineCircuitboard
- 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:
shader: unshaded
visible: false
map: ["enum.PortableScrubberVisualLayers.IsDraining"]
- - type: Pullable
- - type: AtmosDevice
- joinSystem: true
- type: PortableScrubber
gasMixture:
volume: 1250
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
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
recipeUnlocks:
- HellfireFreezerMachineCircuitBoard
- PortableScrubberMachineCircuitBoard
+ - SpaceHeaterMachineCircuitBoard
- HolofanProjector
- type: technology
--- /dev/null
+{
+ "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"
+ }
+ ]
+}