diff --git a/Content.Server/Chemistry/Components/ActiveSolutionHeaterComponent.cs b/Content.Server/Chemistry/Components/ActiveSolutionHeaterComponent.cs
index b94855ef39..e190c0ebd3 100644
--- a/Content.Server/Chemistry/Components/ActiveSolutionHeaterComponent.cs
+++ b/Content.Server/Chemistry/Components/ActiveSolutionHeaterComponent.cs
@@ -3,5 +3,4 @@
[RegisterComponent]
public sealed class ActiveSolutionHeaterComponent : Component
{
-
}
diff --git a/Content.Server/Chemistry/Components/SolutionHeaterComponent.cs b/Content.Server/Chemistry/Components/SolutionHeaterComponent.cs
index ddeb4ff055..2c5f5e5ab8 100644
--- a/Content.Server/Chemistry/Components/SolutionHeaterComponent.cs
+++ b/Content.Server/Chemistry/Components/SolutionHeaterComponent.cs
@@ -1,19 +1,50 @@
-namespace Content.Server.Chemistry.Components;
+using Content.Shared.Whitelist;
+
+namespace Content.Server.Chemistry.Components;
[RegisterComponent]
public sealed class SolutionHeaterComponent : Component
{
- public readonly string BeakerSlotId = "beakerSlot";
-
- [DataField("heatPerSecond")]
- public float HeatPerSecond = 120;
+ ///
+ /// How much heat is added per second to the solution, with no upgrades.
+ ///
+ [DataField("baseHeatPerSecond")]
+ public float BaseHeatPerSecond = 120;
+ ///
+ /// How much heat is added per second to the solution, taking upgrades into account.
+ ///
[ViewVariables(VVAccess.ReadWrite)]
- public float HeatMultiplier = 1;
+ public float HeatPerSecond;
- [DataField("machinePartHeatPerSecond")]
- public string MachinePartHeatPerSecond = "Capacitor";
+ ///
+ /// The machine part that affects the heat multiplier.
+ ///
+ [DataField("machinePartHeatMultiplier")]
+ public string MachinePartHeatMultiplier = "Capacitor";
+ ///
+ /// How much each upgrade multiplies the heat by.
+ ///
[DataField("partRatingHeatMultiplier")]
public float PartRatingHeatMultiplier = 1.5f;
+
+ ///
+ /// The entities that are placed on the heater.
+ ///
+ [DataField("placedEntities")]
+ public HashSet PlacedEntities = new();
+
+ ///
+ /// The max amount of entities that can be heated at the same time.
+ ///
+ [DataField("maxEntities")]
+ public uint MaxEntities = 1;
+
+ ///
+ /// Whitelist for entities that can be placed on the heater.
+ ///
+ [DataField("whitelist")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public EntityWhitelist? Whitelist;
}
diff --git a/Content.Server/Chemistry/EntitySystems/SolutionHeaterSystem.cs b/Content.Server/Chemistry/EntitySystems/SolutionHeaterSystem.cs
index 47f2d2d981..4e3db49e80 100644
--- a/Content.Server/Chemistry/EntitySystems/SolutionHeaterSystem.cs
+++ b/Content.Server/Chemistry/EntitySystems/SolutionHeaterSystem.cs
@@ -1,14 +1,22 @@
-using Content.Server.Chemistry.Components;
+using System.Linq;
+using Content.Server.Chemistry.Components;
using Content.Server.Chemistry.Components.SolutionManager;
using Content.Server.Construction;
using Content.Server.Power.Components;
-using Content.Shared.Containers.ItemSlots;
+using Content.Server.Power.EntitySystems;
+using Content.Shared.Chemistry;
+using Content.Shared.Placeable;
+using Robust.Shared.Physics.Events;
+using Robust.Shared.Physics.Systems;
namespace Content.Server.Chemistry.EntitySystems;
public sealed class SolutionHeaterSystem : EntitySystem
{
- [Dependency] private readonly ItemSlotsSystem _itemSlots = default!;
+ [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+ [Dependency] private readonly SharedPhysicsSystem _physics = default!;
+ [Dependency] private readonly PlaceableSurfaceSystem _placeableSurface = default!;
+ [Dependency] private readonly PowerReceiverSystem _powerReceiver = default!;
[Dependency] private readonly SolutionContainerSystem _solution = default!;
///
@@ -17,48 +25,101 @@ public sealed class SolutionHeaterSystem : EntitySystem
SubscribeLocalEvent(OnPowerChanged);
SubscribeLocalEvent(OnRefreshParts);
SubscribeLocalEvent(OnUpgradeExamine);
+ SubscribeLocalEvent(OnStartCollide);
+ SubscribeLocalEvent(OnEndCollide);
+ }
+
+ private void TurnOn(EntityUid uid)
+ {
+ _appearance.SetData(uid, SolutionHeaterVisuals.IsOn, true);
+ EnsureComp(uid);
+ }
+
+ public bool TryTurnOn(EntityUid uid, SolutionHeaterComponent component)
+ {
+ if (component.PlacedEntities.Count <= 0 || !_powerReceiver.IsPowered(uid))
+ return false;
+
+ TurnOn(uid);
+ return true;
+ }
+
+ public void TurnOff(EntityUid uid)
+ {
+ _appearance.SetData(uid, SolutionHeaterVisuals.IsOn, false);
+ RemComp(uid);
}
private void OnPowerChanged(EntityUid uid, SolutionHeaterComponent component, ref PowerChangedEvent args)
{
- if (args.Powered)
+ if (args.Powered && component.PlacedEntities.Count > 0)
{
- EnsureComp(uid);
+ TurnOn(uid);
}
else
{
- RemComp(uid);
+ TurnOff(uid);
}
}
private void OnRefreshParts(EntityUid uid, SolutionHeaterComponent component, RefreshPartsEvent args)
{
- var heatRating = args.PartRatings[component.MachinePartHeatPerSecond] - 1;
+ var heatRating = args.PartRatings[component.MachinePartHeatMultiplier] - 1;
- component.HeatMultiplier = MathF.Pow(component.PartRatingHeatMultiplier, heatRating);
+ component.HeatPerSecond = component.BaseHeatPerSecond * MathF.Pow(component.PartRatingHeatMultiplier, heatRating);
}
private void OnUpgradeExamine(EntityUid uid, SolutionHeaterComponent component, UpgradeExamineEvent args)
{
- args.AddPercentageUpgrade("solution-heater-upgrade-heat", component.HeatMultiplier);
+ args.AddPercentageUpgrade("solution-heater-upgrade-heat", component.HeatPerSecond / component.BaseHeatPerSecond);
+ }
+
+ private void OnStartCollide(EntityUid uid, SolutionHeaterComponent component, ref StartCollideEvent args)
+ {
+ if (component.Whitelist is not null && !component.Whitelist.IsValid(args.OtherEntity))
+ return;
+
+ // Disallow sleeping so we can detect when entity is removed from the heater.
+ _physics.SetSleepingAllowed(args.OtherEntity, args.OtherBody, false);
+
+ component.PlacedEntities.Add(args.OtherEntity);
+
+ TryTurnOn(uid, component);
+
+ if (component.PlacedEntities.Count >= component.MaxEntities)
+ _placeableSurface.SetPlaceable(uid, false);
+ }
+
+ private void OnEndCollide(EntityUid uid, SolutionHeaterComponent component, ref EndCollideEvent args)
+ {
+ // Re-allow sleeping.
+ _physics.SetSleepingAllowed(args.OtherEntity, args.OtherBody, true);
+
+ component.PlacedEntities.Remove(args.OtherEntity);
+
+ if (component.PlacedEntities.Count == 0) // Last entity was removed
+ TurnOff(uid);
+
+ _placeableSurface.SetPlaceable(uid, true);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
- foreach (var (_, heater) in EntityQuery())
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out _, out _, out var heater))
{
- if (_itemSlots.GetItemOrNull(heater.Owner, heater.BeakerSlotId) is not { } item)
- continue;
-
- if (!TryComp(item, out var solution))
- continue;
-
- var energy = heater.HeatPerSecond * heater.HeatMultiplier * frameTime;
- foreach (var s in solution.Solutions.Values)
+ foreach (var heatingEntity in heater.PlacedEntities.Take((int) heater.MaxEntities))
{
- _solution.AddThermalEnergy(solution.Owner, s, energy);
+ if (!TryComp(heatingEntity, out var solution))
+ continue;
+
+ var energy = heater.HeatPerSecond * frameTime;
+ foreach (var s in solution.Solutions.Values)
+ {
+ _solution.AddThermalEnergy(heatingEntity, s, energy);
+ }
}
}
}
diff --git a/Content.Shared/Chemistry/SharedSolutionHeater.cs b/Content.Shared/Chemistry/SharedSolutionHeater.cs
new file mode 100644
index 0000000000..32032a59f0
--- /dev/null
+++ b/Content.Shared/Chemistry/SharedSolutionHeater.cs
@@ -0,0 +1,9 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Chemistry;
+
+[Serializable, NetSerializable]
+public enum SolutionHeaterVisuals
+{
+ IsOn,
+}
diff --git a/Resources/Prototypes/Entities/Structures/Machines/hotplate.yml b/Resources/Prototypes/Entities/Structures/Machines/hotplate.yml
index ab7e9e65b5..0c5fa3989a 100644
--- a/Resources/Prototypes/Entities/Structures/Machines/hotplate.yml
+++ b/Resources/Prototypes/Entities/Structures/Machines/hotplate.yml
@@ -15,7 +15,10 @@
mask:
- TabletopMachineMask
layer:
- - TabletopMachineLayer
+ - Impassable
+ - MidImpassable
+ - LowImpassable
+ hard: false
- type: Sprite
sprite: Structures/Machines/hotplate.rsi
drawdepth: SmallObjects
@@ -23,16 +26,10 @@
layers:
- state: icon
- state: on
- map: ["enum.PowerDeviceVisualLayers.Powered"]
+ map: ["enum.SolutionHeaterVisuals.IsOn"]
shader: unshaded
- type: ApcPowerReceiver
powerLoad: 300
- - type: ItemSlots
- slots:
- beakerSlot:
- whitelist:
- components:
- - FitsInDispenser
- type: ItemMapper
sprite: Structures/Machines/hotplate.rsi
mapLayers:
@@ -41,17 +38,22 @@
components:
- FitsInDispenser
- type: SolutionHeater
+ whitelist:
+ components:
+ - FitsInDispenser
+ - type: PlaceableSurface
+ placeCentered: true
+ positionOffset: 0, 0.25
- type: Machine
board: HotplateMachineCircuitboard
- type: Appearance
- type: ContainerContainer
containers:
- beakerSlot: !type:ContainerSlot
machine_board: !type:Container
machine_parts: !type:Container
- type: GenericVisualizer
visuals:
- enum.PowerDeviceVisuals.Powered:
- enum.PowerDeviceVisualLayers.Powered:
+ enum.SolutionHeaterVisuals.IsOn:
+ enum.SolutionHeaterVisuals.IsOn:
True: { visible: true }
False: { visible: false }