diff --git a/Content.IntegrationTests/Tests/Body/LungTest.cs b/Content.IntegrationTests/Tests/Body/LungTest.cs index ec1c809f92..e4abb8a332 100644 --- a/Content.IntegrationTests/Tests/Body/LungTest.cs +++ b/Content.IntegrationTests/Tests/Body/LungTest.cs @@ -1,7 +1,7 @@ -using System; -using System.Linq; -using System.Threading.Tasks; +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,7 +9,6 @@ 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; @@ -21,16 +20,17 @@ namespace Content.IntegrationTests.Tests.Body { private const string Prototypes = @" - type: entity - name: HumanBodyAndBloodstreamDummy - id: HumanBodyAndBloodstreamDummy + name: HumanBodyDummy + id: HumanBodyDummy 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,101 +40,84 @@ 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); - server.Assert(() => + 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(() => { - 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)); + mapId = mapManager.CreateMap(); + grid = mapLoader.LoadBlueprint(mapId, testMapName); }); + 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(); } @@ -169,22 +152,21 @@ namespace Content.IntegrationTests.Tests.Body { var center = new Vector2(0.5f, -1.5f); var coordinates = new EntityCoordinates(grid.GridEntityId, center); - human = entityManager.SpawnEntity("HumanBodyAndBloodstreamDummy", coordinates); + human = entityManager.SpawnEntity("HumanBodyDummy", coordinates); Assert.True(entityManager.HasComponent(human)); Assert.True(entityManager.TryGetComponent(human, out respirator)); - Assert.False(respirator.Suffocating); + Assert.False(respirator.SuffocationCycles > respirator.SuffocationCycleThreshold); }); var increment = 10; - for (var tick = 0; tick < 600; tick += increment) { await server.WaitRunTicks(increment); await server.WaitAssertion(() => { - Assert.False(respirator.Suffocating, $"Entity {entityManager.GetComponent(human).EntityName} is suffocating on tick {tick}"); + Assert.False(respirator.SuffocationCycles > respirator.SuffocationCycleThreshold, $"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 8c4136f184..b588184d55 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Gases.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Gases.cs @@ -27,6 +27,8 @@ namespace Content.Server.Atmos.EntitySystems /// public float[] GasSpecificHeats => _gasSpecificHeats; + public string?[] GasReagents = new string[Atmospherics.TotalNumberOfGases]; + private void InitializeGases() { _gasReactions = _protoMan.EnumeratePrototypes().ToArray(); @@ -37,6 +39,7 @@ 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 b00b99f6d4..aa8426efbe 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, IGasMixtureHolder + public class BloodstreamComponent : Component { /// /// Max volume of internal solution storage @@ -24,9 +24,5 @@ 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 5da7fe9b87..4502854845 100644 --- a/Content.Server/Body/Components/LungComponent.cs +++ b/Content.Server/Body/Components/LungComponent.cs @@ -1,7 +1,8 @@ -using System; +using System.Collections.Generic; 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; @@ -12,11 +13,6 @@ 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() { @@ -24,19 +20,9 @@ public class LungComponent : Component Temperature = Atmospherics.NormalBodyTemperature }; - [DataField("gaspPopupCooldown")] - public TimeSpan GaspPopupCooldown { get; private set; } = TimeSpan.FromSeconds(8); + [DataField("validReagentGases", required: true)] + public HashSet ValidGases = default!; [ViewVariables] - public LungStatus Status { get; set; } - - [DataField("cycleDelay")] - public float CycleDelay { get; set; } = 2; -} - -public enum LungStatus -{ - None = 0, - Inhaling, - Exhaling + public Solution LungSolution = default!; } diff --git a/Content.Server/Body/Components/RespiratorComponent.cs b/Content.Server/Body/Components/RespiratorComponent.cs index 33c27e7b7a..0f253985f8 100644 --- a/Content.Server/Body/Components/RespiratorComponent.cs +++ b/Content.Server/Body/Components/RespiratorComponent.cs @@ -1,6 +1,5 @@ -using System.Collections.Generic; +using System; using Content.Server.Body.Systems; -using Content.Shared.Atmos; using Content.Shared.Damage; using Robust.Shared.Analyzers; using Robust.Shared.GameObjects; @@ -12,19 +11,26 @@ namespace Content.Server.Body.Components [RegisterComponent, Friend(typeof(RespiratorSystem))] public class RespiratorComponent : Component { - [ViewVariables] - [DataField("needsGases")] - public Dictionary NeedsGases { get; set; } = new(); + /// + /// 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("producesGases")] - public Dictionary ProducesGases { get; set; } = new(); + /// + /// At what level of saturation will you begin to suffocate? + /// + [DataField("suffocationThreshold")] + public float SuffocationThreshold; - [ViewVariables] - [DataField("deficitGases")] - public Dictionary DeficitGases { get; set; } = new(); + [DataField("maxSaturation")] + public float MaxSaturation = 5.0f; - [ViewVariables] public bool Suffocating { get; set; } + [DataField("minSaturation")] + public float MinSaturation = -5.0f; + + // TODO HYPEROXIA? [DataField("damage", required: true)] [ViewVariables(VVAccess.ReadWrite)] @@ -34,6 +40,36 @@ 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 b6a0cd235c..98e637fe53 100644 --- a/Content.Server/Body/Systems/BloodstreamSystem.cs +++ b/Content.Server/Body/Systems/BloodstreamSystem.cs @@ -44,34 +44,4 @@ 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 new file mode 100644 index 0000000000..4a8a7a1367 --- /dev/null +++ b/Content.Server/Body/Systems/InternalsSystem.cs @@ -0,0 +1,25 @@ +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 3d0f5dd0d4..59838b81f5 100644 --- a/Content.Server/Body/Systems/LungSystem.cs +++ b/Content.Server/Body/Systems/LungSystem.cs @@ -1,33 +1,28 @@ -using System; -using Content.Server.Atmos; +using System.Linq; using Content.Server.Atmos.Components; using Content.Server.Atmos.EntitySystems; using Content.Server.Body.Components; -using Content.Server.Popups; +using Content.Server.Chemistry.EntitySystems; using Content.Shared.Atmos; -using Content.Shared.Body.Components; -using Content.Shared.Body.Events; +using Content.Shared.Chemistry.Components; using Content.Shared.Inventory.Events; -using Content.Shared.MobState.Components; using Robust.Shared.GameObjects; using Robust.Shared.IoC; -using Robust.Shared.Localization; -using Robust.Shared.Player; -using Robust.Shared.Timing; +using Robust.Shared.Utility; namespace Content.Server.Body.Systems; public class LungSystem : EntitySystem { - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly AtmosphereSystem _atmosSys = default!; - [Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; + [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; + + public static string LungSolutionName = "Lung"; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnAddedToBody); + SubscribeLocalEvent(OnComponentInit); SubscribeLocalEvent(OnGotEquipped); SubscribeLocalEvent(OnGotUnequipped); } @@ -50,175 +45,30 @@ public class LungSystem : EntitySystem } } - private void OnAddedToBody(EntityUid uid, LungComponent component, AddedToBodyEvent args) + private void OnComponentInit(EntityUid uid, LungComponent component, ComponentInit args) { - Inhale(uid, component.CycleDelay); + component.LungSolution = _solutionContainerSystem.EnsureSolution(uid, LungSolutionName); + component.LungSolution.MaxVolume = 100.0f; + component.LungSolution.CanReact = false; // No dexalin lungs } - public void Gasp(EntityUid uid, - LungComponent? lung=null, - MechanismComponent? mech=null) + public void GasToReagent(EntityUid uid, LungComponent lung) { - if (!Resolve(uid, ref lung, ref mech)) - return; - - if (_gameTiming.CurTime >= lung.LastGaspPopupTime + lung.GaspPopupCooldown) + foreach (var gas in lung.ValidGases) { - lung.LastGaspPopupTime = _gameTiming.CurTime; - _popupSystem.PopupEntity(Loc.GetString("lung-behavior-gasp"), uid, Filter.Pvs(uid)); + 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. } - - 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 477a4782b0..e5d211a78c 100644 --- a/Content.Server/Body/Systems/MetabolizerSystem.cs +++ b/Content.Server/Body/Systems/MetabolizerSystem.cs @@ -77,6 +77,8 @@ 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) @@ -144,6 +146,10 @@ 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 @@ -153,7 +159,8 @@ namespace Content.Server.Body.Systems continue; } - var args = new ReagentEffectArgs(solutionEntityUid.Value, (meta).Owner, solution, proto, entry.MetabolismRate, + var actualEntity = bodyEntityUid != null ? bodyEntityUid.Value : solutionEntityUid.Value; + var args = new ReagentEffectArgs(actualEntity, (meta).Owner, solution, proto, mostToRemove, EntityManager, null); // do all effects, if conditions apply @@ -164,9 +171,8 @@ 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 {entity} at {Transform(entity).Coordinates:coordinates}"); + $"Metabolism effect {effect.GetType().Name:effect} of reagent {args.Reagent.Name:reagent} applied on entity {actualEntity:entity} at {Transform(actualEntity).Coordinates:coordinates}"); } effect.Effect(args); diff --git a/Content.Server/Body/Systems/RespiratorSystem.cs b/Content.Server/Body/Systems/RespiratorSystem.cs index 323a019a05..5c71f3035a 100644 --- a/Content.Server/Body/Systems/RespiratorSystem.cs +++ b/Content.Server/Body/Systems/RespiratorSystem.cs @@ -1,9 +1,10 @@ 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; @@ -13,6 +14,9 @@ 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 { @@ -23,14 +27,25 @@ 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, blood, body) in - EntityManager.EntityQuery()) + foreach (var (respirator, body) in + EntityManager.EntityQuery()) { var uid = respirator.Owner; if (!EntityManager.TryGetComponent(uid, out var state) || @@ -41,211 +56,142 @@ namespace Content.Server.Body.Systems respirator.AccumulatedFrametime += frameTime; - if (respirator.AccumulatedFrametime < 1) - { + if (respirator.AccumulatedFrametime < respirator.CycleDelay) continue; + respirator.AccumulatedFrametime -= respirator.CycleDelay; + UpdateSaturation(respirator.Owner, -respirator.CycleDelay, respirator); + + switch (respirator.Status) + { + case RespiratorStatus.Inhaling: + Inhale(uid, body); + respirator.Status = RespiratorStatus.Exhaling; + break; + case RespiratorStatus.Exhaling: + Exhale(uid, body); + respirator.Status = RespiratorStatus.Inhaling; + break; } - ProcessGases(uid, respirator, blood, body); - - respirator.AccumulatedFrametime -= 1; - - if (SuffocatingPercentage(respirator) > 0) + 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)); + } + TakeSuffocationDamage(uid, respirator); + respirator.SuffocationCycles += 1; continue; } StopSuffocation(uid, respirator); + respirator.SuffocationCycles = 0; } } - private Dictionary NeedsAndDeficit(RespiratorComponent respirator) + public void Inhale(EntityUid uid, SharedBodyComponent? body=null) { - var needs = new Dictionary(respirator.NeedsGases); - foreach (var (gas, amount) in respirator.DeficitGases) - { - var newAmount = (needs.GetValueOrDefault(gas) + amount); - 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, - BloodstreamComponent? bloodstream, - SharedBodyComponent? body) - { - if (!Resolve(uid, ref bloodstream, ref body, false)) + if (!Resolve(uid, ref body, false)) return; - var lungs = _bodySystem.GetComponentsOnMechanisms(uid, body).ToArray(); + var organs = _bodySystem.GetComponentsOnMechanisms(uid, body).ToArray(); - var needs = NeedsAndDeficit(respirator); - var used = 0f; + // Inhale gas + var ev = new InhaleLocationEvent(); + RaiseLocalEvent(uid, ev, false); - foreach (var (lung, mech) in lungs) + if (ev.Gas == null) { - _lungSystem.UpdateLung(lung.Owner, lung, mech); + ev.Gas = _atmosSys.GetTileMixture(Transform(uid).Coordinates); + if (ev.Gas == null) return; } - foreach (var (gas, amountNeeded) in needs) + var ratio = (Atmospherics.BreathVolume / ev.Gas.Volume); + var actualGas = ev.Gas.RemoveRatio(ratio); + + var lungRatio = 1.0f / organs.Length; + var gas = organs.Length == 1 ? actualGas : actualGas.RemoveRatio(lungRatio); + foreach (var (lung, _) in organs) { - var bloodstreamAmount = bloodstream.Air.GetMoles(gas); - var deficit = 0f; + // Merge doesn't remove gas from the giver. + _atmosSys.Merge(lung.Air, gas); + _lungSystem.GasToReagent(lung.Owner, lung); + } + } - if (bloodstreamAmount < amountNeeded) - { - // Panic inhale - foreach (var (lung, mech) in lungs) - { - _lungSystem.Gasp((lung).Owner, lung, mech); - } + public void Exhale(EntityUid uid, SharedBodyComponent? body=null) + { + if (!Resolve(uid, ref body, false)) + return; - bloodstreamAmount = bloodstream.Air.GetMoles(gas); + var organs = _bodySystem.GetComponentsOnMechanisms(uid, body).ToArray(); - deficit = Math.Max(0, amountNeeded - bloodstreamAmount); + // exhale gas - if (deficit > 0) - { - bloodstream.Air.SetMoles(gas, 0); - } - else - { - bloodstream.Air.AdjustMoles(gas, -amountNeeded); - } - } - else - { - bloodstream.Air.AdjustMoles(gas, -amountNeeded); - } + var ev = new ExhaleLocationEvent(); + RaiseLocalEvent(uid, ev, false); - respirator.DeficitGases[gas] = deficit; - - used += (amountNeeded - deficit) / amountNeeded; + if (ev.Gas == null) + { + ev.Gas = _atmosSys.GetTileMixture(Transform(uid).Coordinates); + if (ev.Gas == null) return; } - var produced = GasProduced(respirator, used / needs.Count); - - foreach (var (gas, amountProduced) in produced) + var outGas = new GasMixture(ev.Gas.Volume); + foreach (var (lung, _) in organs) { - bloodstream.Air.AdjustMoles(gas, amountProduced); + _atmosSys.Merge(outGas, lung.Air); + lung.Air.Clear(); + lung.LungSolution.RemoveAllSolution(); } - ClampDeficit(respirator); + _atmosSys.Merge(ev.Gas, outGas); } private void TakeSuffocationDamage(EntityUid uid, RespiratorComponent respirator) { - if (!respirator.Suffocating) + if (respirator.SuffocationCycles == 2) _logSys.Add(LogType.Asphyxiation, $"{ToPrettyString(uid):entity} started suffocating"); - respirator.Suffocating = true; - - _alertsSystem.ShowAlert(uid, AlertType.LowOxygen); + if (respirator.SuffocationCycles >= respirator.SuffocationCycleThreshold) + { + _alertsSystem.ShowAlert(uid, AlertType.LowOxygen); + } _damageableSys.TryChangeDamage(uid, respirator.Damage, true, false); } private void StopSuffocation(EntityUid uid, RespiratorComponent respirator) { - if (respirator.Suffocating) + if (respirator.SuffocationCycles >= 2) _logSys.Add(LogType.Asphyxiation, $"{ToPrettyString(uid):entity} stopped suffocating"); - respirator.Suffocating = false; - _alertsSystem.ClearAlert(uid, AlertType.LowOxygen); _damageableSys.TryChangeDamage(uid, respirator.DamageRecovery, true); } - public GasMixture Clean(EntityUid uid, RespiratorComponent respirator, BloodstreamComponent bloodstream) + public void UpdateSaturation(EntityUid uid, float amount, + RespiratorComponent? respirator = null) { - var gasMixture = new GasMixture(bloodstream.Air.Volume) - { - Temperature = bloodstream.Air.Temperature - }; + if (!Resolve(uid, ref respirator, false)) + return; - 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; + respirator.Saturation += amount; + respirator.Saturation = + Math.Clamp(respirator.Saturation, respirator.MinSaturation, respirator.MaxSaturation); } } } + +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 new file mode 100644 index 0000000000..841d760f89 --- /dev/null +++ b/Content.Server/Chemistry/ReagentEffects/AdjustAlert.cs @@ -0,0 +1,46 @@ +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 new file mode 100644 index 0000000000..c9bbe452af --- /dev/null +++ b/Content.Server/Chemistry/ReagentEffects/ModifyLungGas.cs @@ -0,0 +1,24 @@ +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 new file mode 100644 index 0000000000..d7f7cb3771 --- /dev/null +++ b/Content.Server/Chemistry/ReagentEffects/Oxygenate.cs @@ -0,0 +1,22 @@ +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 8c7d65ef76..db81212271 100644 --- a/Content.Shared/Alert/AlertCategory.cs +++ b/Content.Shared/Alert/AlertCategory.cs @@ -12,5 +12,6 @@ public enum AlertCategory Health, Piloting, Hunger, - Thirst -} \ No newline at end of file + Thirst, + Toxins +} diff --git a/Content.Shared/Alert/AlertType.cs b/Content.Shared/Alert/AlertType.cs index 3c87650419..b6a7ef8763 100644 --- a/Content.Shared/Alert/AlertType.cs +++ b/Content.Shared/Alert/AlertType.cs @@ -31,6 +31,7 @@ Pulled, Pulling, Magboots, + Toxins, Debug1, Debug2, Debug3, diff --git a/Content.Shared/Atmos/Atmospherics.cs b/Content.Shared/Atmos/Atmospherics.cs index 6610f23570..bd9b2cc050 100644 --- a/Content.Shared/Atmos/Atmospherics.cs +++ b/Content.Shared/Atmos/Atmospherics.cs @@ -255,11 +255,10 @@ namespace Content.Shared.Atmos /// public const float NormalBodyTemperature = 37f; - public const float HumanNeededOxygen = MolesCellStandard * BreathPercentage * 0.16f; - - public const float HumanProducedOxygen = HumanNeededOxygen * 0.75f; - - public const float HumanProducedCarbonDioxide = HumanNeededOxygen * 0.25f; + /// + /// I hereby decree. This is Arbitrary Suck my Dick + /// + public const float BreathMolesToReagentMultiplier = 1144; #region Pipes diff --git a/Content.Shared/Atmos/Prototypes/GasPrototype.cs b/Content.Shared/Atmos/Prototypes/GasPrototype.cs index fdf4ba643a..790b44712b 100644 --- a/Content.Shared/Atmos/Prototypes/GasPrototype.cs +++ b/Content.Shared/Atmos/Prototypes/GasPrototype.cs @@ -1,5 +1,7 @@ -using Robust.Shared.Prototypes; +using Content.Shared.Chemistry.Reagent; +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 @@ -70,6 +72,12 @@ 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 2c4bd70be7..5e02ebe15e 100644 --- a/Resources/Prototypes/Alerts/alerts.yml +++ b/Resources/Prototypes/Alerts/alerts.yml @@ -25,7 +25,16 @@ sprite: /Textures/Interface/Alerts/breathing.rsi state: not_enough_oxy name: "[color=red]Low Oxygen[/color]" - description: "There is not [color=red]not enough oxygen[/color] in the air you are breathing. Put on [color=green]internals[/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." - type: alert alertType: LowPressure diff --git a/Resources/Prototypes/Atmospherics/gases.yml b/Resources/Prototypes/Atmospherics/gases.yml index 7355812aba..2ff48cede3 100644 --- a/Resources/Prototypes/Atmospherics/gases.yml +++ b/Resources/Prototypes/Atmospherics/gases.yml @@ -5,6 +5,7 @@ heatCapacityRatio: 1.4 molarMass: 32 color: 2887E8 + reagent: Oxygen - type: gas id: 1 @@ -13,6 +14,7 @@ heatCapacityRatio: 1.4 molarMass: 28 color: DA1010 + reagent: Nitrogen - type: gas id: 2 @@ -21,6 +23,7 @@ heatCapacityRatio: 1.3 molarMass: 44 color: 4e4e4e + reagent: CarbonDioxide - type: gas id: 3 @@ -31,6 +34,7 @@ gasOverlaySprite: /Textures/Effects/atmospherics.rsi gasOverlayState: plasma color: FF3300 + reagent: Plasma - type: gas id: 4 @@ -41,6 +45,7 @@ gasOverlaySprite: /Textures/Effects/atmospherics.rsi gasOverlayState: tritium color: 13FF4B + reagent: Tritium - type: gas id: 5 @@ -51,3 +56,4 @@ 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 d43ced7573..84c2201f6f 100644 --- a/Resources/Prototypes/Body/Mechanisms/human.yml +++ b/Resources/Prototypes/Body/Mechanisms/human.yml @@ -100,6 +100,19 @@ 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 add05c09bf..9e744775c1 100644 --- a/Resources/Prototypes/Body/Mechanisms/slime.yml +++ b/Resources/Prototypes/Body/Mechanisms/slime.yml @@ -11,7 +11,6 @@ - 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 9fdf956298..e00b6caaab 100644 --- a/Resources/Prototypes/Body/Parts/animal.yml +++ b/Resources/Prototypes/Body/Parts/animal.yml @@ -82,6 +82,19 @@ 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 f311b8eb36..fc59edd81f 100644 --- a/Resources/Prototypes/Chemistry/metabolism_groups.yml +++ b/Resources/Prototypes/Chemistry/metabolism_groups.yml @@ -16,3 +16,7 @@ - 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 9771436550..cfa0171208 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml @@ -152,17 +152,12 @@ normalBodyTemperature: 310.15 thermalRegulationTemperatureThreshold: 25 - type: Respirator - needsGases: - Oxygen: 0.00060763888 - producesGases: - Oxygen: 0.00045572916 - CarbonDioxide: 0.00015190972 damage: types: - Asphyxiation: 1 + Asphyxiation: 3 damageRecovery: types: - Asphyxiation: -1 + Asphyxiation: -1.5 - 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 5cbd2e613f..a7faed6b1a 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: 1 + Asphyxiation: 3 damageRecovery: types: - Asphyxiation: -1 + Asphyxiation: -1.5 - 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 0fea50040f..f7a8e785b3 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/human.yml @@ -11,6 +11,13 @@ 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 bab9b6403d..e07ba16064 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml @@ -9,6 +9,13 @@ - 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 a95472b3be..5afb2bfabc 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml @@ -199,18 +199,6 @@ 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 d26ea684d9..33c35a6bd8 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/vox.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/vox.yml @@ -83,18 +83,14 @@ - 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: 1 + Asphyxiation: 3 damageRecovery: types: - Asphyxiation: -1 + Asphyxiation: -1.5 # - type: Appearance # visuals: # - type: RotationVisualizer diff --git a/Resources/Prototypes/Reagents/elements.yml b/Resources/Prototypes/Reagents/elements.yml index 30b01be70b..9b0f65b619 100644 --- a/Resources/Prototypes/Reagents/elements.yml +++ b/Resources/Prototypes/Reagents/elements.yml @@ -134,26 +134,6 @@ 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 new file mode 100644 index 0000000000..90d0971c2a --- /dev/null +++ b/Resources/Prototypes/Reagents/gases.yml @@ -0,0 +1,121 @@ +- 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 5f517a79a5..4531ca855d 100644 --- a/Resources/Prototypes/Reagents/toxins.yml +++ b/Resources/Prototypes/Reagents/toxins.yml @@ -133,35 +133,6 @@ 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