using System; using Content.Server.Atmos; using Content.Server.Atmos.Components; using Content.Server.Atmos.EntitySystems; using Content.Server.Body.Components; using Content.Server.Popups; using Content.Shared.Atmos; using Content.Shared.Body.Components; using Content.Shared.Body.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; 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!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnAddedToBody); } private void OnAddedToBody(EntityUid uid, LungComponent component, AddedToBodyEvent args) { Inhale(uid, component.CycleDelay); } public void Gasp(EntityUid uid, LungComponent? lung=null, SharedMechanismComponent? mech=null) { if (!Resolve(uid, ref lung)) return; if (_gameTiming.CurTime >= lung.LastGaspPopupTime + lung.GaspPopupCooldown) { lung.LastGaspPopupTime = _gameTiming.CurTime; _popupSystem.PopupEntity(Loc.GetString("lung-behavior-gasp"), uid, Filter.Pvs(uid)); } Inhale(uid, lung.CycleDelay); } public void UpdateLung(EntityUid uid, LungComponent? lung=null, SharedMechanismComponent? mech=null) { if (!Resolve(uid, ref lung, ref mech)) return; if (mech.Body != null && EntityManager.TryGetComponent(mech.Body.OwnerUid, 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, SharedMechanismComponent? 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.OwnerUid, out InternalsComponent? internals) && internals.BreathToolEntity != null && internals.GasTankEntity != null && internals.BreathToolEntity.TryGetComponent(out BreathToolComponent? breathTool) && breathTool.IsFunctional && internals.GasTankEntity.TryGetComponent(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, SharedMechanismComponent? 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.OwnerUid, 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, SharedMechanismComponent? 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, SharedMechanismComponent? 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.OwnerUid, out BloodstreamComponent? bloodstream)) return; _bloodstreamSystem.PumpToxins(mech.Body.OwnerUid, lung.Air, bloodstream); var lungRemoved = lung.Air.RemoveRatio(0.5f); _atmosSys.Merge(to, lungRemoved); } }