diff --git a/Content.Client/Entry/IgnoredComponents.cs b/Content.Client/Entry/IgnoredComponents.cs index c6f7151d20..d49aa2f81e 100644 --- a/Content.Client/Entry/IgnoredComponents.cs +++ b/Content.Client/Entry/IgnoredComponents.cs @@ -50,6 +50,7 @@ namespace Content.Client.Entry "AccessReader", "IdCardConsole", "Airlock", + "ThermalRegulator", "AtmosFixMarker", "CablePlacer", "Drink", diff --git a/Content.IntegrationTests/Tests/Body/LungTest.cs b/Content.IntegrationTests/Tests/Body/LungTest.cs index f97e8533ee..3e3acd1a43 100644 --- a/Content.IntegrationTests/Tests/Body/LungTest.cs +++ b/Content.IntegrationTests/Tests/Body/LungTest.cs @@ -32,7 +32,7 @@ namespace Content.IntegrationTests.Tests.Body template: HumanoidTemplate preset: HumanPreset centerSlot: torso - - type: Respirator + - type: ThermalRegulator metabolismHeat: 5000 radiatedHeat: 400 implicitHeatRegulation: 5000 @@ -40,6 +40,7 @@ namespace Content.IntegrationTests.Tests.Body shiveringHeatRegulation: 5000 normalBodyTemperature: 310.15 thermalRegulationTemperatureThreshold: 25 + - type: Respirator needsGases: Oxygen: 0.00060763888 producesGases: diff --git a/Content.Server/Administration/Commands/AddBodyPartCommand.cs b/Content.Server/Administration/Commands/AddBodyPartCommand.cs index 912b0d44e5..f9cf3cfd97 100644 --- a/Content.Server/Administration/Commands/AddBodyPartCommand.cs +++ b/Content.Server/Administration/Commands/AddBodyPartCommand.cs @@ -1,7 +1,4 @@ -using Content.Server.Body; using Content.Server.Body.Components; -using Content.Server.Body.Part; -using Content.Server.Storage.Components; using Content.Shared.Administration; using Robust.Shared.Console; using Robust.Shared.GameObjects; diff --git a/Content.Server/Administration/Commands/AddEntityStorageCommand.cs b/Content.Server/Administration/Commands/AddEntityStorageCommand.cs index 6e602c8e00..85da28ad53 100644 --- a/Content.Server/Administration/Commands/AddEntityStorageCommand.cs +++ b/Content.Server/Administration/Commands/AddEntityStorageCommand.cs @@ -1,4 +1,3 @@ -using Content.Server.Body.Part; using Content.Server.Storage.Components; using Content.Shared.Administration; using Robust.Shared.Console; diff --git a/Content.Server/Administration/Commands/AddMechanismCommand.cs b/Content.Server/Administration/Commands/AddMechanismCommand.cs index 05bff1498b..b71d0f384c 100644 --- a/Content.Server/Administration/Commands/AddMechanismCommand.cs +++ b/Content.Server/Administration/Commands/AddMechanismCommand.cs @@ -1,7 +1,4 @@ -using Content.Server.Body; using Content.Server.Body.Components; -using Content.Server.Body.Part; -using Content.Server.Storage.Components; using Content.Shared.Administration; using Robust.Shared.Console; using Robust.Shared.GameObjects; diff --git a/Content.Server/Administration/Commands/RemoveBodyPartCommand.cs b/Content.Server/Administration/Commands/RemoveBodyPartCommand.cs index 3aaed66b97..9d9822c532 100644 --- a/Content.Server/Administration/Commands/RemoveBodyPartCommand.cs +++ b/Content.Server/Administration/Commands/RemoveBodyPartCommand.cs @@ -1,6 +1,4 @@ -using Content.Server.Body; using Content.Server.Body.Components; -using Content.Server.Body.Part; using Content.Shared.Administration; using Robust.Shared.Console; using Robust.Shared.GameObjects; diff --git a/Content.Server/Administration/Commands/RemoveEntityStorageCommand.cs b/Content.Server/Administration/Commands/RemoveEntityStorageCommand.cs index e296c5d338..fb4cc0698d 100644 --- a/Content.Server/Administration/Commands/RemoveEntityStorageCommand.cs +++ b/Content.Server/Administration/Commands/RemoveEntityStorageCommand.cs @@ -1,4 +1,3 @@ -using Content.Server.Body.Part; using Content.Server.Storage.Components; using Content.Shared.Administration; using Robust.Shared.Console; diff --git a/Content.Server/Administration/Commands/RemoveMechanismCommand.cs b/Content.Server/Administration/Commands/RemoveMechanismCommand.cs index a8eecd9961..1794580866 100644 --- a/Content.Server/Administration/Commands/RemoveMechanismCommand.cs +++ b/Content.Server/Administration/Commands/RemoveMechanismCommand.cs @@ -1,6 +1,4 @@ -using Content.Server.Body; using Content.Server.Body.Components; -using Content.Server.Body.Part; using Content.Shared.Administration; using Robust.Shared.Console; using Robust.Shared.GameObjects; diff --git a/Content.Server/Body/Commands/AttachBodyPartCommand.cs b/Content.Server/Body/Commands/AttachBodyPartCommand.cs index 494c4bcfc2..4264b5cf8e 100644 --- a/Content.Server/Body/Commands/AttachBodyPartCommand.cs +++ b/Content.Server/Body/Commands/AttachBodyPartCommand.cs @@ -1,12 +1,10 @@ using Content.Server.Administration; using Content.Shared.Administration; using Content.Shared.Body.Components; -using Content.Shared.Body.Part; using Robust.Server.Player; using Robust.Shared.Console; using Robust.Shared.GameObjects; using Robust.Shared.IoC; -using static Content.Server.Body.Part.BodyPartComponent; namespace Content.Server.Body.Commands { diff --git a/Content.Server/Body/Components/BloodstreamComponent.cs b/Content.Server/Body/Components/BloodstreamComponent.cs index 9852f969e1..603a961bbd 100644 --- a/Content.Server/Body/Components/BloodstreamComponent.cs +++ b/Content.Server/Body/Components/BloodstreamComponent.cs @@ -1,6 +1,7 @@ using System; using Content.Server.Atmos; using Content.Server.Atmos.EntitySystems; +using Content.Server.Body.Systems; using Content.Server.Chemistry.EntitySystems; using Content.Shared.Atmos; using Content.Shared.Body.Components; @@ -73,6 +74,7 @@ namespace Content.Server.Body.Components public void PumpToxins(GasMixture to) { var atmosphereSystem = EntitySystem.Get(); + var respiratorSystem = EntitySystem.Get(); if (!Owner.TryGetComponent(out RespiratorComponent? metabolism)) { @@ -81,7 +83,7 @@ namespace Content.Server.Body.Components return; } - var toxins = metabolism.Clean(this); + var toxins = respiratorSystem.Clean(OwnerUid, metabolism, this); var toOld = new float[to.Moles.Length]; Array.Copy(to.Moles, toOld, toOld.Length); diff --git a/Content.Server/Body/Part/BodyPartComponent.cs b/Content.Server/Body/Components/BodyPartComponent.cs similarity index 99% rename from Content.Server/Body/Part/BodyPartComponent.cs rename to Content.Server/Body/Components/BodyPartComponent.cs index 8c47adb572..e9cceeb7c6 100644 --- a/Content.Server/Body/Part/BodyPartComponent.cs +++ b/Content.Server/Body/Components/BodyPartComponent.cs @@ -18,7 +18,7 @@ using Robust.Shared.Localization; using Robust.Shared.Log; using Robust.Shared.ViewVariables; -namespace Content.Server.Body.Part +namespace Content.Server.Body.Components { [RegisterComponent] [ComponentReference(typeof(SharedBodyPartComponent))] diff --git a/Content.Server/Body/Scanner/BodyScannerComponent.cs b/Content.Server/Body/Components/BodyScannerComponent.cs similarity index 97% rename from Content.Server/Body/Scanner/BodyScannerComponent.cs rename to Content.Server/Body/Components/BodyScannerComponent.cs index 9c178bfd59..bd17d36887 100644 --- a/Content.Server/Body/Scanner/BodyScannerComponent.cs +++ b/Content.Server/Body/Components/BodyScannerComponent.cs @@ -5,7 +5,7 @@ using Robust.Server.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.ViewVariables; -namespace Content.Server.Body.Scanner +namespace Content.Server.Body.Components { [RegisterComponent] [ComponentReference(typeof(IActivate))] diff --git a/Content.Server/Body/Components/RespiratorComponent.cs b/Content.Server/Body/Components/RespiratorComponent.cs index effc3325e7..d379125b5d 100644 --- a/Content.Server/Body/Components/RespiratorComponent.cs +++ b/Content.Server/Body/Components/RespiratorComponent.cs @@ -6,6 +6,7 @@ using Content.Server.Alert; using Content.Server.Atmos; using Content.Server.Atmos.EntitySystems; using Content.Server.Body.Behavior; +using Content.Server.Body.Systems; using Content.Server.Temperature.Components; using Content.Server.Temperature.Systems; using Content.Shared.ActionBlocker; @@ -17,6 +18,7 @@ using Content.Shared.Damage; using Content.Shared.Database; using Content.Shared.MobState.Components; using Content.Shared.Popups; +using Robust.Shared.Analyzers; using Robust.Shared.GameObjects; using Robust.Shared.Localization; using Robust.Shared.Serialization.Manager.Attributes; @@ -24,70 +26,24 @@ using Robust.Shared.ViewVariables; namespace Content.Server.Body.Components { - [RegisterComponent] + [RegisterComponent, Friend(typeof(RespiratorSystem))] public class RespiratorComponent : Component { - [ComponentDependency] private readonly SharedBodyComponent? _body = default!; - public override string Name => "Respirator"; - private float _accumulatedFrameTime; - - [ViewVariables] [DataField("needsGases")] public Dictionary NeedsGases { get; set; } = new(); - - [ViewVariables] [DataField("producesGases")] public Dictionary ProducesGases { get; set; } = new(); - - [ViewVariables] [DataField("deficitGases")] public Dictionary DeficitGases { get; set; } = new(); - - /// - /// Heat generated due to metabolism. It's generated via metabolism - /// [ViewVariables] - [DataField("metabolismHeat")] - public float MetabolismHeat { get; private set; } + [DataField("needsGases")] + public Dictionary NeedsGases { get; set; } = new(); - /// - /// Heat output via radiation. - /// [ViewVariables] - [DataField("radiatedHeat")] - public float RadiatedHeat { get; private set; } + [DataField("producesGases")] + public Dictionary ProducesGases { get; set; } = new(); - /// - /// Maximum heat regulated via sweat - /// [ViewVariables] - [DataField("sweatHeatRegulation")] - public float SweatHeatRegulation { get; private set; } + [DataField("deficitGases")] + public Dictionary DeficitGases { get; set; } = new(); - /// - /// Maximum heat regulated via shivering - /// - [ViewVariables] - [DataField("shiveringHeatRegulation")] - public float ShiveringHeatRegulation { get; private set; } - - /// - /// Amount of heat regulation that represents thermal regulation processes not - /// explicitly coded. - /// - [DataField("implicitHeatRegulation")] - public float ImplicitHeatRegulation { get; private set; } - - /// - /// Normal body temperature - /// - [ViewVariables] - [DataField("normalBodyTemperature")] - public float NormalBodyTemperature { get; private set; } - - /// - /// Deviation from normal temperature for body to start thermal regulation - /// - [DataField("thermalRegulationTemperatureThreshold")] - public float ThermalRegulationTemperatureThreshold { get; private set; } - - [ViewVariables] public bool Suffocating { get; private set; } + [ViewVariables] public bool Suffocating { get; set; } [DataField("damage", required: true)] [ViewVariables(VVAccess.ReadWrite)] @@ -97,283 +53,6 @@ namespace Content.Server.Body.Components [ViewVariables(VVAccess.ReadWrite)] public DamageSpecifier DamageRecovery = default!; - private Dictionary NeedsAndDeficit(float frameTime) - { - var needs = new Dictionary(NeedsGases); - foreach (var (gas, amount) in DeficitGases) - { - var newAmount = (needs.GetValueOrDefault(gas) + amount) * frameTime; - needs[gas] = newAmount; - } - - return needs; - } - - private void ClampDeficit() - { - var deficitGases = new Dictionary(DeficitGases); - - foreach (var (gas, deficit) in deficitGases) - { - if (!NeedsGases.TryGetValue(gas, out var need)) - { - DeficitGases.Remove(gas); - continue; - } - - if (deficit > need) - { - DeficitGases[gas] = need; - } - } - } - - private float SuffocatingPercentage() - { - var total = 0f; - - foreach (var (gas, deficit) in DeficitGases) - { - var lack = 1f; - if (NeedsGases.TryGetValue(gas, out var needed)) - { - lack = deficit / needed; - } - - total += lack / Atmospherics.TotalNumberOfGases; - } - - return total; - } - - private float GasProducedMultiplier(Gas gas, float usedAverage) - { - if (!ProducesGases.TryGetValue(gas, out var produces)) - { - return 0; - } - - if (!NeedsGases.TryGetValue(gas, out var needs)) - { - needs = 1; - } - - return needs * produces * usedAverage; - } - - private Dictionary GasProduced(float usedAverage) - { - return ProducesGases.ToDictionary(pair => pair.Key, pair => GasProducedMultiplier(pair.Key, usedAverage)); - } - - private void ProcessGases(float frameTime) - { - if (!Owner.TryGetComponent(out BloodstreamComponent? bloodstream)) - { - return; - } - - if (_body == null) - { - return; - } - - var lungs = _body.GetMechanismBehaviors().ToArray(); - - var needs = NeedsAndDeficit(frameTime); - var used = 0f; - foreach (var (gas, amountNeeded) in needs) - { - var bloodstreamAmount = bloodstream.Air.GetMoles(gas); - var deficit = 0f; - - if (bloodstreamAmount < amountNeeded) - { - if (!Owner.GetComponent().IsCritical()) - { - // Panic inhale - foreach (var lung in lungs) - { - lung.Gasp(); - } - } - - bloodstreamAmount = bloodstream.Air.GetMoles(gas); - - deficit = Math.Max(0, amountNeeded - bloodstreamAmount); - - if (deficit > 0) - { - bloodstream.Air.SetMoles(gas, 0); - } - else - { - bloodstream.Air.AdjustMoles(gas, -amountNeeded); - } - } - else - { - bloodstream.Air.AdjustMoles(gas, -amountNeeded); - } - - DeficitGases[gas] = deficit; - - - used += (amountNeeded - deficit) / amountNeeded; - } - - var produced = GasProduced(used / needs.Count); - - foreach (var (gas, amountProduced) in produced) - { - bloodstream.Air.AdjustMoles(gas, amountProduced); - } - - ClampDeficit(); - } - - /// - /// Process thermal regulation - /// - /// - private void ProcessThermalRegulation(float frameTime) - { - var temperatureSystem = EntitySystem.Get(); - if (!Owner.TryGetComponent(out TemperatureComponent? temperatureComponent)) return; - - float totalMetabolismTempChange = MetabolismHeat - RadiatedHeat; - // implicit heat regulation - var tempDiff = Math.Abs(temperatureComponent.CurrentTemperature - NormalBodyTemperature); - var targetHeat = tempDiff * temperatureComponent.HeatCapacity; - if (temperatureComponent.CurrentTemperature > NormalBodyTemperature) - { - totalMetabolismTempChange -= Math.Min(targetHeat, ImplicitHeatRegulation); - } - else - { - totalMetabolismTempChange += Math.Min(targetHeat, ImplicitHeatRegulation); - } - - temperatureSystem.ChangeHeat(Owner.Uid, totalMetabolismTempChange, true, temperatureComponent); - - // recalc difference and target heat - tempDiff = Math.Abs(temperatureComponent.CurrentTemperature - NormalBodyTemperature); - targetHeat = tempDiff * temperatureComponent.HeatCapacity; - - // if body temperature is not within comfortable, thermal regulation - // processes starts - if (tempDiff > ThermalRegulationTemperatureThreshold) - return; - - var actionBlocker = EntitySystem.Get(); - - if (temperatureComponent.CurrentTemperature > NormalBodyTemperature) - { - if (!actionBlocker.CanSweat(OwnerUid)) return; - temperatureSystem.ChangeHeat(OwnerUid, -Math.Min(targetHeat, SweatHeatRegulation), true, temperatureComponent); - } - else - { - if (!actionBlocker.CanShiver(OwnerUid)) return; - temperatureSystem.ChangeHeat(OwnerUid, Math.Min(targetHeat, ShiveringHeatRegulation), true, temperatureComponent); - } - } - - /// - /// Processes gases in the bloodstream. - /// - /// - /// The time since the last metabolism tick in seconds. - /// - public void Update(float frameTime) - { - if (!Owner.TryGetComponent(out var state) || - state.IsDead()) - { - return; - } - - _accumulatedFrameTime += frameTime; - - if (_accumulatedFrameTime < 1) - { - return; - } - - ProcessGases(_accumulatedFrameTime); - ProcessThermalRegulation(_accumulatedFrameTime); - - _accumulatedFrameTime -= 1; - - if (SuffocatingPercentage() > 0) - { - TakeSuffocationDamage(); - return; - } - - StopSuffocation(); - } - - private void TakeSuffocationDamage() - { - if (!Suffocating) - EntitySystem.Get().Add(LogType.Asphyxiation, $"{Owner} started suffocating"); - - Suffocating = true; - - if (Owner.TryGetComponent(out ServerAlertsComponent? alertsComponent)) - { - alertsComponent.ShowAlert(AlertType.LowOxygen); - } - - EntitySystem.Get().TryChangeDamage(Owner.Uid, Damage, true, false); - } - - private void StopSuffocation() - { - if (Suffocating) - EntitySystem.Get().Add(LogType.Asphyxiation, $"{Owner} stopped suffocating"); - - Suffocating = false; - - if (Owner.TryGetComponent(out ServerAlertsComponent? alertsComponent)) - { - alertsComponent.ClearAlert(AlertType.LowOxygen); - } - - EntitySystem.Get().TryChangeDamage(Owner.Uid, DamageRecovery, true); - } - - public GasMixture Clean(BloodstreamComponent bloodstream) - { - var gasMixture = new GasMixture(bloodstream.Air.Volume) - { - Temperature = bloodstream.Air.Temperature - }; - - for (Gas gas = 0; gas < (Gas) Atmospherics.TotalNumberOfGases; gas++) - { - float amount; - var molesInBlood = bloodstream.Air.GetMoles(gas); - - if (!NeedsGases.TryGetValue(gas, out var needed)) - { - amount = molesInBlood; - } - else - { - var overflowThreshold = needed * 5f; - - amount = molesInBlood > overflowThreshold - ? molesInBlood - overflowThreshold - : 0; - } - - gasMixture.AdjustMoles(gas, amount); - bloodstream.Air.AdjustMoles(gas, -amount); - } - - return gasMixture; - } + public float AccumulatedFrametime; } } diff --git a/Content.Server/Body/Components/RespiratorSystem.cs b/Content.Server/Body/Components/RespiratorSystem.cs deleted file mode 100644 index 10bd67837a..0000000000 --- a/Content.Server/Body/Components/RespiratorSystem.cs +++ /dev/null @@ -1,19 +0,0 @@ -using JetBrains.Annotations; -using Robust.Shared.GameObjects; - -namespace Content.Server.Body.Components -{ - [UsedImplicitly] - public class RespiratorSystem : EntitySystem - { - public override void Update(float frameTime) - { - base.Update(frameTime); - - foreach (var respirator in EntityManager.EntityQuery(false)) - { - respirator.Update(frameTime); - } - } - } -} diff --git a/Content.Server/Body/Components/ThermalRegulatorComponent.cs b/Content.Server/Body/Components/ThermalRegulatorComponent.cs new file mode 100644 index 0000000000..5b71020719 --- /dev/null +++ b/Content.Server/Body/Components/ThermalRegulatorComponent.cs @@ -0,0 +1,64 @@ +using Content.Server.Body.Systems; +using Robust.Shared.Analyzers; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.ViewVariables; + +namespace Content.Server.Body.Components; + +[RegisterComponent, ComponentProtoName("ThermalRegulator")] +[Friend(typeof(ThermalRegulatorSystem))] +public class ThermalRegulatorComponent : Component +{ + public override string Name => "ThermalRegulator"; + + /// + /// Heat generated due to metabolism. It's generated via metabolism + /// + [ViewVariables] + [DataField("metabolismHeat")] + public float MetabolismHeat { get; private set; } + + /// + /// Heat output via radiation. + /// + [ViewVariables] + [DataField("radiatedHeat")] + public float RadiatedHeat { get; private set; } + + /// + /// Maximum heat regulated via sweat + /// + [ViewVariables] + [DataField("sweatHeatRegulation")] + public float SweatHeatRegulation { get; private set; } + + /// + /// Maximum heat regulated via shivering + /// + [ViewVariables] + [DataField("shiveringHeatRegulation")] + public float ShiveringHeatRegulation { get; private set; } + + /// + /// Amount of heat regulation that represents thermal regulation processes not + /// explicitly coded. + /// + [DataField("implicitHeatRegulation")] + public float ImplicitHeatRegulation { get; private set; } + + /// + /// Normal body temperature + /// + [ViewVariables] + [DataField("normalBodyTemperature")] + public float NormalBodyTemperature { get; private set; } + + /// + /// Deviation from normal temperature for body to start thermal regulation + /// + [DataField("thermalRegulationTemperatureThreshold")] + public float ThermalRegulationTemperatureThreshold { get; private set; } + + public float AccumulatedFrametime; +} diff --git a/Content.Server/Body/Systems/RespiratorSystem.cs b/Content.Server/Body/Systems/RespiratorSystem.cs new file mode 100644 index 0000000000..698cdcd144 --- /dev/null +++ b/Content.Server/Body/Systems/RespiratorSystem.cs @@ -0,0 +1,254 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Content.Server.Administration.Logs; +using Content.Server.Alert; +using Content.Server.Atmos; +using Content.Server.Body.Behavior; +using Content.Server.Body.Components; +using Content.Shared.Alert; +using Content.Shared.Atmos; +using Content.Shared.Body.Components; +using Content.Shared.Damage; +using Content.Shared.Database; +using Content.Shared.MobState.Components; +using JetBrains.Annotations; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; + +namespace Content.Server.Body.Systems +{ + [UsedImplicitly] + public class RespiratorSystem : EntitySystem + { + [Dependency] private readonly DamageableSystem _damageableSys = default!; + [Dependency] private readonly AdminLogSystem _logSys = default!; + + public override void Update(float frameTime) + { + base.Update(frameTime); + + foreach (var (respirator, blood, body) in + EntityManager.EntityQuery()) + { + var uid = respirator.OwnerUid; + if (!EntityManager.TryGetComponent(uid, out var state) || + state.IsDead()) + { + return; + } + + respirator.AccumulatedFrametime += frameTime; + + if (respirator.AccumulatedFrametime < 1) + { + return; + } + + ProcessGases(uid, respirator, frameTime, blood, body); + + respirator.AccumulatedFrametime -= 1; + + if (SuffocatingPercentage(respirator) > 0) + { + TakeSuffocationDamage(uid, respirator); + return; + } + + StopSuffocation(uid, respirator); + } + } + + + private Dictionary NeedsAndDeficit(RespiratorComponent respirator, float frameTime) + { + var needs = new Dictionary(respirator.NeedsGases); + foreach (var (gas, amount) in respirator.DeficitGases) + { + var newAmount = (needs.GetValueOrDefault(gas) + amount) * frameTime; + needs[gas] = newAmount; + } + + return needs; + } + + private void ClampDeficit(RespiratorComponent respirator) + { + var deficitGases = new Dictionary(respirator.DeficitGases); + + foreach (var (gas, deficit) in deficitGases) + { + if (!respirator.NeedsGases.TryGetValue(gas, out var need)) + { + respirator.DeficitGases.Remove(gas); + continue; + } + + if (deficit > need) + { + respirator.DeficitGases[gas] = need; + } + } + } + + private float SuffocatingPercentage(RespiratorComponent respirator) + { + var total = 0f; + + foreach (var (gas, deficit) in respirator.DeficitGases) + { + var lack = 1f; + if (respirator.NeedsGases.TryGetValue(gas, out var needed)) + { + lack = deficit / needed; + } + + total += lack / Atmospherics.TotalNumberOfGases; + } + + return total; + } + + private float GasProducedMultiplier(RespiratorComponent respirator, Gas gas, float usedAverage) + { + if (!respirator.ProducesGases.TryGetValue(gas, out var produces)) + { + return 0; + } + + if (!respirator.NeedsGases.TryGetValue(gas, out var needs)) + { + needs = 1; + } + + return needs * produces * usedAverage; + } + + private Dictionary GasProduced(RespiratorComponent respirator, float usedAverage) + { + return respirator.ProducesGases.ToDictionary(pair => pair.Key, pair => GasProducedMultiplier(respirator, pair.Key, usedAverage)); + } + + private void ProcessGases(EntityUid uid, RespiratorComponent respirator, float frameTime, + BloodstreamComponent? bloodstream, + SharedBodyComponent? body) + { + if (!Resolve(uid, ref bloodstream, ref body, false)) + return; + + var lungs = body.GetMechanismBehaviors().ToArray(); + + var needs = NeedsAndDeficit(respirator, frameTime); + var used = 0f; + foreach (var (gas, amountNeeded) in needs) + { + var bloodstreamAmount = bloodstream.Air.GetMoles(gas); + var deficit = 0f; + + if (bloodstreamAmount < amountNeeded) + { + if (!EntityManager.GetComponent(uid).IsCritical()) + { + // Panic inhale + foreach (var lung in lungs) + { + lung.Gasp(); + } + } + + bloodstreamAmount = bloodstream.Air.GetMoles(gas); + + deficit = Math.Max(0, amountNeeded - bloodstreamAmount); + + if (deficit > 0) + { + bloodstream.Air.SetMoles(gas, 0); + } + else + { + bloodstream.Air.AdjustMoles(gas, -amountNeeded); + } + } + else + { + bloodstream.Air.AdjustMoles(gas, -amountNeeded); + } + + respirator.DeficitGases[gas] = deficit; + + used += (amountNeeded - deficit) / amountNeeded; + } + + var produced = GasProduced(respirator, used / needs.Count); + + foreach (var (gas, amountProduced) in produced) + { + bloodstream.Air.AdjustMoles(gas, amountProduced); + } + + ClampDeficit(respirator); + } + + private void TakeSuffocationDamage(EntityUid uid, RespiratorComponent respirator) + { + if (!respirator.Suffocating) + _logSys.Add(LogType.Asphyxiation, $"{EntityManager.GetEntity(uid)} started suffocating"); + + respirator.Suffocating = true; + + if (EntityManager.TryGetComponent(uid, out ServerAlertsComponent? alertsComponent)) + { + alertsComponent.ShowAlert(AlertType.LowOxygen); + } + + _damageableSys.TryChangeDamage(uid, respirator.Damage, true, false); + } + + private void StopSuffocation(EntityUid uid, RespiratorComponent respirator) + { + if (respirator.Suffocating) + _logSys.Add(LogType.Asphyxiation, $"{EntityManager.GetEntity(uid)} stopped suffocating"); + + respirator.Suffocating = false; + + if (EntityManager.TryGetComponent(uid, out ServerAlertsComponent? alertsComponent)) + { + alertsComponent.ClearAlert(AlertType.LowOxygen); + } + + _damageableSys.TryChangeDamage(uid, respirator.DamageRecovery, true); + } + + public GasMixture Clean(EntityUid uid, RespiratorComponent respirator, BloodstreamComponent bloodstream) + { + var gasMixture = new GasMixture(bloodstream.Air.Volume) + { + Temperature = bloodstream.Air.Temperature + }; + + for (Gas gas = 0; gas < (Gas) Atmospherics.TotalNumberOfGases; gas++) + { + float amount; + var molesInBlood = bloodstream.Air.GetMoles(gas); + + if (!respirator.NeedsGases.TryGetValue(gas, out var needed)) + { + amount = molesInBlood; + } + else + { + var overflowThreshold = needed * 5f; + + amount = molesInBlood > overflowThreshold + ? molesInBlood - overflowThreshold + : 0; + } + + gasMixture.AdjustMoles(gas, amount); + bloodstream.Air.AdjustMoles(gas, -amount); + } + + return gasMixture; + } + } +} diff --git a/Content.Server/Body/Systems/ThermalRegulatorSystem.cs b/Content.Server/Body/Systems/ThermalRegulatorSystem.cs new file mode 100644 index 0000000000..95d4eaee7d --- /dev/null +++ b/Content.Server/Body/Systems/ThermalRegulatorSystem.cs @@ -0,0 +1,74 @@ +using System; +using Content.Server.Body.Components; +using Content.Server.Temperature.Components; +using Content.Server.Temperature.Systems; +using Content.Shared.ActionBlocker; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; + +namespace Content.Server.Body.Systems; + +public class ThermalRegulatorSystem : EntitySystem +{ + [Dependency] private readonly TemperatureSystem _tempSys = default!; + [Dependency] private readonly ActionBlockerSystem _actionBlockerSys = default!; + + public override void Update(float frameTime) + { + foreach (var regulator in EntityManager.EntityQuery()) + { + regulator.AccumulatedFrametime += frameTime; + if (regulator.AccumulatedFrametime < 1) + continue; + + regulator.AccumulatedFrametime -= 1; + ProcessThermalRegulation(regulator.OwnerUid, regulator); + } + } + + /// + /// Processes thermal regulation for a mob + /// + private void ProcessThermalRegulation(EntityUid uid, ThermalRegulatorComponent comp) + { + if (!EntityManager.TryGetComponent(uid, out TemperatureComponent? temperatureComponent)) return; + + var totalMetabolismTempChange = comp.MetabolismHeat - comp.RadiatedHeat; + + // implicit heat regulation + var tempDiff = Math.Abs(temperatureComponent.CurrentTemperature - comp.NormalBodyTemperature); + var targetHeat = tempDiff * temperatureComponent.HeatCapacity; + if (temperatureComponent.CurrentTemperature > comp.NormalBodyTemperature) + { + totalMetabolismTempChange -= Math.Min(targetHeat, comp.ImplicitHeatRegulation); + } + else + { + totalMetabolismTempChange += Math.Min(targetHeat, comp.ImplicitHeatRegulation); + } + + _tempSys.ChangeHeat(uid, totalMetabolismTempChange, true, temperatureComponent); + + // recalc difference and target heat + tempDiff = Math.Abs(temperatureComponent.CurrentTemperature - comp.NormalBodyTemperature); + targetHeat = tempDiff * temperatureComponent.HeatCapacity; + + // if body temperature is not within comfortable, thermal regulation + // processes starts + if (tempDiff > comp.ThermalRegulationTemperatureThreshold) + return; + + if (temperatureComponent.CurrentTemperature > comp.NormalBodyTemperature) + { + if (!_actionBlockerSys.CanSweat(uid)) return; + _tempSys.ChangeHeat(uid, -Math.Min(targetHeat, comp.SweatHeatRegulation), true, + temperatureComponent); + } + else + { + if (!_actionBlockerSys.CanShiver(uid)) return; + _tempSys.ChangeHeat(uid, Math.Min(targetHeat, comp.ShiveringHeatRegulation), true, + temperatureComponent); + } + } +} diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml index 47907853cb..30a5470194 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml @@ -141,7 +141,7 @@ damage: types: Blunt: 1 #per second, scales with pressure and other constants. - - type: Respirator + - type: ThermalRegulator metabolismHeat: 800 radiatedHeat: 100 implicitHeatRegulation: 250 @@ -149,6 +149,7 @@ shiveringHeatRegulation: 500 normalBodyTemperature: 310.15 thermalRegulationTemperatureThreshold: 25 + - type: Respirator needsGases: Oxygen: 0.00060763888 producesGases: diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml index cb083c54f3..3814c27caa 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml @@ -189,7 +189,7 @@ preset: HumanPreset - type: Damageable damageContainer: Biological - - type: Respirator + - type: ThermalRegulator metabolismHeat: 800 radiatedHeat: 100 implicitHeatRegulation: 500 @@ -197,6 +197,7 @@ shiveringHeatRegulation: 2000 normalBodyTemperature: 310.15 thermalRegulationTemperatureThreshold: 25 + - type: Respirator needsGases: Oxygen: 0.00060763888 producesGases: