diff --git a/Content.Server/Body/Systems/ThermalRegulatorSystem.cs b/Content.Server/Body/Systems/ThermalRegulatorSystem.cs index 60d2e389da..a9556be773 100644 --- a/Content.Server/Body/Systems/ThermalRegulatorSystem.cs +++ b/Content.Server/Body/Systems/ThermalRegulatorSystem.cs @@ -35,7 +35,8 @@ public sealed class ThermalRegulatorSystem : EntitySystem // implicit heat regulation 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) { totalMetabolismTempChange -= Math.Min(targetHeat, comp.ImplicitHeatRegulation); @@ -49,7 +50,7 @@ public sealed class ThermalRegulatorSystem : EntitySystem // recalc difference and target heat tempDiff = Math.Abs(temperatureComponent.CurrentTemperature - comp.NormalBodyTemperature); - targetHeat = tempDiff * temperatureComponent.HeatCapacity; + targetHeat = tempDiff * heatCapacity; // if body temperature is not within comfortable, thermal regulation // processes starts diff --git a/Content.Server/Construction/ConstructionSystem.Interactions.cs b/Content.Server/Construction/ConstructionSystem.Interactions.cs index 21daabdb5d..11dd0b2bf0 100644 --- a/Content.Server/Construction/ConstructionSystem.Interactions.cs +++ b/Content.Server/Construction/ConstructionSystem.Interactions.cs @@ -380,16 +380,28 @@ namespace Content.Server.Construction if (ev is not OnTemperatureChangeEvent) break; - if (TryComp(uid, out var tempComp)) + // prefer using InternalTemperature since that's more accurate for cooking. + float temp; + if (TryComp(uid, out var internalTemp)) { - if ((!temperatureChangeStep.MinTemperature.HasValue || tempComp.CurrentTemperature >= temperatureChangeStep.MinTemperature.Value) && - (!temperatureChangeStep.MaxTemperature.HasValue || tempComp.CurrentTemperature <= temperatureChangeStep.MaxTemperature.Value)) - { - return HandleResult.True; - } + temp = internalTemp.Temperature; + } + else if (TryComp(uid, out var tempComp)) + { + 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: diff --git a/Content.Server/Temperature/Components/InternalTemperatureComponent.cs b/Content.Server/Temperature/Components/InternalTemperatureComponent.cs new file mode 100644 index 0000000000..1e456d4410 --- /dev/null +++ b/Content.Server/Temperature/Components/InternalTemperatureComponent.cs @@ -0,0 +1,48 @@ +using Content.Server.Temperature.Systems; + +namespace Content.Server.Temperature.Components; + +/// +/// Entity has an internal temperature which conducts heat from its surface. +/// Requires to function. +/// +/// +/// 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. +/// +[RegisterComponent, Access(typeof(TemperatureSystem))] +public sealed partial class InternalTemperatureComponent : Component +{ + /// + /// Internal temperature which is modified by surface temperature. + /// This gets set to on mapinit. + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float Temperature; + + /// + /// Thermal conductivity of the material in W/m/K. + /// Higher conductivity means its insides will heat up faster. + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float Conductivity = 0.5f; + + /// + /// Average thickness between the surface and the inside. + /// For meats and such this is constant. + /// Thicker materials take longer for heat to dissipate. + /// + [DataField(required: true), ViewVariables(VVAccess.ReadWrite)] + public float Thickness; + + /// + /// 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 + /// + /// + /// 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. + /// + [DataField(required: true), ViewVariables(VVAccess.ReadWrite)] + public float Area; +} diff --git a/Content.Server/Temperature/Components/TemperatureComponent.cs b/Content.Server/Temperature/Components/TemperatureComponent.cs index 7330ebf9ba..ec00a570f9 100644 --- a/Content.Server/Temperature/Components/TemperatureComponent.cs +++ b/Content.Server/Temperature/Components/TemperatureComponent.cs @@ -1,8 +1,7 @@ +using Content.Server.Temperature.Systems; using Content.Shared.Atmos; using Content.Shared.Damage; using Content.Shared.FixedPoint; -using Robust.Shared.Physics; -using Robust.Shared.Physics.Components; namespace Content.Server.Temperature.Components; @@ -14,6 +13,9 @@ namespace Content.Server.Temperature.Components; [RegisterComponent] public sealed partial class TemperatureComponent : Component { + /// + /// Surface temperature which is modified by the environment. + /// [DataField, ViewVariables(VVAccess.ReadWrite)] public float CurrentTemperature = Atmospherics.T20C; @@ -47,16 +49,12 @@ public sealed partial class TemperatureComponent : Component [DataField, ViewVariables(VVAccess.ReadWrite)] public float AtmosTemperatureTransferEfficiency = 0.1f; - [ViewVariables] public float HeatCapacity + [Obsolete("Use system method")] + public float HeatCapacity { get { - if (IoCManager.Resolve().TryGetComponent(Owner, out var physics) && physics.FixturesMass != 0) - { - return SpecificHeat * physics.FixturesMass; - } - - return Atmospherics.MinimumHeatCapacity; + return IoCManager.Resolve().System().GetHeatCapacity(Owner, this); } } diff --git a/Content.Server/Temperature/Systems/TemperatureSystem.cs b/Content.Server/Temperature/Systems/TemperatureSystem.cs index 27521cadf6..646f60eb63 100644 --- a/Content.Server/Temperature/Systems/TemperatureSystem.cs +++ b/Content.Server/Temperature/Systems/TemperatureSystem.cs @@ -12,6 +12,7 @@ using Content.Shared.Inventory; using Content.Shared.Rejuvenate; using Content.Shared.Temperature; using Robust.Server.GameObjects; +using Robust.Shared.Physics.Components; namespace Content.Server.Temperature.Systems; @@ -19,8 +20,8 @@ public sealed class TemperatureSystem : EntitySystem { [Dependency] private readonly AlertsSystem _alerts = default!; [Dependency] private readonly AtmosphereSystem _atmosphere = default!; - [Dependency] private readonly IAdminLogManager _adminLogger = default!; [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly IAdminLogManager _adminLogger = default!; [Dependency] private readonly TransformSystem _transform = default!; /// @@ -43,6 +44,8 @@ public sealed class TemperatureSystem : EntitySystem SubscribeLocalEvent>( OnTemperatureChangeAttempt); + SubscribeLocalEvent(OnInit); + // Allows overriding thresholds based on the parent's thresholds. SubscribeLocalEvent(OnParentChange); SubscribeLocalEvent( @@ -55,6 +58,34 @@ public sealed class TemperatureSystem : EntitySystem { base.Update(frameTime); + // conduct heat from the surface to the inside of entities with internal temperatures + var query = EntityQueryEnumerator(); + 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; if (_accumulatedFrametime < UpdateInterval) @@ -104,7 +135,7 @@ public sealed class TemperatureSystem : EntitySystem } float lastTemp = temperature.CurrentTemperature; - temperature.CurrentTemperature += heatAmount / temperature.HeatCapacity; + temperature.CurrentTemperature += heatAmount / GetHeatCapacity(uid, temperature); float delta = temperature.CurrentTemperature - lastTemp; RaiseLocalEvent(uid, new OnTemperatureChangeEvent(temperature.CurrentTemperature, lastTemp, delta), true); @@ -114,6 +145,7 @@ public sealed class TemperatureSystem : EntitySystem ref AtmosExposedUpdateEvent args) { var transform = args.Transform; + if (transform.MapUid == null) return; @@ -122,11 +154,30 @@ public sealed class TemperatureSystem : EntitySystem var temperatureDelta = args.GasMixture.Temperature - temperature.CurrentTemperature; var tileHeatCapacity = _atmosphere.GetTileHeatCapacity(transform.GridUid, transform.MapUid.Value, position); - var heat = temperatureDelta * (tileHeatCapacity * temperature.HeatCapacity / - (tileHeatCapacity + temperature.HeatCapacity)); + var heatCapacity = GetHeatCapacity(uid, temperature); + var heat = temperatureDelta * (tileHeatCapacity * heatCapacity / + (tileHeatCapacity + heatCapacity)); 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(uid, out var temp)) + return; + + comp.Temperature = temp.CurrentTemperature; + } + private void OnRejuvenate(EntityUid uid, TemperatureComponent comp, RejuvenateEvent args) { ForceChangeTemperature(uid, Atmospherics.T20C, comp); diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/meat.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/meat.yml index 21a100841d..35057aeacd 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/meat.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/meat.yml @@ -25,10 +25,26 @@ Quantity: 5 - type: Item 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 - type: AtmosExposed - type: Temperature 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: PhysicalComposition materialComposition: @@ -84,6 +100,8 @@ - type: SliceableFood count: 3 slice: FoodMeatCutlet + - type: InternalTemperature + conductivity: 0.43 - type: Construction graph: MeatSteak node: start @@ -159,6 +177,10 @@ Quantity: 2 - ReagentId: Fat 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 graph: Bacon node: start @@ -232,6 +254,8 @@ - type: SliceableFood count: 3 slice: FoodMeatChickenCutlet + - type: InternalTemperature + conductivity: 0.41 - type: Construction graph: ChickenSteak node: start @@ -324,6 +348,8 @@ Quantity: 5 - ReagentId: Fat Quantity: 3 + - type: InternalTemperature + thickness: 0.1 # very big, do cook it in lava - type: Construction graph: GoliathSteak node: start