diff --git a/Content.IntegrationTests/Tests/Body/LungTest.cs b/Content.IntegrationTests/Tests/Body/LungTest.cs index e4abb8a332..ec1c809f92 100644 --- a/Content.IntegrationTests/Tests/Body/LungTest.cs +++ b/Content.IntegrationTests/Tests/Body/LungTest.cs @@ -1,7 +1,7 @@ -using System.Threading.Tasks; +using System; +using System.Linq; +using System.Threading.Tasks; using Content.Server.Atmos; -using Content.Server.Atmos.Components; -using Content.Server.Atmos.EntitySystems; using Content.Server.Body.Components; using Content.Server.Body.Systems; using Content.Shared.Atmos; @@ -9,6 +9,7 @@ using Content.Shared.Body.Components; using NUnit.Framework; using Robust.Server.Maps; using Robust.Shared.GameObjects; +using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.Maths; @@ -20,17 +21,16 @@ namespace Content.IntegrationTests.Tests.Body { private const string Prototypes = @" - type: entity - name: HumanBodyDummy - id: HumanBodyDummy + name: HumanBodyAndBloodstreamDummy + id: HumanBodyAndBloodstreamDummy components: + - type: Bloodstream + max_volume: 100 - type: SolutionContainerManager - type: Body template: HumanoidTemplate preset: HumanPreset centerSlot: torso - - type: MobState - thresholds: - 0: !type:NormalMobState {} - type: ThermalRegulator metabolismHeat: 5000 radiatedHeat: 400 @@ -40,84 +40,101 @@ namespace Content.IntegrationTests.Tests.Body normalBodyTemperature: 310.15 thermalRegulationTemperatureThreshold: 25 - type: Respirator + needsGases: + Oxygen: 0.00060763888 + producesGases: + Oxygen: 0.00045572916 + CarbonDioxide: 0.00015190972 "; [Test] public async Task AirConsistencyTest() { - // --- Setup var options = new ServerContentIntegrationOption{ExtraPrototypes = Prototypes}; var server = StartServer(options); - await server.WaitIdleAsync(); - - var mapLoader = server.ResolveDependency(); - var mapManager = server.ResolveDependency(); - var entityManager = server.ResolveDependency(); - RespiratorSystem respSys = default; - MetabolizerSystem metaSys = default; - - MapId mapId; - IMapGrid grid = null; - SharedBodyComponent body = default; - EntityUid human = default; - GridAtmosphereComponent relevantAtmos = default; - float startingMoles = 0.0f; - - var testMapName = "Maps/Test/Breathing/3by3-20oxy-80nit.yml"; - - await server.WaitPost(() => + server.Assert(() => { - mapId = mapManager.CreateMap(); - grid = mapLoader.LoadBlueprint(mapId, testMapName); + var mapManager = IoCManager.Resolve(); + + var mapId = mapManager.CreateMap(); + + var entityManager = IoCManager.Resolve(); + + var human = entityManager.SpawnEntity("HumanBodyAndBloodstreamDummy", new MapCoordinates(Vector2.Zero, mapId)); + + var bodySys = EntitySystem.Get(); + var lungSys = EntitySystem.Get(); + + Assert.That(entityManager.TryGetComponent(human, out SharedBodyComponent body)); + + var lungs = bodySys.GetComponentsOnMechanisms(human, body).ToArray(); + Assert.That(lungs.Count, Is.EqualTo(1)); + Assert.That(entityManager.TryGetComponent(human, out BloodstreamComponent bloodstream)); + + var gas = new GasMixture(1); + + var originalOxygen = 2; + var originalNitrogen = 8; + var breathedPercentage = Atmospherics.BreathVolume / gas.Volume; + + gas.AdjustMoles(Gas.Oxygen, originalOxygen); + gas.AdjustMoles(Gas.Nitrogen, originalNitrogen); + + var (lung, _) = lungs[0]; + lungSys.TakeGasFrom(((IComponent) lung).Owner, 1, gas, lung); + + var lungOxygen = originalOxygen * breathedPercentage; + var lungNitrogen = originalNitrogen * breathedPercentage; + + Assert.That(bloodstream.Air.GetMoles(Gas.Oxygen), Is.EqualTo(lungOxygen)); + Assert.That(bloodstream.Air.GetMoles(Gas.Nitrogen), Is.EqualTo(lungNitrogen)); + + var mixtureOxygen = originalOxygen - lungOxygen; + var mixtureNitrogen = originalNitrogen - lungNitrogen; + + Assert.That(gas.GetMoles(Gas.Oxygen), Is.EqualTo(mixtureOxygen)); + Assert.That(gas.GetMoles(Gas.Nitrogen), Is.EqualTo(mixtureNitrogen)); + + var lungOxygenBeforeExhale = lung.Air.GetMoles(Gas.Oxygen); + var lungNitrogenBeforeExhale = lung.Air.GetMoles(Gas.Nitrogen); + + // Empty after it transfer to the bloodstream + Assert.Zero(lungOxygenBeforeExhale); + Assert.Zero(lungNitrogenBeforeExhale); + + lungSys.PushGasTo(((IComponent) lung).Owner, gas, lung); + + var lungOxygenAfterExhale = lung.Air.GetMoles(Gas.Oxygen); + var exhaledOxygen = Math.Abs(lungOxygenBeforeExhale - lungOxygenAfterExhale); + + // Not completely empty + Assert.Positive(lung.Air.Moles.Sum()); + + // Retains needed gas + Assert.Positive(bloodstream.Air.GetMoles(Gas.Oxygen)); + + // Expels toxins + Assert.Zero(bloodstream.Air.GetMoles(Gas.Nitrogen)); + + mixtureOxygen += exhaledOxygen; + + var finalTotalOxygen = gas.GetMoles(Gas.Oxygen) + + bloodstream.Air.GetMoles(Gas.Oxygen) + + lung.Air.GetMoles(Gas.Oxygen); + + // No ticks were run, metabolism doesn't run and so no oxygen is used up + Assert.That(finalTotalOxygen, Is.EqualTo(originalOxygen)); + Assert.That(gas.GetMoles(Gas.Oxygen), Is.EqualTo(mixtureOxygen).Within(0.000001f)); + + var finalTotalNitrogen = gas.GetMoles(Gas.Nitrogen) + + bloodstream.Air.GetMoles(Gas.Nitrogen) + + lung.Air.GetMoles(Gas.Nitrogen); + + // Nitrogen stays constant + Assert.That(finalTotalNitrogen, Is.EqualTo(originalNitrogen).Within(0.000001f)); }); - Assert.NotNull(grid, $"Test blueprint {testMapName} not found."); - - float GetMapMoles() - { - var totalMapMoles = 0.0f; - foreach (var tile in relevantAtmos.Tiles.Values) - { - totalMapMoles += tile.Air?.TotalMoles ?? 0.0f; - } - - return totalMapMoles; - } - - await server.WaitAssertion(() => - { - var coords = new Vector2(0.5f, -1f); - var coordinates = new EntityCoordinates(grid.GridEntityId, coords); - human = entityManager.SpawnEntity("HumanBodyDummy", coordinates); - respSys = EntitySystem.Get(); - metaSys = EntitySystem.Get(); - relevantAtmos = entityManager.GetComponent(grid.GridEntityId); - startingMoles = GetMapMoles(); - - Assert.True(entityManager.TryGetComponent(human, out body)); - Assert.True(entityManager.HasComponent(human)); - }); - - // --- End setup - - var inhaleCycles = 100; - for (var i = 0; i < inhaleCycles; i++) - { - await server.WaitAssertion(() => - { - // inhale - respSys.Update(2.0f); - Assert.That(GetMapMoles(), Is.LessThan(startingMoles)); - - // metabolize + exhale - metaSys.Update(1.0f); - metaSys.Update(1.0f); - respSys.Update(2.0f); - Assert.That(GetMapMoles(), Is.EqualTo(startingMoles).Within(0.0001)); - }); - } - await server.WaitIdleAsync(); } @@ -152,21 +169,22 @@ namespace Content.IntegrationTests.Tests.Body { var center = new Vector2(0.5f, -1.5f); var coordinates = new EntityCoordinates(grid.GridEntityId, center); - human = entityManager.SpawnEntity("HumanBodyDummy", coordinates); + human = entityManager.SpawnEntity("HumanBodyAndBloodstreamDummy", coordinates); Assert.True(entityManager.HasComponent(human)); Assert.True(entityManager.TryGetComponent(human, out respirator)); - Assert.False(respirator.SuffocationCycles > respirator.SuffocationCycleThreshold); + Assert.False(respirator.Suffocating); }); var increment = 10; + for (var tick = 0; tick < 600; tick += increment) { await server.WaitRunTicks(increment); await server.WaitAssertion(() => { - Assert.False(respirator.SuffocationCycles > respirator.SuffocationCycleThreshold, $"Entity {entityManager.GetComponent(human).EntityName} is suffocating on tick {tick}"); + Assert.False(respirator.Suffocating, $"Entity {entityManager.GetComponent(human).EntityName} is suffocating on tick {tick}"); }); } diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Gases.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Gases.cs index b588184d55..8c4136f184 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Gases.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Gases.cs @@ -27,8 +27,6 @@ namespace Content.Server.Atmos.EntitySystems /// public float[] GasSpecificHeats => _gasSpecificHeats; - public string?[] GasReagents = new string[Atmospherics.TotalNumberOfGases]; - private void InitializeGases() { _gasReactions = _protoMan.EnumeratePrototypes().ToArray(); @@ -39,7 +37,6 @@ namespace Content.Server.Atmos.EntitySystems for (var i = 0; i < GasPrototypes.Length; i++) { _gasSpecificHeats[i] = GasPrototypes[i].SpecificHeat; - GasReagents[i] = GasPrototypes[i].Reagent; } } diff --git a/Content.Server/Body/Components/BloodstreamComponent.cs b/Content.Server/Body/Components/BloodstreamComponent.cs index aa8426efbe..b00b99f6d4 100644 --- a/Content.Server/Body/Components/BloodstreamComponent.cs +++ b/Content.Server/Body/Components/BloodstreamComponent.cs @@ -11,7 +11,7 @@ using Robust.Shared.ViewVariables; namespace Content.Server.Body.Components { [RegisterComponent, Friend(typeof(BloodstreamSystem))] - public class BloodstreamComponent : Component + public class BloodstreamComponent : Component, IGasMixtureHolder { /// /// Max volume of internal solution storage @@ -24,5 +24,9 @@ namespace Content.Server.Body.Components /// [ViewVariables(VVAccess.ReadWrite)] public Solution Solution = default!; + + [ViewVariables] + public GasMixture Air { get; set; } = new(6) + { Temperature = Atmospherics.NormalBodyTemperature }; } } diff --git a/Content.Server/Body/Components/LungComponent.cs b/Content.Server/Body/Components/LungComponent.cs index 4502854845..5da7fe9b87 100644 --- a/Content.Server/Body/Components/LungComponent.cs +++ b/Content.Server/Body/Components/LungComponent.cs @@ -1,8 +1,7 @@ -using System.Collections.Generic; +using System; using Content.Server.Atmos; using Content.Server.Body.Systems; using Content.Shared.Atmos; -using Content.Shared.Chemistry.Components; using Robust.Shared.Analyzers; using Robust.Shared.GameObjects; using Robust.Shared.Serialization.Manager.Attributes; @@ -13,6 +12,11 @@ namespace Content.Server.Body.Components; [RegisterComponent, Friend(typeof(LungSystem))] public class LungComponent : Component { + public float AccumulatedFrametime; + + [ViewVariables] + public TimeSpan LastGaspPopupTime; + [DataField("air")] public GasMixture Air { get; set; } = new() { @@ -20,9 +24,19 @@ public class LungComponent : Component Temperature = Atmospherics.NormalBodyTemperature }; - [DataField("validReagentGases", required: true)] - public HashSet ValidGases = default!; + [DataField("gaspPopupCooldown")] + public TimeSpan GaspPopupCooldown { get; private set; } = TimeSpan.FromSeconds(8); [ViewVariables] - public Solution LungSolution = default!; + public LungStatus Status { get; set; } + + [DataField("cycleDelay")] + public float CycleDelay { get; set; } = 2; +} + +public enum LungStatus +{ + None = 0, + Inhaling, + Exhaling } diff --git a/Content.Server/Body/Components/RespiratorComponent.cs b/Content.Server/Body/Components/RespiratorComponent.cs index 0f253985f8..33c27e7b7a 100644 --- a/Content.Server/Body/Components/RespiratorComponent.cs +++ b/Content.Server/Body/Components/RespiratorComponent.cs @@ -1,5 +1,6 @@ -using System; +using System.Collections.Generic; using Content.Server.Body.Systems; +using Content.Shared.Atmos; using Content.Shared.Damage; using Robust.Shared.Analyzers; using Robust.Shared.GameObjects; @@ -11,26 +12,19 @@ namespace Content.Server.Body.Components [RegisterComponent, Friend(typeof(RespiratorSystem))] public class RespiratorComponent : Component { - /// - /// Saturation level. Reduced by CycleDelay each tick. - /// Can be thought of as 'how many seconds you have until you start suffocating' in this configuration. - /// - [DataField("saturation")] - public float Saturation = 5.0f; + [ViewVariables] + [DataField("needsGases")] + public Dictionary NeedsGases { get; set; } = new(); - /// - /// At what level of saturation will you begin to suffocate? - /// - [DataField("suffocationThreshold")] - public float SuffocationThreshold; + [ViewVariables] + [DataField("producesGases")] + public Dictionary ProducesGases { get; set; } = new(); - [DataField("maxSaturation")] - public float MaxSaturation = 5.0f; + [ViewVariables] + [DataField("deficitGases")] + public Dictionary DeficitGases { get; set; } = new(); - [DataField("minSaturation")] - public float MinSaturation = -5.0f; - - // TODO HYPEROXIA? + [ViewVariables] public bool Suffocating { get; set; } [DataField("damage", required: true)] [ViewVariables(VVAccess.ReadWrite)] @@ -40,36 +34,6 @@ namespace Content.Server.Body.Components [ViewVariables(VVAccess.ReadWrite)] public DamageSpecifier DamageRecovery = default!; - [DataField("gaspPopupCooldown")] - public TimeSpan GaspPopupCooldown { get; private set; } = TimeSpan.FromSeconds(8); - - [ViewVariables] - public TimeSpan LastGaspPopupTime; - - /// - /// How many cycles in a row has the mob been under-saturated? - /// - [ViewVariables] - public int SuffocationCycles = 0; - - /// - /// How many cycles in a row does it take for the suffocation alert to pop up? - /// - [ViewVariables] - public int SuffocationCycleThreshold = 3; - - [ViewVariables] - public RespiratorStatus Status = RespiratorStatus.Inhaling; - - [DataField("cycleDelay")] - public float CycleDelay = 2.0f; - public float AccumulatedFrametime; } } - -public enum RespiratorStatus -{ - Inhaling, - Exhaling -} diff --git a/Content.Server/Body/Systems/BloodstreamSystem.cs b/Content.Server/Body/Systems/BloodstreamSystem.cs index 98e637fe53..b6a0cd235c 100644 --- a/Content.Server/Body/Systems/BloodstreamSystem.cs +++ b/Content.Server/Body/Systems/BloodstreamSystem.cs @@ -44,4 +44,34 @@ public class BloodstreamSystem : EntitySystem return _solutionContainerSystem.TryAddSolution(uid, component.Solution, solution); } + + public void PumpToxins(EntityUid uid, GasMixture to, BloodstreamComponent? blood=null, RespiratorComponent? respiration=null) + { + if (!Resolve(uid, ref blood)) + return; + + if(!Resolve(uid, ref respiration, false)) + { + _atmosSystem.Merge(to, blood.Air); + blood.Air.Clear(); + return; + } + + var toxins = _respiratorSystem.Clean(uid, respiration, blood); + var toOld = new float[to.Moles.Length]; + Array.Copy(to.Moles, toOld, toOld.Length); + + _atmosSystem.Merge(to, toxins); + + for (var i = 0; i < toOld.Length; i++) + { + var newAmount = to.GetMoles(i); + var oldAmount = toOld[i]; + var delta = newAmount - oldAmount; + + toxins.AdjustMoles(i, -delta); + } + + _atmosSystem.Merge(blood.Air, toxins); + } } diff --git a/Content.Server/Body/Systems/InternalsSystem.cs b/Content.Server/Body/Systems/InternalsSystem.cs deleted file mode 100644 index 4a8a7a1367..0000000000 --- a/Content.Server/Body/Systems/InternalsSystem.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Content.Server.Atmos.Components; -using Content.Server.Body.Components; -using Content.Shared.Atmos; -using Robust.Shared.GameObjects; - -namespace Content.Server.Body.Systems; - -public class InternalsSystem : EntitySystem -{ - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnInhaleLocation); - } - - private void OnInhaleLocation(EntityUid uid, InternalsComponent component, InhaleLocationEvent args) - { - if (component.AreInternalsWorking()) - { - var gasTank = Comp(component.GasTankEntity!.Value); - args.Gas = gasTank.RemoveAirVolume(Atmospherics.BreathVolume); - } - } -} diff --git a/Content.Server/Body/Systems/LungSystem.cs b/Content.Server/Body/Systems/LungSystem.cs index 59838b81f5..3d0f5dd0d4 100644 --- a/Content.Server/Body/Systems/LungSystem.cs +++ b/Content.Server/Body/Systems/LungSystem.cs @@ -1,28 +1,33 @@ -using System.Linq; +using System; +using Content.Server.Atmos; using Content.Server.Atmos.Components; using Content.Server.Atmos.EntitySystems; using Content.Server.Body.Components; -using Content.Server.Chemistry.EntitySystems; +using Content.Server.Popups; using Content.Shared.Atmos; -using Content.Shared.Chemistry.Components; +using Content.Shared.Body.Components; +using Content.Shared.Body.Events; using Content.Shared.Inventory.Events; +using Content.Shared.MobState.Components; using Robust.Shared.GameObjects; using Robust.Shared.IoC; -using Robust.Shared.Utility; +using Robust.Shared.Localization; +using Robust.Shared.Player; +using Robust.Shared.Timing; namespace Content.Server.Body.Systems; public class LungSystem : EntitySystem { - [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; - [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; - - public static string LungSolutionName = "Lung"; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly AtmosphereSystem _atmosSys = default!; + [Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnComponentInit); + SubscribeLocalEvent(OnAddedToBody); SubscribeLocalEvent(OnGotEquipped); SubscribeLocalEvent(OnGotUnequipped); } @@ -45,30 +50,175 @@ public class LungSystem : EntitySystem } } - private void OnComponentInit(EntityUid uid, LungComponent component, ComponentInit args) + private void OnAddedToBody(EntityUid uid, LungComponent component, AddedToBodyEvent args) { - component.LungSolution = _solutionContainerSystem.EnsureSolution(uid, LungSolutionName); - component.LungSolution.MaxVolume = 100.0f; - component.LungSolution.CanReact = false; // No dexalin lungs + Inhale(uid, component.CycleDelay); } - public void GasToReagent(EntityUid uid, LungComponent lung) + public void Gasp(EntityUid uid, + LungComponent? lung=null, + MechanismComponent? mech=null) { - foreach (var gas in lung.ValidGases) + if (!Resolve(uid, ref lung, ref mech)) + return; + + if (_gameTiming.CurTime >= lung.LastGaspPopupTime + lung.GaspPopupCooldown) { - var i = (int) gas; - var moles = lung.Air.Moles[i]; - if (moles <= 0) - continue; - var reagent = _atmosphereSystem.GasReagents[i]; - if (reagent == null) continue; - - var amount = moles * Atmospherics.BreathMolesToReagentMultiplier; - _solutionContainerSystem.TryAddReagent(uid, lung.LungSolution, reagent, amount, out _); - - // We don't remove the gas from the lung mix, - // that's the responsibility of whatever gas is being metabolized. - // Most things will just want to exhale again. + lung.LastGaspPopupTime = _gameTiming.CurTime; + _popupSystem.PopupEntity(Loc.GetString("lung-behavior-gasp"), uid, Filter.Pvs(uid)); } + + if (mech.Body != null && TryComp((mech.Body).Owner, out MobStateComponent? mobState) && !mobState.IsAlive()) + return; + + Inhale(uid, lung.CycleDelay); + } + + public void UpdateLung(EntityUid uid, + LungComponent? lung=null, + MechanismComponent? mech=null) + { + if (!Resolve(uid, ref lung, ref mech)) + return; + + if (mech.Body != null && EntityManager.TryGetComponent((mech.Body).Owner, out MobStateComponent? mobState) && mobState.IsCritical()) + { + return; + } + + if (lung.Status == LungStatus.None) + { + lung.Status = LungStatus.Inhaling; + } + + lung.AccumulatedFrametime += lung.Status switch + { + LungStatus.Inhaling => 1, + LungStatus.Exhaling => -1, + _ => throw new ArgumentOutOfRangeException() + }; + + var absoluteTime = Math.Abs(lung.AccumulatedFrametime); + var delay = lung.CycleDelay; + + if (absoluteTime < delay) + { + return; + } + + switch (lung.Status) + { + case LungStatus.Inhaling: + Inhale(uid, absoluteTime); + lung.Status = LungStatus.Exhaling; + break; + case LungStatus.Exhaling: + Exhale(uid, absoluteTime); + lung.Status = LungStatus.Inhaling; + break; + default: + throw new ArgumentOutOfRangeException(); + } + + lung.AccumulatedFrametime = absoluteTime - delay; + } + + /// + /// Tries to find an air mixture to inhale from, then inhales from it. + /// + public void Inhale(EntityUid uid, float frameTime, + LungComponent? lung=null, + MechanismComponent? mech=null) + { + if (!Resolve(uid, ref lung, ref mech)) + return; + + // TODO Jesus Christ make this event based. + if (mech.Body != null && + EntityManager.TryGetComponent((mech.Body).Owner, out InternalsComponent? internals) && + internals.BreathToolEntity != null && + internals.GasTankEntity != null && + EntityManager.TryGetComponent(internals.BreathToolEntity, out BreathToolComponent? breathTool) && + breathTool.IsFunctional && + EntityManager.TryGetComponent(internals.GasTankEntity, out GasTankComponent? gasTank)) + { + TakeGasFrom(uid, frameTime, gasTank.RemoveAirVolume(Atmospherics.BreathVolume), lung); + return; + } + + if (_atmosSys.GetTileMixture(EntityManager.GetComponent(uid).Coordinates, true) is not { } tileAir) + { + return; + } + + TakeGasFrom(uid, frameTime, tileAir, lung); + } + + /// + /// Inhales directly from a given mixture. + /// + public void TakeGasFrom(EntityUid uid, float frameTime, GasMixture from, + LungComponent? lung=null, + MechanismComponent? mech=null) + { + if (!Resolve(uid, ref lung, ref mech)) + return; + + var ratio = (Atmospherics.BreathVolume / from.Volume) * frameTime; + + _atmosSys.Merge(lung.Air, from.RemoveRatio(ratio)); + + // Push to bloodstream + if (mech.Body == null) + return; + + if (!EntityManager.TryGetComponent((mech.Body).Owner, out BloodstreamComponent? bloodstream)) + return; + + var to = bloodstream.Air; + + _atmosSys.Merge(to, lung.Air); + lung.Air.Clear(); + } + + /// + /// Tries to find a gas mixture to exhale to, then pushes gas to it. + /// + public void Exhale(EntityUid uid, float frameTime, + LungComponent? lung=null, + MechanismComponent? mech=null) + { + if (!Resolve(uid, ref lung, ref mech)) + return; + + if (_atmosSys.GetTileMixture(EntityManager.GetComponent(uid).Coordinates, true) is not { } tileAir) + { + return; + } + + PushGasTo(uid, tileAir, lung, mech); + } + + /// + /// Pushes gas from the lungs to a gas mixture. + /// + public void PushGasTo(EntityUid uid, GasMixture to, + LungComponent? lung=null, + MechanismComponent? mech=null) + { + if (!Resolve(uid, ref lung, ref mech)) + return; + + // TODO: Make the bloodstream separately pump toxins into the lungs, making the lungs' only job to empty. + if (mech.Body == null) + return; + + if (!EntityManager.TryGetComponent((mech.Body).Owner, out BloodstreamComponent? bloodstream)) + return; + + _bloodstreamSystem.PumpToxins((mech.Body).Owner, lung.Air, bloodstream); + + var lungRemoved = lung.Air.RemoveRatio(0.5f); + _atmosSys.Merge(to, lungRemoved); } } diff --git a/Content.Server/Body/Systems/MetabolizerSystem.cs b/Content.Server/Body/Systems/MetabolizerSystem.cs index e5d211a78c..477a4782b0 100644 --- a/Content.Server/Body/Systems/MetabolizerSystem.cs +++ b/Content.Server/Body/Systems/MetabolizerSystem.cs @@ -77,8 +77,6 @@ namespace Content.Server.Body.Systems // First step is get the solution we actually care about Solution? solution = null; EntityUid? solutionEntityUid = null; - EntityUid? bodyEntityUid = mech?.Body?.Owner; - SolutionContainerManagerComponent? manager = null; if (meta.SolutionOnBody) @@ -146,10 +144,6 @@ namespace Content.Server.Body.Systems if (entry.MetabolismRate > mostToRemove) mostToRemove = entry.MetabolismRate; - mostToRemove *= group.MetabolismRateModifier; - - mostToRemove = FixedPoint2.Clamp(mostToRemove, 0, reagent.Quantity); - // if it's possible for them to be dead, and they are, // then we shouldn't process any effects, but should probably // still remove reagents @@ -159,8 +153,7 @@ namespace Content.Server.Body.Systems continue; } - var actualEntity = bodyEntityUid != null ? bodyEntityUid.Value : solutionEntityUid.Value; - var args = new ReagentEffectArgs(actualEntity, (meta).Owner, solution, proto, mostToRemove, + var args = new ReagentEffectArgs(solutionEntityUid.Value, (meta).Owner, solution, proto, entry.MetabolismRate, EntityManager, null); // do all effects, if conditions apply @@ -171,8 +164,9 @@ namespace Content.Server.Body.Systems if (effect.ShouldLog) { + var entity = args.SolutionEntity; _logSystem.Add(LogType.ReagentEffect, effect.LogImpact, - $"Metabolism effect {effect.GetType().Name:effect} of reagent {args.Reagent.Name:reagent} applied on entity {actualEntity:entity} at {Transform(actualEntity).Coordinates:coordinates}"); + $"Metabolism effect {effect.GetType().Name:effect} of reagent {args.Reagent.Name:reagent} applied on entity {entity} at {Transform(entity).Coordinates:coordinates}"); } effect.Effect(args); diff --git a/Content.Server/Body/Systems/RespiratorSystem.cs b/Content.Server/Body/Systems/RespiratorSystem.cs index 5c71f3035a..323a019a05 100644 --- a/Content.Server/Body/Systems/RespiratorSystem.cs +++ b/Content.Server/Body/Systems/RespiratorSystem.cs @@ -1,10 +1,9 @@ using System; +using System.Collections.Generic; using System.Linq; using Content.Server.Administration.Logs; using Content.Server.Atmos; -using Content.Server.Atmos.EntitySystems; using Content.Server.Body.Components; -using Content.Server.Popups; using Content.Shared.Alert; using Content.Shared.Atmos; using Content.Shared.Body.Components; @@ -14,9 +13,6 @@ using Content.Shared.MobState.Components; using JetBrains.Annotations; using Robust.Shared.GameObjects; using Robust.Shared.IoC; -using Robust.Shared.Localization; -using Robust.Shared.Player; -using Robust.Shared.Timing; namespace Content.Server.Body.Systems { @@ -27,25 +23,14 @@ namespace Content.Server.Body.Systems [Dependency] private readonly AdminLogSystem _logSys = default!; [Dependency] private readonly BodySystem _bodySystem = default!; [Dependency] private readonly LungSystem _lungSystem = default!; - [Dependency] private readonly AtmosphereSystem _atmosSys = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly AlertsSystem _alertsSystem = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - - public override void Initialize() - { - base.Initialize(); - - // We want to process lung reagents before we inhale new reagents. - UpdatesAfter.Add(typeof(MetabolizerSystem)); - } public override void Update(float frameTime) { base.Update(frameTime); - foreach (var (respirator, body) in - EntityManager.EntityQuery()) + foreach (var (respirator, blood, body) in + EntityManager.EntityQuery()) { var uid = respirator.Owner; if (!EntityManager.TryGetComponent(uid, out var state) || @@ -56,142 +41,211 @@ namespace Content.Server.Body.Systems respirator.AccumulatedFrametime += frameTime; - if (respirator.AccumulatedFrametime < respirator.CycleDelay) - continue; - respirator.AccumulatedFrametime -= respirator.CycleDelay; - UpdateSaturation(respirator.Owner, -respirator.CycleDelay, respirator); - - switch (respirator.Status) + if (respirator.AccumulatedFrametime < 1) { - case RespiratorStatus.Inhaling: - Inhale(uid, body); - respirator.Status = RespiratorStatus.Exhaling; - break; - case RespiratorStatus.Exhaling: - Exhale(uid, body); - respirator.Status = RespiratorStatus.Inhaling; - break; + continue; } - if (respirator.Saturation < respirator.SuffocationThreshold) - { - if (_gameTiming.CurTime >= respirator.LastGaspPopupTime + respirator.GaspPopupCooldown) - { - respirator.LastGaspPopupTime = _gameTiming.CurTime; - _popupSystem.PopupEntity(Loc.GetString("lung-behavior-gasp"), uid, Filter.Pvs(uid)); - } + ProcessGases(uid, respirator, blood, body); + respirator.AccumulatedFrametime -= 1; + + if (SuffocatingPercentage(respirator) > 0) + { TakeSuffocationDamage(uid, respirator); - respirator.SuffocationCycles += 1; continue; } StopSuffocation(uid, respirator); - respirator.SuffocationCycles = 0; } } - public void Inhale(EntityUid uid, SharedBodyComponent? body=null) + private Dictionary NeedsAndDeficit(RespiratorComponent respirator) { - if (!Resolve(uid, ref body, false)) - return; - - var organs = _bodySystem.GetComponentsOnMechanisms(uid, body).ToArray(); - - // Inhale gas - var ev = new InhaleLocationEvent(); - RaiseLocalEvent(uid, ev, false); - - if (ev.Gas == null) + var needs = new Dictionary(respirator.NeedsGases); + foreach (var (gas, amount) in respirator.DeficitGases) { - ev.Gas = _atmosSys.GetTileMixture(Transform(uid).Coordinates); - if (ev.Gas == null) return; + var newAmount = (needs.GetValueOrDefault(gas) + amount); + needs[gas] = newAmount; } - var ratio = (Atmospherics.BreathVolume / ev.Gas.Volume); - var actualGas = ev.Gas.RemoveRatio(ratio); + return needs; + } - var lungRatio = 1.0f / organs.Length; - var gas = organs.Length == 1 ? actualGas : actualGas.RemoveRatio(lungRatio); - foreach (var (lung, _) in organs) + private void ClampDeficit(RespiratorComponent respirator) + { + var deficitGases = new Dictionary(respirator.DeficitGases); + + foreach (var (gas, deficit) in deficitGases) { - // Merge doesn't remove gas from the giver. - _atmosSys.Merge(lung.Air, gas); - _lungSystem.GasToReagent(lung.Owner, lung); + if (!respirator.NeedsGases.TryGetValue(gas, out var need)) + { + respirator.DeficitGases.Remove(gas); + continue; + } + + if (deficit > need) + { + respirator.DeficitGases[gas] = need; + } } } - public void Exhale(EntityUid uid, SharedBodyComponent? body=null) + private float SuffocatingPercentage(RespiratorComponent respirator) { - if (!Resolve(uid, ref body, false)) + 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, + BloodstreamComponent? bloodstream, + SharedBodyComponent? body) + { + if (!Resolve(uid, ref bloodstream, ref body, false)) return; - var organs = _bodySystem.GetComponentsOnMechanisms(uid, body).ToArray(); + var lungs = _bodySystem.GetComponentsOnMechanisms(uid, body).ToArray(); - // exhale gas + var needs = NeedsAndDeficit(respirator); + var used = 0f; - var ev = new ExhaleLocationEvent(); - RaiseLocalEvent(uid, ev, false); - - if (ev.Gas == null) + foreach (var (lung, mech) in lungs) { - ev.Gas = _atmosSys.GetTileMixture(Transform(uid).Coordinates); - if (ev.Gas == null) return; + _lungSystem.UpdateLung(lung.Owner, lung, mech); } - var outGas = new GasMixture(ev.Gas.Volume); - foreach (var (lung, _) in organs) + foreach (var (gas, amountNeeded) in needs) { - _atmosSys.Merge(outGas, lung.Air); - lung.Air.Clear(); - lung.LungSolution.RemoveAllSolution(); + var bloodstreamAmount = bloodstream.Air.GetMoles(gas); + var deficit = 0f; + + if (bloodstreamAmount < amountNeeded) + { + // Panic inhale + foreach (var (lung, mech) in lungs) + { + _lungSystem.Gasp((lung).Owner, lung, mech); + } + + 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; } - _atmosSys.Merge(ev.Gas, outGas); + 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.SuffocationCycles == 2) + if (!respirator.Suffocating) _logSys.Add(LogType.Asphyxiation, $"{ToPrettyString(uid):entity} started suffocating"); - if (respirator.SuffocationCycles >= respirator.SuffocationCycleThreshold) - { - _alertsSystem.ShowAlert(uid, AlertType.LowOxygen); - } + respirator.Suffocating = true; + + _alertsSystem.ShowAlert(uid, AlertType.LowOxygen); _damageableSys.TryChangeDamage(uid, respirator.Damage, true, false); } private void StopSuffocation(EntityUid uid, RespiratorComponent respirator) { - if (respirator.SuffocationCycles >= 2) + if (respirator.Suffocating) _logSys.Add(LogType.Asphyxiation, $"{ToPrettyString(uid):entity} stopped suffocating"); + respirator.Suffocating = false; + _alertsSystem.ClearAlert(uid, AlertType.LowOxygen); _damageableSys.TryChangeDamage(uid, respirator.DamageRecovery, true); } - public void UpdateSaturation(EntityUid uid, float amount, - RespiratorComponent? respirator = null) + public GasMixture Clean(EntityUid uid, RespiratorComponent respirator, BloodstreamComponent bloodstream) { - if (!Resolve(uid, ref respirator, false)) - return; + var gasMixture = new GasMixture(bloodstream.Air.Volume) + { + Temperature = bloodstream.Air.Temperature + }; - respirator.Saturation += amount; - respirator.Saturation = - Math.Clamp(respirator.Saturation, respirator.MinSaturation, respirator.MaxSaturation); + 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; } } } - -public class InhaleLocationEvent : EntityEventArgs -{ - public GasMixture? Gas; -} - -public class ExhaleLocationEvent : EntityEventArgs -{ - public GasMixture? Gas; -} diff --git a/Content.Server/Chemistry/ReagentEffects/AdjustAlert.cs b/Content.Server/Chemistry/ReagentEffects/AdjustAlert.cs deleted file mode 100644 index 841d760f89..0000000000 --- a/Content.Server/Chemistry/ReagentEffects/AdjustAlert.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using Content.Shared.Alert; -using Content.Shared.Chemistry.Reagent; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Serialization.Manager.Attributes; -using Robust.Shared.Timing; - -namespace Content.Server.Chemistry.ReagentEffects; - -public class AdjustAlert : ReagentEffect -{ - [DataField("alertType", required: true)] - public AlertType Type; - - [DataField("clear")] - public bool Clear; - - [DataField("cooldown")] - public bool Cooldown; - - [DataField("time")] - public float Time; - - public override void Effect(ReagentEffectArgs args) - { - var alertSys = EntitySystem.Get(); - if (args.EntityManager.HasComponent(args.SolutionEntity)) - { - if (Clear) - { - alertSys.ClearAlert(args.SolutionEntity, Type); - } - else - { - (TimeSpan, TimeSpan)? cooldown = null; - if (Cooldown) - { - var timing = IoCManager.Resolve(); - cooldown = (timing.CurTime, timing.CurTime + TimeSpan.FromSeconds(Time)); - } - alertSys.ShowAlert(args.SolutionEntity, Type, cooldown: cooldown); - } - } - } -} diff --git a/Content.Server/Chemistry/ReagentEffects/ModifyLungGas.cs b/Content.Server/Chemistry/ReagentEffects/ModifyLungGas.cs deleted file mode 100644 index c9bbe452af..0000000000 --- a/Content.Server/Chemistry/ReagentEffects/ModifyLungGas.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Collections.Generic; -using Content.Server.Body.Components; -using Content.Shared.Atmos; -using Content.Shared.Chemistry.Reagent; -using Robust.Shared.Serialization.Manager.Attributes; - -namespace Content.Server.Chemistry.ReagentEffects; - -public class ModifyLungGas : ReagentEffect -{ - [DataField("ratios", required: true)] - private Dictionary _ratios = default!; - - public override void Effect(ReagentEffectArgs args) - { - if (args.EntityManager.TryGetComponent(args.OrganEntity, out var lung)) - { - foreach (var (gas, ratio) in _ratios) - { - lung.Air.Moles[(int) gas] += (ratio * args.Quantity.Float()) / Atmospherics.BreathMolesToReagentMultiplier; - } - } - } -} diff --git a/Content.Server/Chemistry/ReagentEffects/Oxygenate.cs b/Content.Server/Chemistry/ReagentEffects/Oxygenate.cs deleted file mode 100644 index d7f7cb3771..0000000000 --- a/Content.Server/Chemistry/ReagentEffects/Oxygenate.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Content.Server.Body.Components; -using Content.Server.Body.Systems; -using Content.Shared.Chemistry.Reagent; -using Robust.Shared.GameObjects; -using Robust.Shared.Serialization.Manager.Attributes; - -namespace Content.Server.Chemistry.ReagentEffects; - -public class Oxygenate : ReagentEffect -{ - [DataField("factor")] - public float Factor = 1f; - - public override void Effect(ReagentEffectArgs args) - { - if (args.EntityManager.TryGetComponent(args.SolutionEntity, out var resp)) - { - var respSys = EntitySystem.Get(); - respSys.UpdateSaturation(resp.Owner, args.Quantity.Float() * Factor, resp); - } - } -} diff --git a/Content.Shared/Alert/AlertCategory.cs b/Content.Shared/Alert/AlertCategory.cs index db81212271..8c7d65ef76 100644 --- a/Content.Shared/Alert/AlertCategory.cs +++ b/Content.Shared/Alert/AlertCategory.cs @@ -12,6 +12,5 @@ public enum AlertCategory Health, Piloting, Hunger, - Thirst, - Toxins -} + Thirst +} \ No newline at end of file diff --git a/Content.Shared/Alert/AlertType.cs b/Content.Shared/Alert/AlertType.cs index b6a7ef8763..3c87650419 100644 --- a/Content.Shared/Alert/AlertType.cs +++ b/Content.Shared/Alert/AlertType.cs @@ -31,7 +31,6 @@ Pulled, Pulling, Magboots, - Toxins, Debug1, Debug2, Debug3, diff --git a/Content.Shared/Atmos/Atmospherics.cs b/Content.Shared/Atmos/Atmospherics.cs index bd9b2cc050..6610f23570 100644 --- a/Content.Shared/Atmos/Atmospherics.cs +++ b/Content.Shared/Atmos/Atmospherics.cs @@ -255,10 +255,11 @@ namespace Content.Shared.Atmos /// public const float NormalBodyTemperature = 37f; - /// - /// I hereby decree. This is Arbitrary Suck my Dick - /// - public const float BreathMolesToReagentMultiplier = 1144; + public const float HumanNeededOxygen = MolesCellStandard * BreathPercentage * 0.16f; + + public const float HumanProducedOxygen = HumanNeededOxygen * 0.75f; + + public const float HumanProducedCarbonDioxide = HumanNeededOxygen * 0.25f; #region Pipes diff --git a/Content.Shared/Atmos/Prototypes/GasPrototype.cs b/Content.Shared/Atmos/Prototypes/GasPrototype.cs index 790b44712b..fdf4ba643a 100644 --- a/Content.Shared/Atmos/Prototypes/GasPrototype.cs +++ b/Content.Shared/Atmos/Prototypes/GasPrototype.cs @@ -1,7 +1,5 @@ -using Content.Shared.Chemistry.Reagent; -using Robust.Shared.Prototypes; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization.Manager.Attributes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.ViewVariables; namespace Content.Shared.Atmos.Prototypes @@ -72,12 +70,6 @@ namespace Content.Shared.Atmos.Prototypes [DataField("overlayPath")] public string OverlayPath { get; } = string.Empty; - /// - /// The reagent that this gas will turn into when inhaled. - /// - [DataField("reagent", customTypeSerializer:typeof(PrototypeIdSerializer))] - public string? Reagent { get; } = default!; - [DataField("color")] public string Color { get; } = string.Empty; } } diff --git a/Resources/Prototypes/Alerts/alerts.yml b/Resources/Prototypes/Alerts/alerts.yml index 5e02ebe15e..2c4bd70be7 100644 --- a/Resources/Prototypes/Alerts/alerts.yml +++ b/Resources/Prototypes/Alerts/alerts.yml @@ -25,16 +25,7 @@ sprite: /Textures/Interface/Alerts/breathing.rsi state: not_enough_oxy name: "[color=red]Low Oxygen[/color]" - description: "There is [color=red]not enough oxygen[/color] in the air you are breathing. Put on [color=green]internals[/color]." - -- type: alert - alertType: Toxins - category: Toxins - icon: - sprite: /Textures/Interface/Alerts/breathing.rsi - state: too_much_tox - name: "[color=red]High Toxin Level[/color]" - description: "There are [color=red]too many toxins[/color] in the air you are breathing. Put on [color=green]internals[/color] or get away." + description: "There is not [color=red]not enough oxygen[/color] in the air you are breathing. Put on [color=green]internals[/color]." - type: alert alertType: LowPressure diff --git a/Resources/Prototypes/Atmospherics/gases.yml b/Resources/Prototypes/Atmospherics/gases.yml index 2ff48cede3..7355812aba 100644 --- a/Resources/Prototypes/Atmospherics/gases.yml +++ b/Resources/Prototypes/Atmospherics/gases.yml @@ -5,7 +5,6 @@ heatCapacityRatio: 1.4 molarMass: 32 color: 2887E8 - reagent: Oxygen - type: gas id: 1 @@ -14,7 +13,6 @@ heatCapacityRatio: 1.4 molarMass: 28 color: DA1010 - reagent: Nitrogen - type: gas id: 2 @@ -23,7 +21,6 @@ heatCapacityRatio: 1.3 molarMass: 44 color: 4e4e4e - reagent: CarbonDioxide - type: gas id: 3 @@ -34,7 +31,6 @@ gasOverlaySprite: /Textures/Effects/atmospherics.rsi gasOverlayState: plasma color: FF3300 - reagent: Plasma - type: gas id: 4 @@ -45,7 +41,6 @@ gasOverlaySprite: /Textures/Effects/atmospherics.rsi gasOverlayState: tritium color: 13FF4B - reagent: Tritium - type: gas id: 5 @@ -56,4 +51,3 @@ gasOverlaySprite: /Textures/Effects/atmospherics.rsi gasOverlayState: water_vapor color: bffffd - reagent: Water diff --git a/Resources/Prototypes/Body/Mechanisms/human.yml b/Resources/Prototypes/Body/Mechanisms/human.yml index 84c2201f6f..d43ced7573 100644 --- a/Resources/Prototypes/Body/Mechanisms/human.yml +++ b/Resources/Prototypes/Body/Mechanisms/human.yml @@ -100,19 +100,6 @@ size: 1 compatibility: Biological - type: Lung - validReagentGases: - - Oxygen - - Plasma - - Tritium - - CarbonDioxide - - type: Metabolizer - removeEmpty: true - solutionOnBody: false - solution: "Lung" - metabolizerTypes: [ Human ] - groups: - - id: Gas - rateModifier: 100.0 - type: entity id: OrganHumanHeart diff --git a/Resources/Prototypes/Body/Mechanisms/slime.yml b/Resources/Prototypes/Body/Mechanisms/slime.yml index 9e744775c1..add05c09bf 100644 --- a/Resources/Prototypes/Body/Mechanisms/slime.yml +++ b/Resources/Prototypes/Body/Mechanisms/slime.yml @@ -11,6 +11,7 @@ - type: Mechanism size: 5 compatibility: Slime + - type: Lung - type: Brain - type: Stomach maxVolume: 250 diff --git a/Resources/Prototypes/Body/Parts/animal.yml b/Resources/Prototypes/Body/Parts/animal.yml index e00b6caaab..9fdf956298 100644 --- a/Resources/Prototypes/Body/Parts/animal.yml +++ b/Resources/Prototypes/Body/Parts/animal.yml @@ -82,19 +82,6 @@ size: 1 compatibility: Biological - type: Lung - validReagentGases: - - Oxygen - - Plasma - - Tritium - - CarbonDioxide - - type: Metabolizer - removeEmpty: true - solutionOnBody: false - solution: "Lung" - metabolizerTypes: [ Animal ] - groups: - - id: Gas - rateModifier: 100.0 - type: entity id: OrganAnimalStomach diff --git a/Resources/Prototypes/Chemistry/metabolism_groups.yml b/Resources/Prototypes/Chemistry/metabolism_groups.yml index fc59edd81f..f311b8eb36 100644 --- a/Resources/Prototypes/Chemistry/metabolism_groups.yml +++ b/Resources/Prototypes/Chemistry/metabolism_groups.yml @@ -16,7 +16,3 @@ - type: metabolismGroup id: Drink - -# Used for gases that have effects on being inhaled -- type: metabolismGroup - id: Gas diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml index cfa0171208..9771436550 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml @@ -152,12 +152,17 @@ normalBodyTemperature: 310.15 thermalRegulationTemperatureThreshold: 25 - type: Respirator + needsGases: + Oxygen: 0.00060763888 + producesGases: + Oxygen: 0.00045572916 + CarbonDioxide: 0.00015190972 damage: types: - Asphyxiation: 3 + Asphyxiation: 1 damageRecovery: types: - Asphyxiation: -1.5 + Asphyxiation: -1 - type: Temperature heatDamageThreshold: 360 coldDamageThreshold: 260 diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml index a7faed6b1a..5cbd2e613f 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml @@ -42,10 +42,10 @@ - type: Respirator damage: types: - Asphyxiation: 3 + Asphyxiation: 1 damageRecovery: types: - Asphyxiation: -1.5 + Asphyxiation: -1 - type: UnarmedCombat range: 1.5 arcwidth: 0 diff --git a/Resources/Prototypes/Entities/Mobs/Player/human.yml b/Resources/Prototypes/Entities/Mobs/Player/human.yml index f7a8e785b3..0fea50040f 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/human.yml @@ -11,13 +11,6 @@ context: "human" - type: PlayerMobMover - type: PlayerInputMover - - type: Respirator - damage: - types: - Asphyxiation: 3 - damageRecovery: - types: - Asphyxiation: -1.5 - type: Alerts - type: Actions innateActions: diff --git a/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml b/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml index e07ba16064..bab9b6403d 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml @@ -9,13 +9,6 @@ - type: Icon sprite: Mobs/Species/Slime/parts.rsi state: full - - type: Respirator - damage: - types: - Asphyxiation: 3 - damageRecovery: - types: - Asphyxiation: -1.5 - type: Sprite netsync: false noRot: true diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml index 5afb2bfabc..a95472b3be 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml @@ -199,6 +199,18 @@ shiveringHeatRegulation: 2000 normalBodyTemperature: 310.15 thermalRegulationTemperatureThreshold: 25 + - type: Respirator + needsGases: + Oxygen: 0.00060763888 + producesGases: + Oxygen: 0.00045572916 + CarbonDioxide: 0.00015190972 + damage: + types: + Asphyxiation: 1 + damageRecovery: + types: + Asphyxiation: -1 - type: Internals - type: MobState thresholds: diff --git a/Resources/Prototypes/Entities/Mobs/Species/vox.yml b/Resources/Prototypes/Entities/Mobs/Species/vox.yml index 33c35a6bd8..d26ea684d9 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/vox.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/vox.yml @@ -83,14 +83,18 @@ - type: Body template: HumanoidTemplate preset: VoxPreset - # TODO Vox nitrogen - type: Respirator + needsGases: + Nitrogen: 0.00060763888 + producesGases: + Nitrogen: 0.00045572916 + CarbonDioxide: 0.00015190972 damage: types: - Asphyxiation: 3 + Asphyxiation: 1 damageRecovery: types: - Asphyxiation: -1.5 + Asphyxiation: -1 # - type: Appearance # visuals: # - type: RotationVisualizer diff --git a/Resources/Prototypes/Reagents/elements.yml b/Resources/Prototypes/Reagents/elements.yml index 9b0f65b619..30b01be70b 100644 --- a/Resources/Prototypes/Reagents/elements.yml +++ b/Resources/Prototypes/Reagents/elements.yml @@ -134,6 +134,26 @@ types: Poison: 2 +- type: reagent + id: Nitrogen + name: nitrogen + group: Elements + desc: A colorless, odorless unreactive gas. Highly stable. + physicalDesc: gaseous + color: "#808080" + boilingPoint: -195.8 + meltingPoint: -210.0 + +- type: reagent + id: Oxygen + name: oxygen + group: Elements + desc: An oxidizing, colorless gas. + physicalDesc: gaseous + color: "#808080" + boilingPoint: -183.0 + meltingPoint: -218.4 + - type: reagent id: Potassium name: potassium diff --git a/Resources/Prototypes/Reagents/gases.yml b/Resources/Prototypes/Reagents/gases.yml deleted file mode 100644 index 90d0971c2a..0000000000 --- a/Resources/Prototypes/Reagents/gases.yml +++ /dev/null @@ -1,121 +0,0 @@ -- type: reagent - id: Oxygen - name: oxygen - desc: An oxidizing, colorless gas. - physicalDesc: gaseous - color: "#808080" - boilingPoint: -183.0 - meltingPoint: -218.4 - metabolisms: - Gas: - effects: - - !type:Oxygenate - conditions: - - !type:OrganType - type: Human - - !type:Oxygenate - conditions: - - !type:OrganType - type: Animal - # Convert Oxygen into CO2. - - !type:ModifyLungGas - ratios: - CarbonDioxide: 1.0 - Oxygen: -1.0 - -- type: reagent - id: Plasma - name: plasma - desc: Funky, space-magic pixie dust. You probably shouldn't eat this, but we both know you will anyways. - physicalDesc: gaseous - color: "#7e009e" - boilingPoint: -127.3 # Random values picked between the actual values for CO2 and O2 - meltingPoint: -186.4 - tileReactions: - - !type:FlammableTileReaction - temperatureMultiplier: 1.5 - metabolisms: - Poison: - effects: - - !type:HealthChange - damage: - types: - Poison: 3 - - !type:AdjustReagent - reagent: Inaprovaline - amount: -2.0 - Gas: - effects: - - !type:HealthChange - scaleByQuantity: true - ignoreResistances: true - damage: - types: - Poison: - 1 - # Cant be added until I add metabolism effects on reagent removal - #- !type:AdjustAlert - # alertType: Toxins - reactiveEffects: - Flammable: - methods: [ Touch ] - effects: - - !type:FlammableReaction - -- type: reagent - id: Tritium - name: tritium - desc: Radioactive space-magic pixie dust. - physicalDesc: ionizing - color: "#66ff33" - tileReactions: - - !type:FlammableTileReaction - temperatureMultiplier: 2.0 - metabolisms: - Poison: - effects: - - !type:HealthChange - damage: - types: - Radiation: 3 - Gas: - effects: - - !type:HealthChange - scaleByQuantity: true - ignoreResistances: true - damage: - types: - Radiation: - 1 - # Cant be added until I add metabolism effects on reagent removal - #- !type:AdjustAlert - # alertType: Toxins - -- type: reagent - id: CarbonDioxide - name: carbon dioxide - desc: You have genuinely no idea what this is. - physicalDesc: odorless - color: "#66ff33" - metabolisms: - Gas: - effects: - - !type:HealthChange - scaleByQuantity: true - ignoreResistances: true - damage: - types: - Poison: - 0.2 - # Cant be added until I add metabolism effects on reagent removal - #- !type:AdjustAlert - # alertType: CarbonDioxide - -- type: reagent - id: Nitrogen - name: nitrogen - desc: A colorless, odorless unreactive gas. Highly stable. - physicalDesc: gaseous - color: "#808080" - boilingPoint: -195.8 - meltingPoint: -210.0 diff --git a/Resources/Prototypes/Reagents/toxins.yml b/Resources/Prototypes/Reagents/toxins.yml index 4531ca855d..5f517a79a5 100644 --- a/Resources/Prototypes/Reagents/toxins.yml +++ b/Resources/Prototypes/Reagents/toxins.yml @@ -133,6 +133,35 @@ type: Local messages: [ "generic-reagent-effect-burning-insides" ] probability: 0.33 + # TODO MIRROR acid! + +- type: reagent + id: Plasma + name: plasma + group: Toxins + desc: Funky, space-magic pixie dust. You probably shouldn't eat this, but we both know you will anyways. + physicalDesc: gaseous + color: "#7e009e" + boilingPoint: -127.3 # Random values picked between the actual values for CO2 and O2 + meltingPoint: -186.4 + tileReactions: + - !type:FlammableTileReaction + temperatureMultiplier: 1.5 + metabolisms: + Poison: + effects: + - !type:HealthChange + damage: + types: + Poison: 3 + - !type:AdjustReagent + reagent: Inaprovaline + amount: -2.0 + reactiveEffects: + Flammable: + methods: [ Touch ] + effects: + - !type:FlammableReaction - type: reagent id: UnstableMutagen