Remove LungBehavior and replace with LungComponent/System (#5630)

This commit is contained in:
mirrorcult
2021-11-30 18:25:02 -07:00
committed by GitHub
parent e3af2b5727
commit ccf01d7431
11 changed files with 311 additions and 242 deletions

View File

@@ -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
}
}

View 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
}

View File

@@ -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))
{

View 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);
}
}

View File

@@ -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);
}
}