Remove LungBehavior and replace with LungComponent/System (#5630)
This commit is contained in:
@@ -1,205 +0,0 @@
|
||||
using System;
|
||||
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.Server.Popups;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.MobState;
|
||||
using Content.Shared.MobState.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Body.Behavior
|
||||
{
|
||||
[DataDefinition]
|
||||
public class LungBehavior : MechanismBehavior, ISerializationHooks
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
private float _accumulatedFrameTime;
|
||||
|
||||
[ViewVariables] private TimeSpan _lastGaspPopupTime;
|
||||
|
||||
[DataField("air")]
|
||||
[ViewVariables]
|
||||
public GasMixture Air { get; set; } = new()
|
||||
{
|
||||
Volume = 6,
|
||||
Temperature = Atmospherics.NormalBodyTemperature
|
||||
};
|
||||
|
||||
[DataField("gaspPopupCooldown")]
|
||||
[ViewVariables]
|
||||
public TimeSpan GaspPopupCooldown { get; private set; } = TimeSpan.FromSeconds(8);
|
||||
|
||||
[ViewVariables] public LungStatus Status { get; set; }
|
||||
|
||||
[DataField("cycleDelay")]
|
||||
[ViewVariables]
|
||||
public float CycleDelay { get; set; } = 2;
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
protected override void OnAddedToBody(SharedBodyComponent body)
|
||||
{
|
||||
base.OnAddedToBody(body);
|
||||
Inhale(CycleDelay);
|
||||
}
|
||||
|
||||
public void Gasp()
|
||||
{
|
||||
if (_gameTiming.CurTime >= _lastGaspPopupTime + GaspPopupCooldown)
|
||||
{
|
||||
_lastGaspPopupTime = _gameTiming.CurTime;
|
||||
Owner.PopupMessageEveryone(Loc.GetString("lung-behavior-gasp"));
|
||||
}
|
||||
|
||||
Inhale(CycleDelay);
|
||||
}
|
||||
|
||||
public void Transfer(GasMixture from, GasMixture to, float ratio)
|
||||
{
|
||||
EntitySystem.Get<AtmosphereSystem>().Merge(to, from.RemoveRatio(ratio));
|
||||
}
|
||||
|
||||
public void ToBloodstream(GasMixture mixture)
|
||||
{
|
||||
if (Body == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Body.Owner.TryGetComponent(out BloodstreamComponent? bloodstream))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var to = bloodstream.Air;
|
||||
|
||||
EntitySystem.Get<AtmosphereSystem>().Merge(to, mixture);
|
||||
mixture.Clear();
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
if (Body != null && Body.Owner.TryGetComponent(out MobStateComponent? mobState) && mobState.IsCritical())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Status == LungStatus.None)
|
||||
{
|
||||
Status = LungStatus.Inhaling;
|
||||
}
|
||||
|
||||
_accumulatedFrameTime += Status switch
|
||||
{
|
||||
LungStatus.Inhaling => frameTime,
|
||||
LungStatus.Exhaling => -frameTime,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
var absoluteTime = Math.Abs(_accumulatedFrameTime);
|
||||
var delay = CycleDelay;
|
||||
|
||||
if (absoluteTime < delay)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (Status)
|
||||
{
|
||||
case LungStatus.Inhaling:
|
||||
Inhale(absoluteTime);
|
||||
Status = LungStatus.Exhaling;
|
||||
break;
|
||||
case LungStatus.Exhaling:
|
||||
Exhale(absoluteTime);
|
||||
Status = LungStatus.Inhaling;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
_accumulatedFrameTime = absoluteTime - delay;
|
||||
}
|
||||
|
||||
public void Inhale(float frameTime)
|
||||
{
|
||||
if (Body != null &&
|
||||
Body.Owner.TryGetComponent(out InternalsComponent? internals) &&
|
||||
internals.BreathToolEntity != null &&
|
||||
internals.GasTankEntity != null &&
|
||||
internals.BreathToolEntity.TryGetComponent(out BreathToolComponent? breathTool) &&
|
||||
breathTool.IsFunctional &&
|
||||
internals.GasTankEntity.TryGetComponent(out GasTankComponent? gasTank) &&
|
||||
gasTank.Air != null)
|
||||
{
|
||||
Inhale(frameTime, gasTank.RemoveAirVolume(Atmospherics.BreathVolume));
|
||||
return;
|
||||
}
|
||||
|
||||
if (EntitySystem.Get<AtmosphereSystem>().GetTileMixture(Owner.Transform.Coordinates, true) is not {} tileAir)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Inhale(frameTime, tileAir);
|
||||
}
|
||||
|
||||
public void Inhale(float frameTime, GasMixture from)
|
||||
{
|
||||
var ratio = (Atmospherics.BreathVolume / from.Volume) * frameTime;
|
||||
|
||||
Transfer(from, Air, ratio);
|
||||
ToBloodstream(Air);
|
||||
}
|
||||
|
||||
public void Exhale(float frameTime)
|
||||
{
|
||||
if (EntitySystem.Get<AtmosphereSystem>().GetTileMixture(Owner.Transform.Coordinates, true) is not {} tileAir)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Exhale(frameTime, tileAir);
|
||||
}
|
||||
|
||||
public void Exhale(float frameTime, GasMixture to)
|
||||
{
|
||||
// TODO: Make the bloodstream separately pump toxins into the lungs, making the lungs' only job to empty.
|
||||
if (Body == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Body.Owner.TryGetComponent(out BloodstreamComponent? bloodstream))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EntitySystem.Get<BloodstreamSystem>().PumpToxins(Body.OwnerUid, Air, bloodstream);
|
||||
|
||||
var lungRemoved = Air.RemoveRatio(0.5f);
|
||||
EntitySystem.Get<AtmosphereSystem>().Merge(to, lungRemoved);
|
||||
}
|
||||
}
|
||||
|
||||
public enum LungStatus
|
||||
{
|
||||
None = 0,
|
||||
Inhaling,
|
||||
Exhaling
|
||||
}
|
||||
}
|
||||
44
Content.Server/Body/Components/LungComponent.cs
Normal file
44
Content.Server/Body/Components/LungComponent.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Shared.Atmos;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Body.Components;
|
||||
|
||||
[RegisterComponent, Friend(typeof(LungSystem))]
|
||||
public class LungComponent : Component
|
||||
{
|
||||
public override string Name => "Lung";
|
||||
|
||||
public float AccumulatedFrametime;
|
||||
|
||||
[ViewVariables]
|
||||
public TimeSpan LastGaspPopupTime;
|
||||
|
||||
[DataField("air")]
|
||||
public GasMixture Air { get; set; } = new()
|
||||
{
|
||||
Volume = 6,
|
||||
Temperature = Atmospherics.NormalBodyTemperature
|
||||
};
|
||||
|
||||
[DataField("gaspPopupCooldown")]
|
||||
public TimeSpan GaspPopupCooldown { get; private set; } = TimeSpan.FromSeconds(8);
|
||||
|
||||
[ViewVariables]
|
||||
public LungStatus Status { get; set; }
|
||||
|
||||
[DataField("cycleDelay")]
|
||||
public float CycleDelay { get; set; } = 2;
|
||||
}
|
||||
|
||||
public enum LungStatus
|
||||
{
|
||||
None = 0,
|
||||
Inhaling,
|
||||
Exhaling
|
||||
}
|
||||
@@ -40,8 +40,15 @@ namespace Content.Server.Body.Systems
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<T> GetComponentsOnMechanisms<T>(EntityUid uid,
|
||||
SharedBodyComponent? body) where T : Component
|
||||
/// <summary>
|
||||
/// Returns a list of ValueTuples of <see cref="T"/> and SharedMechanismComponent on each mechanism
|
||||
/// in the given body.
|
||||
/// </summary>
|
||||
/// <param name="uid">The entity to check for the component on.</param>
|
||||
/// <param name="body">The body to check for mechanisms on.</param>
|
||||
/// <typeparam name="T">The component to check for.</typeparam>
|
||||
public IEnumerable<(T Comp, SharedMechanismComponent Mech)> GetComponentsOnMechanisms<T>(EntityUid uid,
|
||||
SharedBodyComponent? body=null) where T : Component
|
||||
{
|
||||
if (!Resolve(uid, ref body))
|
||||
yield break;
|
||||
@@ -50,13 +57,22 @@ namespace Content.Server.Body.Systems
|
||||
foreach (var mechanism in part.Mechanisms)
|
||||
{
|
||||
if (EntityManager.TryGetComponent<T>(mechanism.OwnerUid, out var comp))
|
||||
yield return comp;
|
||||
yield return (comp, mechanism);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get a list of ValueTuples of <see cref="T"/> and SharedMechanismComponent on each mechanism
|
||||
/// in the given body.
|
||||
/// </summary>
|
||||
/// <param name="uid">The entity to check for the component on.</param>
|
||||
/// <param name="comps">The list of components.</param>
|
||||
/// <param name="body">The body to check for mechanisms on.</param>
|
||||
/// <typeparam name="T">The component to check for.</typeparam>
|
||||
/// <returns>Whether any were found.</returns>
|
||||
public bool TryGetComponentsOnMechanisms<T>(EntityUid uid,
|
||||
[NotNullWhen(true)] out IEnumerable<T>? comps,
|
||||
SharedBodyComponent? body) where T: Component
|
||||
[NotNullWhen(true)] out IEnumerable<(T Comp, SharedMechanismComponent Mech)>? comps,
|
||||
SharedBodyComponent? body=null) where T: Component
|
||||
{
|
||||
if (!Resolve(uid, ref body))
|
||||
{
|
||||
|
||||
200
Content.Server/Body/Systems/LungSystem.cs
Normal file
200
Content.Server/Body/Systems/LungSystem.cs
Normal file
@@ -0,0 +1,200 @@
|
||||
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<LungComponent, AddedToBodyEvent>(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, float frameTime,
|
||||
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 => frameTime,
|
||||
LungStatus.Exhaling => -frameTime,
|
||||
_ => 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find an air mixture to inhale from, then inhales from it.
|
||||
/// </summary>
|
||||
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<TransformComponent>(uid).Coordinates, true) is not { } tileAir)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TakeGasFrom(uid, frameTime, tileAir, lung);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inhales directly from a given mixture.
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find a gas mixture to exhale to, then pushes gas to it.
|
||||
/// </summary>
|
||||
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<TransformComponent>(uid).Coordinates, true) is not { } tileAir)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PushGasTo(uid, tileAir, lung, mech);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes gas from the lungs to a gas mixture.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ using System.Linq;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Alert;
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.Body.Behavior;
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Atmos;
|
||||
@@ -23,6 +22,8 @@ namespace Content.Server.Body.Systems
|
||||
{
|
||||
[Dependency] private readonly DamageableSystem _damageableSys = default!;
|
||||
[Dependency] private readonly AdminLogSystem _logSys = default!;
|
||||
[Dependency] private readonly BodySystem _bodySystem = default!;
|
||||
[Dependency] private readonly LungSystem _lungSystem = default!;
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
@@ -136,10 +137,16 @@ namespace Content.Server.Body.Systems
|
||||
if (!Resolve(uid, ref bloodstream, ref body, false))
|
||||
return;
|
||||
|
||||
var lungs = body.GetMechanismBehaviors<LungBehavior>().ToArray();
|
||||
var lungs = _bodySystem.GetComponentsOnMechanisms<LungComponent>(uid, body).ToArray();
|
||||
|
||||
var needs = NeedsAndDeficit(respirator, frameTime);
|
||||
var used = 0f;
|
||||
|
||||
foreach (var (lung, mech) in lungs)
|
||||
{
|
||||
_lungSystem.UpdateLung(lung.OwnerUid, frameTime, lung, mech);
|
||||
}
|
||||
|
||||
foreach (var (gas, amountNeeded) in needs)
|
||||
{
|
||||
var bloodstreamAmount = bloodstream.Air.GetMoles(gas);
|
||||
@@ -150,9 +157,9 @@ namespace Content.Server.Body.Systems
|
||||
if (!EntityManager.GetComponent<MobStateComponent>(uid).IsCritical())
|
||||
{
|
||||
// Panic inhale
|
||||
foreach (var lung in lungs)
|
||||
foreach (var (lung, mech) in lungs)
|
||||
{
|
||||
lung.Gasp();
|
||||
_lungSystem.Gasp(lung.OwnerUid, lung, mech);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user