Remove LungBehavior and replace with LungComponent/System (#5630)
This commit is contained in:
@@ -116,6 +116,7 @@ namespace Content.Client.Entry
|
|||||||
"PowerSupplier",
|
"PowerSupplier",
|
||||||
"PowerConsumer",
|
"PowerConsumer",
|
||||||
"Battery",
|
"Battery",
|
||||||
|
"Lung",
|
||||||
"BatteryDischarger",
|
"BatteryDischarger",
|
||||||
"Apc",
|
"Apc",
|
||||||
"PowerProvider",
|
"PowerProvider",
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Content.Server.Atmos;
|
using Content.Server.Atmos;
|
||||||
using Content.Server.Body.Behavior;
|
|
||||||
using Content.Server.Body.Components;
|
using Content.Server.Body.Components;
|
||||||
|
using Content.Server.Body.Systems;
|
||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
using Content.Shared.Body.Components;
|
using Content.Shared.Body.Components;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@@ -17,7 +16,7 @@ using Robust.Shared.Maths;
|
|||||||
namespace Content.IntegrationTests.Tests.Body
|
namespace Content.IntegrationTests.Tests.Body
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
[TestOf(typeof(LungBehavior))]
|
[TestOf(typeof(LungSystem))]
|
||||||
public class LungTest : ContentIntegrationTest
|
public class LungTest : ContentIntegrationTest
|
||||||
{
|
{
|
||||||
private const string Prototypes = @"
|
private const string Prototypes = @"
|
||||||
@@ -64,8 +63,12 @@ namespace Content.IntegrationTests.Tests.Body
|
|||||||
|
|
||||||
var human = entityManager.SpawnEntity("HumanBodyAndBloodstreamDummy", new MapCoordinates(Vector2.Zero, mapId));
|
var human = entityManager.SpawnEntity("HumanBodyAndBloodstreamDummy", new MapCoordinates(Vector2.Zero, mapId));
|
||||||
|
|
||||||
|
var bodySys = EntitySystem.Get<BodySystem>();
|
||||||
|
var lungSys = EntitySystem.Get<LungSystem>();
|
||||||
|
|
||||||
Assert.That(human.TryGetComponent(out SharedBodyComponent body));
|
Assert.That(human.TryGetComponent(out SharedBodyComponent body));
|
||||||
Assert.That(body.TryGetMechanismBehaviors(out List<LungBehavior> lungs));
|
|
||||||
|
var lungs = bodySys.GetComponentsOnMechanisms<LungComponent>(human.Uid, body).ToArray();
|
||||||
Assert.That(lungs.Count, Is.EqualTo(1));
|
Assert.That(lungs.Count, Is.EqualTo(1));
|
||||||
Assert.That(human.TryGetComponent(out BloodstreamComponent bloodstream));
|
Assert.That(human.TryGetComponent(out BloodstreamComponent bloodstream));
|
||||||
|
|
||||||
@@ -78,8 +81,8 @@ namespace Content.IntegrationTests.Tests.Body
|
|||||||
gas.AdjustMoles(Gas.Oxygen, originalOxygen);
|
gas.AdjustMoles(Gas.Oxygen, originalOxygen);
|
||||||
gas.AdjustMoles(Gas.Nitrogen, originalNitrogen);
|
gas.AdjustMoles(Gas.Nitrogen, originalNitrogen);
|
||||||
|
|
||||||
var lung = lungs[0];
|
var (lung, _) = lungs[0];
|
||||||
lung.Inhale(1, gas);
|
lungSys.TakeGasFrom(lung.OwnerUid, 1, gas, lung);
|
||||||
|
|
||||||
var lungOxygen = originalOxygen * breathedPercentage;
|
var lungOxygen = originalOxygen * breathedPercentage;
|
||||||
var lungNitrogen = originalNitrogen * breathedPercentage;
|
var lungNitrogen = originalNitrogen * breathedPercentage;
|
||||||
@@ -100,7 +103,7 @@ namespace Content.IntegrationTests.Tests.Body
|
|||||||
Assert.Zero(lungOxygenBeforeExhale);
|
Assert.Zero(lungOxygenBeforeExhale);
|
||||||
Assert.Zero(lungNitrogenBeforeExhale);
|
Assert.Zero(lungNitrogenBeforeExhale);
|
||||||
|
|
||||||
lung.Exhale(1, gas);
|
lungSys.PushGasTo(lung.OwnerUid, gas, lung);
|
||||||
|
|
||||||
var lungOxygenAfterExhale = lung.Air.GetMoles(Gas.Oxygen);
|
var lungOxygenAfterExhale = lung.Air.GetMoles(Gas.Oxygen);
|
||||||
var exhaledOxygen = Math.Abs(lungOxygenBeforeExhale - lungOxygenAfterExhale);
|
var exhaledOxygen = Math.Abs(lungOxygenBeforeExhale - lungOxygenAfterExhale);
|
||||||
@@ -168,8 +171,7 @@ namespace Content.IntegrationTests.Tests.Body
|
|||||||
var coordinates = new EntityCoordinates(grid.GridEntityId, center);
|
var coordinates = new EntityCoordinates(grid.GridEntityId, center);
|
||||||
human = entityManager.SpawnEntity("HumanBodyAndBloodstreamDummy", coordinates);
|
human = entityManager.SpawnEntity("HumanBodyAndBloodstreamDummy", coordinates);
|
||||||
|
|
||||||
Assert.True(human.TryGetComponent(out SharedBodyComponent body));
|
Assert.True(human.HasComponent<SharedBodyComponent>());
|
||||||
Assert.True(body.HasMechanismBehavior<LungBehavior>());
|
|
||||||
Assert.True(human.TryGetComponent(out respirator));
|
Assert.True(human.TryGetComponent(out respirator));
|
||||||
Assert.False(respirator.Suffocating);
|
Assert.False(respirator.Suffocating);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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,
|
/// <summary>
|
||||||
SharedBodyComponent? body) where T : Component
|
/// 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))
|
if (!Resolve(uid, ref body))
|
||||||
yield break;
|
yield break;
|
||||||
@@ -50,13 +57,22 @@ namespace Content.Server.Body.Systems
|
|||||||
foreach (var mechanism in part.Mechanisms)
|
foreach (var mechanism in part.Mechanisms)
|
||||||
{
|
{
|
||||||
if (EntityManager.TryGetComponent<T>(mechanism.OwnerUid, out var comp))
|
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,
|
public bool TryGetComponentsOnMechanisms<T>(EntityUid uid,
|
||||||
[NotNullWhen(true)] out IEnumerable<T>? comps,
|
[NotNullWhen(true)] out IEnumerable<(T Comp, SharedMechanismComponent Mech)>? comps,
|
||||||
SharedBodyComponent? body) where T: Component
|
SharedBodyComponent? body=null) where T: Component
|
||||||
{
|
{
|
||||||
if (!Resolve(uid, ref body))
|
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.Administration.Logs;
|
||||||
using Content.Server.Alert;
|
using Content.Server.Alert;
|
||||||
using Content.Server.Atmos;
|
using Content.Server.Atmos;
|
||||||
using Content.Server.Body.Behavior;
|
|
||||||
using Content.Server.Body.Components;
|
using Content.Server.Body.Components;
|
||||||
using Content.Shared.Alert;
|
using Content.Shared.Alert;
|
||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
@@ -23,6 +22,8 @@ namespace Content.Server.Body.Systems
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly DamageableSystem _damageableSys = default!;
|
[Dependency] private readonly DamageableSystem _damageableSys = default!;
|
||||||
[Dependency] private readonly AdminLogSystem _logSys = 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)
|
public override void Update(float frameTime)
|
||||||
{
|
{
|
||||||
@@ -136,10 +137,16 @@ namespace Content.Server.Body.Systems
|
|||||||
if (!Resolve(uid, ref bloodstream, ref body, false))
|
if (!Resolve(uid, ref bloodstream, ref body, false))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var lungs = body.GetMechanismBehaviors<LungBehavior>().ToArray();
|
var lungs = _bodySystem.GetComponentsOnMechanisms<LungComponent>(uid, body).ToArray();
|
||||||
|
|
||||||
var needs = NeedsAndDeficit(respirator, frameTime);
|
var needs = NeedsAndDeficit(respirator, frameTime);
|
||||||
var used = 0f;
|
var used = 0f;
|
||||||
|
|
||||||
|
foreach (var (lung, mech) in lungs)
|
||||||
|
{
|
||||||
|
_lungSystem.UpdateLung(lung.OwnerUid, frameTime, lung, mech);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var (gas, amountNeeded) in needs)
|
foreach (var (gas, amountNeeded) in needs)
|
||||||
{
|
{
|
||||||
var bloodstreamAmount = bloodstream.Air.GetMoles(gas);
|
var bloodstreamAmount = bloodstream.Air.GetMoles(gas);
|
||||||
@@ -150,9 +157,9 @@ namespace Content.Server.Body.Systems
|
|||||||
if (!EntityManager.GetComponent<MobStateComponent>(uid).IsCritical())
|
if (!EntityManager.GetComponent<MobStateComponent>(uid).IsCritical())
|
||||||
{
|
{
|
||||||
// Panic inhale
|
// Panic inhale
|
||||||
foreach (var lung in lungs)
|
foreach (var (lung, mech) in lungs)
|
||||||
{
|
{
|
||||||
lung.Gasp();
|
_lungSystem.Gasp(lung.OwnerUid, lung, mech);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ using Robust.Shared.IoC;
|
|||||||
using Robust.Shared.Localization;
|
using Robust.Shared.Localization;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Server.Nutrition.EntitySystems
|
namespace Content.Server.Nutrition.EntitySystems
|
||||||
{
|
{
|
||||||
@@ -263,8 +264,8 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
|
|
||||||
var transferAmount = FixedPoint2.Min(drink.TransferAmount, drinkSolution.DrainAvailable);
|
var transferAmount = FixedPoint2.Min(drink.TransferAmount, drinkSolution.DrainAvailable);
|
||||||
var drain = _solutionContainerSystem.Drain(uid, drinkSolution, transferAmount);
|
var drain = _solutionContainerSystem.Drain(uid, drinkSolution, transferAmount);
|
||||||
var firstStomach = stomachs.FirstOrDefault(
|
var firstStomach = stomachs.FirstOrNull(
|
||||||
stomach => _stomachSystem.CanTransferSolution(stomach.OwnerUid, drain));
|
stomach => _stomachSystem.CanTransferSolution(stomach.Comp.OwnerUid, drain));
|
||||||
|
|
||||||
// All stomach are full or can't handle whatever solution we have.
|
// All stomach are full or can't handle whatever solution we have.
|
||||||
if (firstStomach == null)
|
if (firstStomach == null)
|
||||||
@@ -289,7 +290,7 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
Filter.Pvs(userUid));
|
Filter.Pvs(userUid));
|
||||||
|
|
||||||
drain.DoEntityReaction(userUid, ReactionMethod.Ingestion);
|
drain.DoEntityReaction(userUid, ReactionMethod.Ingestion);
|
||||||
_stomachSystem.TryTransferSolution(firstStomach.OwnerUid, drain, firstStomach);
|
_stomachSystem.TryTransferSolution(firstStomach.Value.Comp.OwnerUid, drain, firstStomach.Value.Comp);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -371,8 +372,8 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var firstStomach = stomachs.FirstOrDefault(
|
var firstStomach = stomachs.FirstOrNull(
|
||||||
stomach => _stomachSystem.CanTransferSolution(stomach.OwnerUid, drained));
|
stomach => _stomachSystem.CanTransferSolution(stomach.Comp.OwnerUid, drained));
|
||||||
|
|
||||||
// All stomach are full or can't handle whatever solution we have.
|
// All stomach are full or can't handle whatever solution we have.
|
||||||
if (firstStomach == null)
|
if (firstStomach == null)
|
||||||
@@ -399,7 +400,7 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
SoundSystem.Play(Filter.Pvs(uid), args.Drink.UseSound.GetSound(), uid, AudioParams.Default.WithVolume(-2f));
|
SoundSystem.Play(Filter.Pvs(uid), args.Drink.UseSound.GetSound(), uid, AudioParams.Default.WithVolume(-2f));
|
||||||
|
|
||||||
drained.DoEntityReaction(uid, ReactionMethod.Ingestion);
|
drained.DoEntityReaction(uid, ReactionMethod.Ingestion);
|
||||||
_stomachSystem.TryTransferSolution(firstStomach.OwnerUid, drained, firstStomach);
|
_stomachSystem.TryTransferSolution(firstStomach.Value.Comp.OwnerUid, drained, firstStomach.Value.Comp);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnForceDrinkCancelled(ForceDrinkCancelledEvent args)
|
private void OnForceDrinkCancelled(ForceDrinkCancelledEvent args)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ using Robust.Shared.Localization;
|
|||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Server.Nutrition.EntitySystems
|
namespace Content.Server.Nutrition.EntitySystems
|
||||||
{
|
{
|
||||||
@@ -144,7 +145,8 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
|
|
||||||
var transferAmount = component.TransferAmount != null ? FixedPoint2.Min((FixedPoint2) component.TransferAmount, solution.CurrentVolume) : solution.CurrentVolume;
|
var transferAmount = component.TransferAmount != null ? FixedPoint2.Min((FixedPoint2) component.TransferAmount, solution.CurrentVolume) : solution.CurrentVolume;
|
||||||
var split = _solutionContainerSystem.SplitSolution(uid, solution, transferAmount);
|
var split = _solutionContainerSystem.SplitSolution(uid, solution, transferAmount);
|
||||||
var firstStomach = stomachs.FirstOrDefault(stomach => _stomachSystem.CanTransferSolution(stomach.OwnerUid, split));
|
var firstStomach = stomachs.FirstOrNull(
|
||||||
|
stomach => _stomachSystem.CanTransferSolution(stomach.Comp.OwnerUid, split));
|
||||||
|
|
||||||
if (firstStomach == null)
|
if (firstStomach == null)
|
||||||
{
|
{
|
||||||
@@ -155,7 +157,7 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
|
|
||||||
// TODO: Account for partial transfer.
|
// TODO: Account for partial transfer.
|
||||||
split.DoEntityReaction(userUid, ReactionMethod.Ingestion);
|
split.DoEntityReaction(userUid, ReactionMethod.Ingestion);
|
||||||
_stomachSystem.TryTransferSolution(firstStomach.OwnerUid, split, firstStomach);
|
_stomachSystem.TryTransferSolution(firstStomach.Value.Comp.OwnerUid, split, firstStomach.Value.Comp);
|
||||||
|
|
||||||
SoundSystem.Play(Filter.Pvs(userUid), component.UseSound.GetSound(), userUid, AudioParams.Default.WithVolume(-1f));
|
SoundSystem.Play(Filter.Pvs(userUid), component.UseSound.GetSound(), userUid, AudioParams.Default.WithVolume(-1f));
|
||||||
_popupSystem.PopupEntity(Loc.GetString(component.EatMessage, ("food", component.Owner)), userUid, Filter.Entities(userUid));
|
_popupSystem.PopupEntity(Loc.GetString(component.EatMessage, ("food", component.Owner)), userUid, Filter.Entities(userUid));
|
||||||
@@ -292,7 +294,8 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
: args.FoodSolution.CurrentVolume;
|
: args.FoodSolution.CurrentVolume;
|
||||||
|
|
||||||
var split = _solutionContainerSystem.SplitSolution(args.Food.OwnerUid, args.FoodSolution, transferAmount);
|
var split = _solutionContainerSystem.SplitSolution(args.Food.OwnerUid, args.FoodSolution, transferAmount);
|
||||||
var firstStomach = stomachs.FirstOrDefault(stomach => _stomachSystem.CanTransferSolution(stomach.OwnerUid, split));
|
var firstStomach = stomachs.FirstOrNull(
|
||||||
|
stomach => _stomachSystem.CanTransferSolution(stomach.Comp.OwnerUid, split));
|
||||||
|
|
||||||
if (firstStomach == null)
|
if (firstStomach == null)
|
||||||
{
|
{
|
||||||
@@ -302,7 +305,7 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
}
|
}
|
||||||
|
|
||||||
split.DoEntityReaction(uid, ReactionMethod.Ingestion);
|
split.DoEntityReaction(uid, ReactionMethod.Ingestion);
|
||||||
_stomachSystem.TryTransferSolution(firstStomach.OwnerUid, split, firstStomach);
|
_stomachSystem.TryTransferSolution(firstStomach.Value.Comp.OwnerUid, split, firstStomach.Value.Comp);
|
||||||
|
|
||||||
EntityManager.TryGetComponent(uid, out MetaDataComponent? targetMeta);
|
EntityManager.TryGetComponent(uid, out MetaDataComponent? targetMeta);
|
||||||
var targetName = targetMeta?.EntityName ?? string.Empty;
|
var targetName = targetMeta?.EntityName ?? string.Empty;
|
||||||
@@ -350,7 +353,9 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
if (food.UsesRemaining <= 0)
|
if (food.UsesRemaining <= 0)
|
||||||
DeleteAndSpawnTrash(food);
|
DeleteAndSpawnTrash(food);
|
||||||
|
|
||||||
var firstStomach = stomachs.FirstOrDefault(stomach => _stomachSystem.CanTransferSolution(stomach.OwnerUid, foodSolution));
|
var firstStomach = stomachs.FirstOrNull(
|
||||||
|
stomach => _stomachSystem.CanTransferSolution(stomach.Comp.OwnerUid, foodSolution));
|
||||||
|
|
||||||
if (firstStomach == null)
|
if (firstStomach == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -365,7 +370,7 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
_popupSystem.PopupEntity(Loc.GetString(food.EatMessage), target, Filter.Entities(target));
|
_popupSystem.PopupEntity(Loc.GetString(food.EatMessage), target, Filter.Entities(target));
|
||||||
|
|
||||||
foodSolution.DoEntityReaction(uid, ReactionMethod.Ingestion);
|
foodSolution.DoEntityReaction(uid, ReactionMethod.Ingestion);
|
||||||
_stomachSystem.TryTransferSolution(firstStomach.OwnerUid, foodSolution, firstStomach);
|
_stomachSystem.TryTransferSolution(firstStomach.Value.Comp.OwnerUid, foodSolution, firstStomach.Value.Comp);
|
||||||
SoundSystem.Play(Filter.Pvs(target), food.UseSound.GetSound(), target, AudioParams.Default.WithVolume(-1f));
|
SoundSystem.Play(Filter.Pvs(target), food.UseSound.GetSound(), target, AudioParams.Default.WithVolume(-1f));
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(food.TrashPrototype))
|
if (string.IsNullOrEmpty(food.TrashPrototype))
|
||||||
|
|||||||
@@ -99,8 +99,7 @@
|
|||||||
- type: Mechanism
|
- type: Mechanism
|
||||||
size: 1
|
size: 1
|
||||||
compatibility: Biological
|
compatibility: Biological
|
||||||
behaviors:
|
- type: Lung
|
||||||
- !type:LungBehavior {}
|
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: OrganHumanHeart
|
id: OrganHumanHeart
|
||||||
|
|||||||
@@ -83,8 +83,7 @@
|
|||||||
- type: Mechanism
|
- type: Mechanism
|
||||||
size: 1
|
size: 1
|
||||||
compatibility: Biological
|
compatibility: Biological
|
||||||
behaviors:
|
- type: Lung
|
||||||
- !type:LungBehavior {}
|
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: OrganAnimalStomach
|
id: OrganAnimalStomach
|
||||||
|
|||||||
Reference in New Issue
Block a user