Fix Respirator Asserts (#38911)
* Fix errors * Cleanup CanMetabolizeInhaledAir * Wait no don't do that * Revert changes for real * Fix * Code cleanup and some safety rails * Better tests and also comments * Better comments --------- Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
2715933a46
commit
535646aefb
@@ -10,16 +10,6 @@ namespace Content.Server.Body.Components
|
||||
[RegisterComponent, Access(typeof(RespiratorSystem)), AutoGenerateComponentPause]
|
||||
public sealed partial class RespiratorComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Gas container for this entity
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public GasMixture Air = new()
|
||||
{
|
||||
Volume = 6, // 6 liters, the average lung capacity for a human according to Google
|
||||
Temperature = Atmospherics.NormalBodyTemperature
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Volume of our breath in liters
|
||||
/// </summary>
|
||||
|
||||
@@ -48,7 +48,7 @@ public sealed class InternalsSystem : SharedInternalsSystem
|
||||
return;
|
||||
|
||||
// Could the entity metabolise the air in the linked gas tank?
|
||||
if (!_respirator.CanMetabolizeGas(uid, tank.Value.Comp.Air))
|
||||
if (!_respirator.CanMetabolizeInhaledAir(uid, tank.Value.Comp.Air))
|
||||
return;
|
||||
|
||||
ToggleInternals(uid, uid, force: false, component, ToggleMode.On);
|
||||
|
||||
@@ -49,8 +49,13 @@ public sealed class RespiratorSystem : EntitySystem
|
||||
UpdatesAfter.Add(typeof(MetabolizerSystem));
|
||||
SubscribeLocalEvent<RespiratorComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<RespiratorComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
|
||||
|
||||
// BodyComp stuff
|
||||
SubscribeLocalEvent<BodyComponent, InhaledGasEvent>(OnGasInhaled);
|
||||
SubscribeLocalEvent<BodyComponent, ExhaledGasEvent>(OnGasExhaled);
|
||||
SubscribeLocalEvent<BodyComponent, CanMetabolizeGasEvent>(CanBodyMetabolizeGas);
|
||||
SubscribeLocalEvent<BodyComponent, SuffocationEvent>(OnSuffocation);
|
||||
SubscribeLocalEvent<BodyComponent, StopSuffocatingEvent>(OnStopSuffocating);
|
||||
}
|
||||
|
||||
private void OnMapInit(Entity<RespiratorComponent> ent, ref MapInitEvent args)
|
||||
@@ -80,11 +85,11 @@ public sealed class RespiratorSystem : EntitySystem
|
||||
switch (respirator.Status)
|
||||
{
|
||||
case RespiratorStatus.Inhaling:
|
||||
Inhale(uid);
|
||||
Inhale((uid, respirator));
|
||||
respirator.Status = RespiratorStatus.Exhaling;
|
||||
break;
|
||||
case RespiratorStatus.Exhaling:
|
||||
Exhale(uid);
|
||||
Exhale((uid, respirator));
|
||||
respirator.Status = RespiratorStatus.Inhaling;
|
||||
break;
|
||||
}
|
||||
@@ -111,10 +116,10 @@ public sealed class RespiratorSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
public bool Inhale(Entity<RespiratorComponent?> entity)
|
||||
public void Inhale(Entity<RespiratorComponent?> entity)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp, logMissing: false))
|
||||
return false;
|
||||
return;
|
||||
|
||||
// Inhale gas
|
||||
var ev = new InhaleLocationEvent
|
||||
@@ -126,23 +131,22 @@ public sealed class RespiratorSystem : EntitySystem
|
||||
ev.Gas ??= _atmosSys.GetContainingMixture(entity.Owner, excite: true);
|
||||
|
||||
if (ev.Gas is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return;
|
||||
|
||||
var gas = ev.Gas.RemoveVolume(entity.Comp.BreathVolume);
|
||||
|
||||
var inhaleEv = new InhaledGasEvent(gas);
|
||||
RaiseLocalEvent(entity, ref inhaleEv);
|
||||
|
||||
return inhaleEv.Handled && inhaleEv.Succeeded;
|
||||
}
|
||||
|
||||
public void Exhale(Entity<RespiratorComponent?> entity)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp, logMissing: false))
|
||||
if (inhaleEv.Handled && inhaleEv.Succeeded)
|
||||
return;
|
||||
|
||||
// If nothing could inhale the gas give it back.
|
||||
_atmosSys.Merge(ev.Gas, gas);
|
||||
}
|
||||
|
||||
public void Exhale(Entity<RespiratorComponent> entity)
|
||||
{
|
||||
// exhale gas
|
||||
|
||||
var ev = new ExhaleLocationEvent();
|
||||
@@ -157,8 +161,16 @@ public sealed class RespiratorSystem : EntitySystem
|
||||
ev.Gas ??= GasMixture.SpaceGas;
|
||||
}
|
||||
|
||||
var exhaleEv = new ExhaledGasEvent(ev.Gas);
|
||||
RaiseLocalEvent(entity, ref exhaleEv);
|
||||
Exhale(entity!, ev.Gas);
|
||||
}
|
||||
|
||||
public void Exhale(Entity<RespiratorComponent?> entity, GasMixture gas)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp, logMissing: false))
|
||||
return;
|
||||
|
||||
var ev = new ExhaledGasEvent(gas);
|
||||
RaiseLocalEvent(entity, ref ev);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -176,52 +188,76 @@ public sealed class RespiratorSystem : EntitySystem
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check whether or not an entity can metabolize inhaled air without suffocating or taking damage (i.e., no toxic
|
||||
/// gasses).
|
||||
/// Checks if it's safe for a given entity to breathe the air from the environment it is currently situated in.
|
||||
/// </summary>
|
||||
/// <param name="ent">The entity attempting to metabolize the gas.</param>
|
||||
/// <returns>Returns true only if the air is not toxic, and it wouldn't suffocate.</returns>
|
||||
public bool CanMetabolizeInhaledAir(Entity<RespiratorComponent?> ent)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return false;
|
||||
|
||||
if (!Inhale(ent))
|
||||
return false;
|
||||
// Get the gas at our location but don't actually remove it from the gas mixture.
|
||||
var ev = new InhaleLocationEvent
|
||||
{
|
||||
Respirator = ent.Comp,
|
||||
};
|
||||
RaiseLocalEvent(ent, ref ev);
|
||||
|
||||
// If we don't have a body we can't be poisoned by gas, yet...
|
||||
var success = TryMetabolizeGas((ent, ent.Comp));
|
||||
ev.Gas ??= _atmosSys.GetContainingMixture(ent.Owner, excite: true);
|
||||
|
||||
// Don't keep that gas in our lungs lest it poisons a poor nuclear operative.
|
||||
Exhale(ent);
|
||||
return success;
|
||||
// If there's no air to breathe or we can't metabolize it then internals should be on.
|
||||
return ev.Gas is not null && CanMetabolizeInhaledAir(ent, ev.Gas);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check whether or not an entity can metabolize the given gas mixture without suffocating or taking damage
|
||||
/// (i.e., no toxic gasses).
|
||||
/// Checks if a given entity can safely metabolize a given gas mixture.
|
||||
/// </summary>
|
||||
public bool CanMetabolizeGas(Entity<RespiratorComponent?> ent, GasMixture gas)
|
||||
/// <param name="ent">The entity attempting to metabolize the gas.</param>
|
||||
/// <param name="gas">The gas mixture we are trying to metabolize.</param>
|
||||
/// <returns>Returns true only if the gas mixture is not toxic, and it wouldn't suffocate.</returns>
|
||||
public bool CanMetabolizeInhaledAir(Entity<RespiratorComponent?> ent, GasMixture gas)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return false;
|
||||
|
||||
var organs = _bodySystem.GetBodyOrganEntityComps<LungComponent>((ent, null));
|
||||
if (organs.Count == 0)
|
||||
var ev = new CanMetabolizeGasEvent(gas);
|
||||
RaiseLocalEvent(ent, ref ev);
|
||||
|
||||
if (!ev.Handled || ev.Toxic)
|
||||
return false;
|
||||
|
||||
gas = new GasMixture(gas);
|
||||
var lungRatio = 1.0f / organs.Count;
|
||||
gas.Multiply(MathF.Min(lungRatio * gas.Volume / ent.Comp.BreathVolume, lungRatio));
|
||||
var solution = _lungSystem.GasToReagent(gas);
|
||||
return ev.Saturation > ent.Comp.UpdateInterval.TotalSeconds;
|
||||
}
|
||||
|
||||
float saturation = 0;
|
||||
/// <summary>
|
||||
/// Tries to safely metabolize the current solutions in a body's lungs.
|
||||
/// </summary>
|
||||
private void CanBodyMetabolizeGas(Entity<BodyComponent> ent, ref CanMetabolizeGasEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
var organs = _bodySystem.GetBodyOrganEntityComps<LungComponent>((ent, null));
|
||||
if (organs.Count == 0)
|
||||
return;
|
||||
|
||||
var solution = _lungSystem.GasToReagent(args.Gas);
|
||||
|
||||
var saturation = 0f;
|
||||
foreach (var organ in organs)
|
||||
{
|
||||
saturation += GetSaturation(solution, organ.Owner, out var toxic);
|
||||
if (toxic)
|
||||
return false;
|
||||
if (!toxic)
|
||||
continue;
|
||||
|
||||
args.Handled = true;
|
||||
args.Toxic = true;
|
||||
return;
|
||||
}
|
||||
|
||||
return saturation > ent.Comp.UpdateInterval.TotalSeconds;
|
||||
args.Handled = true;
|
||||
args.Saturation = saturation;
|
||||
}
|
||||
|
||||
public bool TryInhaleGasToBody(Entity<BodyComponent?> entity, GasMixture gas)
|
||||
@@ -265,30 +301,6 @@ public sealed class RespiratorSystem : EntitySystem
|
||||
_atmosSys.Merge(gas, outGas);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to safely metabolize the current solutions in a body's lungs.
|
||||
/// </summary>
|
||||
private bool TryMetabolizeGas(Entity<RespiratorComponent, BodyComponent?> ent)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp2))
|
||||
return false;
|
||||
|
||||
var organs = _bodySystem.GetBodyOrganEntityComps<LungComponent>((ent, null));
|
||||
if (organs.Count == 0)
|
||||
return false;
|
||||
|
||||
float saturation = 0;
|
||||
foreach (var organ in organs)
|
||||
{
|
||||
var solution = _lungSystem.GasToReagent(organ.Comp1.Air);
|
||||
saturation += GetSaturation(solution, organ.Owner, out var toxic);
|
||||
if (toxic)
|
||||
return false;
|
||||
}
|
||||
|
||||
return saturation > ent.Comp1.UpdateInterval.TotalSeconds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the amount of saturation that would be generated if the lung were to metabolize the given solution.
|
||||
/// </summary>
|
||||
@@ -354,15 +366,11 @@ public sealed class RespiratorSystem : EntitySystem
|
||||
|
||||
_damageableSys.TryChangeDamage(ent, ent.Comp.Damage, interruptsDoAfters: false);
|
||||
|
||||
if (ent.Comp.SuffocationCycles >= ent.Comp.SuffocationCycleThreshold)
|
||||
{
|
||||
// TODO: This is not going work with multiple different lungs, if that ever becomes a possibility
|
||||
var organs = _bodySystem.GetBodyOrganEntityComps<LungComponent>((ent, null));
|
||||
foreach (var entity in organs)
|
||||
{
|
||||
_alertsSystem.ShowAlert(ent, entity.Comp1.Alert);
|
||||
}
|
||||
}
|
||||
if (ent.Comp.SuffocationCycles < ent.Comp.SuffocationCycleThreshold)
|
||||
return;
|
||||
|
||||
var ev = new SuffocationEvent();
|
||||
RaiseLocalEvent(ent, ref ev);
|
||||
}
|
||||
|
||||
private void StopSuffocation(Entity<RespiratorComponent> ent)
|
||||
@@ -372,6 +380,22 @@ public sealed class RespiratorSystem : EntitySystem
|
||||
|
||||
_damageableSys.TryChangeDamage(ent, ent.Comp.DamageRecovery);
|
||||
|
||||
var ev = new StopSuffocatingEvent();
|
||||
RaiseLocalEvent(ent, ref ev);
|
||||
}
|
||||
|
||||
private void OnSuffocation(Entity<BodyComponent> ent, ref SuffocationEvent args)
|
||||
{
|
||||
// TODO: This is not going work with multiple different lungs, if that ever becomes a possibility
|
||||
var organs = _bodySystem.GetBodyOrganEntityComps<LungComponent>((ent, null));
|
||||
foreach (var entity in organs)
|
||||
{
|
||||
_alertsSystem.ShowAlert(ent, entity.Comp1.Alert);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStopSuffocating(Entity<BodyComponent> ent, ref StopSuffocatingEvent args)
|
||||
{
|
||||
// TODO: This is not going work with multiple different lungs, if that ever becomes a possibility
|
||||
var organs = _bodySystem.GetBodyOrganEntityComps<LungComponent>((ent, null));
|
||||
foreach (var entity in organs)
|
||||
@@ -397,6 +421,9 @@ public sealed class RespiratorSystem : EntitySystem
|
||||
|
||||
private void OnGasInhaled(Entity<BodyComponent> entity, ref InhaledGasEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
|
||||
args.Succeeded = TryInhaleGasToBody((entity, entity.Comp), args.Gas);
|
||||
@@ -404,20 +431,65 @@ public sealed class RespiratorSystem : EntitySystem
|
||||
|
||||
private void OnGasExhaled(Entity<BodyComponent> entity, ref ExhaledGasEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
|
||||
RemoveGasFromBody(entity, args.Gas);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when an entity first tries to inhale that returns a GasMixture from a given location.
|
||||
/// </summary>
|
||||
/// <param name="Gas">The gas that gets returned, null if there is none.</param>
|
||||
/// <param name="Respirator">The Respirator component of the entity attempting to inhale</param>
|
||||
[ByRefEvent]
|
||||
public record struct InhaleLocationEvent(GasMixture? Gas, RespiratorComponent Respirator);
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when an entity first tries to exhale a gas, determines where the gas they're exhaling will be sent.
|
||||
/// </summary>
|
||||
/// <param name="Gas">The gas mixture that the exhaled gas will be merged into.</param>
|
||||
[ByRefEvent]
|
||||
public record struct ExhaleLocationEvent(GasMixture? Gas);
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when an entity successfully inhales a gas, attempts to find a place to put the gas.
|
||||
/// </summary>
|
||||
/// <param name="Gas">The gas we're inhaling.</param>
|
||||
/// <param name="Handled">Whether a system has responded appropriately.</param>
|
||||
/// <param name="Succeeded">Whether we successfully managed to inhale the gas</param>
|
||||
[ByRefEvent]
|
||||
public record struct InhaledGasEvent(GasMixture Gas, bool Handled = false, bool Succeeded = false);
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when an entity is exhaling
|
||||
/// </summary>
|
||||
/// <param name="Gas">The gas mixture we're exhaling into.</param>
|
||||
/// <param name="Handled">Whether we have successfully exhaled or not.</param>
|
||||
[ByRefEvent]
|
||||
public record struct ExhaledGasEvent(GasMixture Gas, bool Handled = false);
|
||||
|
||||
/// <summary>
|
||||
/// Raised when an entity starts suffocating and when suffocation progresses.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct SuffocationEvent;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when an entity that was suffocating stops suffocating.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct StopSuffocatingEvent;
|
||||
|
||||
/// <summary>
|
||||
/// An event raised to inhalation handlers that asks them nicely to simulate what it would be like to metabolize
|
||||
/// a given volume of gas, without actually metabolizing it.
|
||||
/// </summary>
|
||||
/// <param name="Gas">The gas mixture we are testing.</param>
|
||||
/// <param name="Toxic">Whether the gas returns as toxic to any respirator.</param>
|
||||
/// <param name="Saturation">The amount of saturation we got from the gas.</param>
|
||||
[ByRefEvent]
|
||||
public record struct CanMetabolizeGasEvent(GasMixture Gas, bool Toxic = false, float Saturation = 0f, bool Handled = false);
|
||||
|
||||
Reference in New Issue
Block a user