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)
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;
}
}
}
- 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
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;
+ 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)
+ public void Exhale(Entity<RespiratorComponent> entity)
{
- if (!Resolve(entity, ref entity.Comp, logMissing: false))
- return;
-
// exhale gas
var ev = new ExhaleLocationEvent();
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>
}
/// <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 ev = new CanMetabolizeGasEvent(gas);
+ RaiseLocalEvent(ent, ref ev);
+
+ if (!ev.Handled || ev.Toxic)
+ return false;
+
+ return ev.Saturation > ent.Comp.UpdateInterval.TotalSeconds;
+ }
+
+ /// <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 false;
+ return;
- 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);
+ var solution = _lungSystem.GasToReagent(args.Gas);
- float saturation = 0;
+ 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)
_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>
_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)
_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)
private void OnGasInhaled(Entity<BodyComponent> entity, ref InhaledGasEvent args)
{
+ if (args.Handled)
+ return;
+
args.Handled = true;
args.Succeeded = TryInhaleGasToBody((entity, entity.Comp), args.Gas);
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);