Add internal temperatures for cooking meats (#20659)

This commit is contained in:
deltanedas
2023-10-20 21:21:49 +01:00
committed by GitHub
parent 3a561ed993
commit 68aa295a38
6 changed files with 158 additions and 22 deletions

View File

@@ -35,7 +35,8 @@ public sealed class ThermalRegulatorSystem : EntitySystem
// implicit heat regulation // implicit heat regulation
var tempDiff = Math.Abs(temperatureComponent.CurrentTemperature - comp.NormalBodyTemperature); var tempDiff = Math.Abs(temperatureComponent.CurrentTemperature - comp.NormalBodyTemperature);
var targetHeat = tempDiff * temperatureComponent.HeatCapacity; var heatCapacity = _tempSys.GetHeatCapacity(uid, temperatureComponent);
var targetHeat = tempDiff * heatCapacity;
if (temperatureComponent.CurrentTemperature > comp.NormalBodyTemperature) if (temperatureComponent.CurrentTemperature > comp.NormalBodyTemperature)
{ {
totalMetabolismTempChange -= Math.Min(targetHeat, comp.ImplicitHeatRegulation); totalMetabolismTempChange -= Math.Min(targetHeat, comp.ImplicitHeatRegulation);
@@ -49,7 +50,7 @@ public sealed class ThermalRegulatorSystem : EntitySystem
// recalc difference and target heat // recalc difference and target heat
tempDiff = Math.Abs(temperatureComponent.CurrentTemperature - comp.NormalBodyTemperature); tempDiff = Math.Abs(temperatureComponent.CurrentTemperature - comp.NormalBodyTemperature);
targetHeat = tempDiff * temperatureComponent.HeatCapacity; targetHeat = tempDiff * heatCapacity;
// if body temperature is not within comfortable, thermal regulation // if body temperature is not within comfortable, thermal regulation
// processes starts // processes starts

View File

@@ -380,16 +380,28 @@ namespace Content.Server.Construction
if (ev is not OnTemperatureChangeEvent) if (ev is not OnTemperatureChangeEvent)
break; break;
if (TryComp<TemperatureComponent>(uid, out var tempComp)) // prefer using InternalTemperature since that's more accurate for cooking.
float temp;
if (TryComp<InternalTemperatureComponent>(uid, out var internalTemp))
{ {
if ((!temperatureChangeStep.MinTemperature.HasValue || tempComp.CurrentTemperature >= temperatureChangeStep.MinTemperature.Value) && temp = internalTemp.Temperature;
(!temperatureChangeStep.MaxTemperature.HasValue || tempComp.CurrentTemperature <= temperatureChangeStep.MaxTemperature.Value)) }
{ else if (TryComp<TemperatureComponent>(uid, out var tempComp))
return HandleResult.True; {
} temp = tempComp.CurrentTemperature;
}
else
{
return HandleResult.False;
} }
return HandleResult.False;
if ((!temperatureChangeStep.MinTemperature.HasValue || temp >= temperatureChangeStep.MinTemperature.Value) &&
(!temperatureChangeStep.MaxTemperature.HasValue || temp <= temperatureChangeStep.MaxTemperature.Value))
{
return HandleResult.True;
}
return HandleResult.False;
} }
case PartAssemblyConstructionGraphStep partAssemblyStep: case PartAssemblyConstructionGraphStep partAssemblyStep:

View File

@@ -0,0 +1,48 @@
using Content.Server.Temperature.Systems;
namespace Content.Server.Temperature.Components;
/// <summary>
/// Entity has an internal temperature which conducts heat from its surface.
/// Requires <see cref="TemperatureComponent"/> to function.
/// </summary>
/// <remarks>
/// Currently this is only used for cooking but animal metabolism could use it too.
/// Too hot? Suffering heatstroke, start sweating to cool off and increase thirst.
/// Too cold? Suffering hypothermia, start shivering to warm up and increase hunger.
/// </remarks>
[RegisterComponent, Access(typeof(TemperatureSystem))]
public sealed partial class InternalTemperatureComponent : Component
{
/// <summary>
/// Internal temperature which is modified by surface temperature.
/// This gets set to <see cref="TemperatureComponent.CurrentTemperature"/> on mapinit.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public float Temperature;
/// <summary>
/// Thermal conductivity of the material in W/m/K.
/// Higher conductivity means its insides will heat up faster.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public float Conductivity = 0.5f;
/// <summary>
/// Average thickness between the surface and the inside.
/// For meats and such this is constant.
/// Thicker materials take longer for heat to dissipate.
/// </summary>
[DataField(required: true), ViewVariables(VVAccess.ReadWrite)]
public float Thickness;
/// <summary>
/// Surface area in m^2 for the purpose of conducting surface temperature to the inside.
/// Larger surface area means it takes longer to heat up/cool down
/// </summary>
/// <remarks>
/// For meats etc this should just be the area of the cooked surface not the whole thing as it's only getting heat from one side usually.
/// </remarks>
[DataField(required: true), ViewVariables(VVAccess.ReadWrite)]
public float Area;
}

View File

@@ -1,8 +1,7 @@
using Content.Server.Temperature.Systems;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.Damage; using Content.Shared.Damage;
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
namespace Content.Server.Temperature.Components; namespace Content.Server.Temperature.Components;
@@ -14,6 +13,9 @@ namespace Content.Server.Temperature.Components;
[RegisterComponent] [RegisterComponent]
public sealed partial class TemperatureComponent : Component public sealed partial class TemperatureComponent : Component
{ {
/// <summary>
/// Surface temperature which is modified by the environment.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)] [DataField, ViewVariables(VVAccess.ReadWrite)]
public float CurrentTemperature = Atmospherics.T20C; public float CurrentTemperature = Atmospherics.T20C;
@@ -47,16 +49,12 @@ public sealed partial class TemperatureComponent : Component
[DataField, ViewVariables(VVAccess.ReadWrite)] [DataField, ViewVariables(VVAccess.ReadWrite)]
public float AtmosTemperatureTransferEfficiency = 0.1f; public float AtmosTemperatureTransferEfficiency = 0.1f;
[ViewVariables] public float HeatCapacity [Obsolete("Use system method")]
public float HeatCapacity
{ {
get get
{ {
if (IoCManager.Resolve<IEntityManager>().TryGetComponent<PhysicsComponent>(Owner, out var physics) && physics.FixturesMass != 0) return IoCManager.Resolve<IEntityManager>().System<TemperatureSystem>().GetHeatCapacity(Owner, this);
{
return SpecificHeat * physics.FixturesMass;
}
return Atmospherics.MinimumHeatCapacity;
} }
} }

View File

@@ -12,6 +12,7 @@ using Content.Shared.Inventory;
using Content.Shared.Rejuvenate; using Content.Shared.Rejuvenate;
using Content.Shared.Temperature; using Content.Shared.Temperature;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Physics.Components;
namespace Content.Server.Temperature.Systems; namespace Content.Server.Temperature.Systems;
@@ -19,8 +20,8 @@ public sealed class TemperatureSystem : EntitySystem
{ {
[Dependency] private readonly AlertsSystem _alerts = default!; [Dependency] private readonly AlertsSystem _alerts = default!;
[Dependency] private readonly AtmosphereSystem _atmosphere = default!; [Dependency] private readonly AtmosphereSystem _atmosphere = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly DamageableSystem _damageable = default!; [Dependency] private readonly DamageableSystem _damageable = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly TransformSystem _transform = default!; [Dependency] private readonly TransformSystem _transform = default!;
/// <summary> /// <summary>
@@ -43,6 +44,8 @@ public sealed class TemperatureSystem : EntitySystem
SubscribeLocalEvent<TemperatureProtectionComponent, InventoryRelayedEvent<ModifyChangedTemperatureEvent>>( SubscribeLocalEvent<TemperatureProtectionComponent, InventoryRelayedEvent<ModifyChangedTemperatureEvent>>(
OnTemperatureChangeAttempt); OnTemperatureChangeAttempt);
SubscribeLocalEvent<InternalTemperatureComponent, MapInitEvent>(OnInit);
// Allows overriding thresholds based on the parent's thresholds. // Allows overriding thresholds based on the parent's thresholds.
SubscribeLocalEvent<TemperatureComponent, EntParentChangedMessage>(OnParentChange); SubscribeLocalEvent<TemperatureComponent, EntParentChangedMessage>(OnParentChange);
SubscribeLocalEvent<ContainerTemperatureDamageThresholdsComponent, ComponentStartup>( SubscribeLocalEvent<ContainerTemperatureDamageThresholdsComponent, ComponentStartup>(
@@ -55,6 +58,34 @@ public sealed class TemperatureSystem : EntitySystem
{ {
base.Update(frameTime); base.Update(frameTime);
// conduct heat from the surface to the inside of entities with internal temperatures
var query = EntityQueryEnumerator<InternalTemperatureComponent, TemperatureComponent>();
while (query.MoveNext(out var uid, out var comp, out var temp))
{
// don't do anything if they equalised
var diff = Math.Abs(temp.CurrentTemperature - comp.Temperature);
if (diff < 0.1f)
continue;
// heat flow in W/m^2 as per fourier's law in 1D.
var q = comp.Conductivity * diff / comp.Thickness;
// convert to J then K
var joules = q * comp.Area * frameTime;
var degrees = joules / GetHeatCapacity(uid, temp);
if (temp.CurrentTemperature < comp.Temperature)
degrees *= -1;
// exchange heat between inside and surface
comp.Temperature += degrees;
ForceChangeTemperature(uid, temp.CurrentTemperature - degrees, temp);
}
UpdateDamage(frameTime);
}
private void UpdateDamage(float frameTime)
{
_accumulatedFrametime += frameTime; _accumulatedFrametime += frameTime;
if (_accumulatedFrametime < UpdateInterval) if (_accumulatedFrametime < UpdateInterval)
@@ -104,7 +135,7 @@ public sealed class TemperatureSystem : EntitySystem
} }
float lastTemp = temperature.CurrentTemperature; float lastTemp = temperature.CurrentTemperature;
temperature.CurrentTemperature += heatAmount / temperature.HeatCapacity; temperature.CurrentTemperature += heatAmount / GetHeatCapacity(uid, temperature);
float delta = temperature.CurrentTemperature - lastTemp; float delta = temperature.CurrentTemperature - lastTemp;
RaiseLocalEvent(uid, new OnTemperatureChangeEvent(temperature.CurrentTemperature, lastTemp, delta), true); RaiseLocalEvent(uid, new OnTemperatureChangeEvent(temperature.CurrentTemperature, lastTemp, delta), true);
@@ -114,6 +145,7 @@ public sealed class TemperatureSystem : EntitySystem
ref AtmosExposedUpdateEvent args) ref AtmosExposedUpdateEvent args)
{ {
var transform = args.Transform; var transform = args.Transform;
if (transform.MapUid == null) if (transform.MapUid == null)
return; return;
@@ -122,11 +154,30 @@ public sealed class TemperatureSystem : EntitySystem
var temperatureDelta = args.GasMixture.Temperature - temperature.CurrentTemperature; var temperatureDelta = args.GasMixture.Temperature - temperature.CurrentTemperature;
var tileHeatCapacity = var tileHeatCapacity =
_atmosphere.GetTileHeatCapacity(transform.GridUid, transform.MapUid.Value, position); _atmosphere.GetTileHeatCapacity(transform.GridUid, transform.MapUid.Value, position);
var heat = temperatureDelta * (tileHeatCapacity * temperature.HeatCapacity / var heatCapacity = GetHeatCapacity(uid, temperature);
(tileHeatCapacity + temperature.HeatCapacity)); var heat = temperatureDelta * (tileHeatCapacity * heatCapacity /
(tileHeatCapacity + heatCapacity));
ChangeHeat(uid, heat * temperature.AtmosTemperatureTransferEfficiency, temperature: temperature); ChangeHeat(uid, heat * temperature.AtmosTemperatureTransferEfficiency, temperature: temperature);
} }
public float GetHeatCapacity(EntityUid uid, TemperatureComponent? comp = null, PhysicsComponent? physics = null)
{
if (!Resolve(uid, ref comp) || !Resolve(uid, ref physics, false) || physics.FixturesMass <= 0)
{
return Atmospherics.MinimumHeatCapacity;
}
return comp.SpecificHeat * physics.FixturesMass;
}
private void OnInit(EntityUid uid, InternalTemperatureComponent comp, MapInitEvent args)
{
if (!TryComp<TemperatureComponent>(uid, out var temp))
return;
comp.Temperature = temp.CurrentTemperature;
}
private void OnRejuvenate(EntityUid uid, TemperatureComponent comp, RejuvenateEvent args) private void OnRejuvenate(EntityUid uid, TemperatureComponent comp, RejuvenateEvent args)
{ {
ForceChangeTemperature(uid, Atmospherics.T20C, comp); ForceChangeTemperature(uid, Atmospherics.T20C, comp);

View File

@@ -25,10 +25,26 @@
Quantity: 5 Quantity: 5
- type: Item - type: Item
size: 5 size: 5
- type: Fixtures
fixtures:
fix1:
shape:
!type:PhysShapeAabb
bounds: "-0.25,-0.25,0.25,0.25"
# less mass so it can cook faster, a single strip of bacon isnt 5kg
density: 1
mask:
- ItemMask
restitution: 0.3 # fite me
friction: 0.2
# let air cook and freeze meat for cooking and preservation # let air cook and freeze meat for cooking and preservation
- type: AtmosExposed - type: AtmosExposed
- type: Temperature - type: Temperature
currentTemperature: 290 currentTemperature: 290
# required for cooking to work
- type: InternalTemperature
thickness: 0.02
area: 0.02 # arbitrary number that sounds right for a slab of meat
- type: Material - type: Material
- type: PhysicalComposition - type: PhysicalComposition
materialComposition: materialComposition:
@@ -84,6 +100,8 @@
- type: SliceableFood - type: SliceableFood
count: 3 count: 3
slice: FoodMeatCutlet slice: FoodMeatCutlet
- type: InternalTemperature
conductivity: 0.43
- type: Construction - type: Construction
graph: MeatSteak graph: MeatSteak
node: start node: start
@@ -159,6 +177,10 @@
Quantity: 2 Quantity: 2
- ReagentId: Fat - ReagentId: Fat
Quantity: 9 Quantity: 9
- type: InternalTemperature
conductivity: 0.44
thickness: 0.004 # bacon is thin so faster to cook than a steak
area: 0.0075 # ~5x15cm
- type: Construction - type: Construction
graph: Bacon graph: Bacon
node: start node: start
@@ -232,6 +254,8 @@
- type: SliceableFood - type: SliceableFood
count: 3 count: 3
slice: FoodMeatChickenCutlet slice: FoodMeatChickenCutlet
- type: InternalTemperature
conductivity: 0.41
- type: Construction - type: Construction
graph: ChickenSteak graph: ChickenSteak
node: start node: start
@@ -324,6 +348,8 @@
Quantity: 5 Quantity: 5
- ReagentId: Fat - ReagentId: Fat
Quantity: 3 Quantity: 3
- type: InternalTemperature
thickness: 0.1 # very big, do cook it in lava
- type: Construction - type: Construction
graph: GoliathSteak graph: GoliathSteak
node: start node: start