Add internal temperatures for cooking meats (#20659)
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user