Body code cleanup (#24946)
* Fix test * Kill float accumulators * Use entity proxy methods * DataField auto name generation where possible * Kill comp properties * Clean up server comps * Make events record structs * Clean up shared body code * Clean up server body code * Rename organ events to be same names as in med refactor
This commit is contained in:
@@ -9,7 +9,6 @@ using Robust.Shared.Configuration;
|
|||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Map.Components;
|
using Robust.Shared.Map.Components;
|
||||||
using Robust.Shared.Maths;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
@@ -61,12 +60,11 @@ namespace Content.IntegrationTests.Tests.Body
|
|||||||
var mapManager = server.ResolveDependency<IMapManager>();
|
var mapManager = server.ResolveDependency<IMapManager>();
|
||||||
var entityManager = server.ResolveDependency<IEntityManager>();
|
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||||
var mapLoader = entityManager.System<MapLoaderSystem>();
|
var mapLoader = entityManager.System<MapLoaderSystem>();
|
||||||
RespiratorSystem respSys = default;
|
|
||||||
MetabolizerSystem metaSys = default;
|
|
||||||
|
|
||||||
MapId mapId;
|
MapId mapId;
|
||||||
EntityUid? grid = null;
|
EntityUid? grid = null;
|
||||||
BodyComponent body = default;
|
BodyComponent body = default;
|
||||||
|
RespiratorComponent resp = default;
|
||||||
EntityUid human = default;
|
EntityUid human = default;
|
||||||
GridAtmosphereComponent relevantAtmos = default;
|
GridAtmosphereComponent relevantAtmos = default;
|
||||||
var startingMoles = 0.0f;
|
var startingMoles = 0.0f;
|
||||||
@@ -99,17 +97,15 @@ namespace Content.IntegrationTests.Tests.Body
|
|||||||
|
|
||||||
await server.WaitAssertion(() =>
|
await server.WaitAssertion(() =>
|
||||||
{
|
{
|
||||||
var coords = new Vector2(0.5f, -1f);
|
var center = new Vector2(0.5f, 0.5f);
|
||||||
var coordinates = new EntityCoordinates(grid.Value, coords);
|
var coordinates = new EntityCoordinates(grid.Value, center);
|
||||||
human = entityManager.SpawnEntity("HumanLungDummy", coordinates);
|
human = entityManager.SpawnEntity("HumanLungDummy", coordinates);
|
||||||
respSys = entityManager.System<RespiratorSystem>();
|
|
||||||
metaSys = entityManager.System<MetabolizerSystem>();
|
|
||||||
relevantAtmos = entityManager.GetComponent<GridAtmosphereComponent>(grid.Value);
|
relevantAtmos = entityManager.GetComponent<GridAtmosphereComponent>(grid.Value);
|
||||||
startingMoles = GetMapMoles();
|
startingMoles = 100f; // Hardcoded because GetMapMoles returns 900 here for some reason.
|
||||||
|
|
||||||
#pragma warning disable NUnit2045
|
#pragma warning disable NUnit2045
|
||||||
Assert.That(entityManager.TryGetComponent(human, out body), Is.True);
|
Assert.That(entityManager.TryGetComponent(human, out body), Is.True);
|
||||||
Assert.That(entityManager.HasComponent<RespiratorComponent>(human), Is.True);
|
Assert.That(entityManager.TryGetComponent(human, out resp), Is.True);
|
||||||
#pragma warning restore NUnit2045
|
#pragma warning restore NUnit2045
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -118,18 +114,19 @@ namespace Content.IntegrationTests.Tests.Body
|
|||||||
var inhaleCycles = 100;
|
var inhaleCycles = 100;
|
||||||
for (var i = 0; i < inhaleCycles; i++)
|
for (var i = 0; i < inhaleCycles; i++)
|
||||||
{
|
{
|
||||||
await server.WaitAssertion(() =>
|
// Breathe in
|
||||||
{
|
await PoolManager.WaitUntil(server, () => resp.Status == RespiratorStatus.Exhaling);
|
||||||
// inhale
|
Assert.That(
|
||||||
respSys.Update(2.0f);
|
GetMapMoles(), Is.LessThan(startingMoles),
|
||||||
Assert.That(GetMapMoles(), Is.LessThan(startingMoles));
|
"Did not inhale in any gas"
|
||||||
|
);
|
||||||
|
|
||||||
// metabolize + exhale
|
// Breathe out
|
||||||
metaSys.Update(1.0f);
|
await PoolManager.WaitUntil(server, () => resp.Status == RespiratorStatus.Inhaling);
|
||||||
metaSys.Update(1.0f);
|
Assert.That(
|
||||||
respSys.Update(2.0f);
|
GetMapMoles(), Is.EqualTo(startingMoles).Within(0.0002),
|
||||||
Assert.That(GetMapMoles(), Is.EqualTo(startingMoles).Within(0.0002));
|
"Did not exhale as much gas as was inhaled"
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await pair.CleanReturnAsync();
|
await pair.CleanReturnAsync();
|
||||||
|
|||||||
@@ -93,9 +93,8 @@ namespace Content.Server.Bed
|
|||||||
if (!this.IsPowered(uid, EntityManager))
|
if (!this.IsPowered(uid, EntityManager))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var metabolicEvent = new ApplyMetabolicMultiplierEvent
|
var metabolicEvent = new ApplyMetabolicMultiplierEvent(args.BuckledEntity, component.Multiplier, args.Buckling);
|
||||||
{Uid = args.BuckledEntity, Multiplier = component.Multiplier, Apply = args.Buckling};
|
RaiseLocalEvent(args.BuckledEntity, ref metabolicEvent);
|
||||||
RaiseLocalEvent(args.BuckledEntity, metabolicEvent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPowerChanged(EntityUid uid, StasisBedComponent component, ref PowerChangedEvent args)
|
private void OnPowerChanged(EntityUid uid, StasisBedComponent component, ref PowerChangedEvent args)
|
||||||
@@ -121,9 +120,8 @@ namespace Content.Server.Bed
|
|||||||
|
|
||||||
foreach (var buckledEntity in strap.BuckledEntities)
|
foreach (var buckledEntity in strap.BuckledEntities)
|
||||||
{
|
{
|
||||||
var metabolicEvent = new ApplyMetabolicMultiplierEvent
|
var metabolicEvent = new ApplyMetabolicMultiplierEvent(buckledEntity, component.Multiplier, shouldApply);
|
||||||
{Uid = buckledEntity, Multiplier = component.Multiplier, Apply = shouldApply};
|
RaiseLocalEvent(buckledEntity, ref metabolicEvent);
|
||||||
RaiseLocalEvent(buckledEntity, metabolicEvent);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ namespace Content.Server.Body.Commands
|
|||||||
switch (args.Length)
|
switch (args.Length)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
{
|
|
||||||
if (player == null)
|
if (player == null)
|
||||||
{
|
{
|
||||||
shell.WriteLine("Only a player can run this command without arguments.");
|
shell.WriteLine("Only a player can run this command without arguments.");
|
||||||
@@ -50,7 +49,6 @@ namespace Content.Server.Body.Commands
|
|||||||
entity = player.AttachedEntity.Value;
|
entity = player.AttachedEntity.Value;
|
||||||
hand = _entManager.SpawnEntity(DefaultHandPrototype, _entManager.GetComponent<TransformComponent>(entity).Coordinates);
|
hand = _entManager.SpawnEntity(DefaultHandPrototype, _entManager.GetComponent<TransformComponent>(entity).Coordinates);
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
case 1:
|
case 1:
|
||||||
{
|
{
|
||||||
if (NetEntity.TryParse(args[0], out var uidNet) && _entManager.TryGetEntity(uidNet, out var uid))
|
if (NetEntity.TryParse(args[0], out var uidNet) && _entManager.TryGetEntity(uidNet, out var uid))
|
||||||
@@ -111,11 +109,9 @@ namespace Content.Server.Body.Commands
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
{
|
|
||||||
shell.WriteLine(Help);
|
shell.WriteLine(Help);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!_entManager.TryGetComponent(entity, out BodyComponent? body) || body.RootContainer.ContainedEntity == null)
|
if (!_entManager.TryGetComponent(entity, out BodyComponent? body) || body.RootContainer.ContainedEntity == null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
namespace Content.Server.Body.Components;
|
namespace Content.Server.Body.Components;
|
||||||
|
|
||||||
public sealed class BeingGibbedEvent : EntityEventArgs
|
/// <summary>
|
||||||
{
|
/// Raised when a body gets gibbed, before it is deleted.
|
||||||
public readonly HashSet<EntityUid> GibbedParts;
|
/// </summary>
|
||||||
|
[ByRefEvent]
|
||||||
public BeingGibbedEvent(HashSet<EntityUid> gibbedParts)
|
public readonly record struct BeingGibbedEvent(HashSet<EntityUid> GibbedParts);
|
||||||
{
|
|
||||||
GibbedParts = gibbedParts;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
using Content.Server.Body.Systems;
|
using Content.Server.Body.Systems;
|
||||||
using Content.Server.Chemistry.EntitySystems;
|
using Content.Server.Chemistry.EntitySystems;
|
||||||
using Content.Shared.Chemistry.Components;
|
using Content.Shared.Chemistry.Components;
|
||||||
|
using Content.Shared.Chemistry.Reagent;
|
||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
using Content.Shared.Damage.Prototypes;
|
using Content.Shared.Damage.Prototypes;
|
||||||
using Content.Shared.FixedPoint;
|
using Content.Shared.FixedPoint;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||||
|
|
||||||
namespace Content.Server.Body.Components
|
namespace Content.Server.Body.Components
|
||||||
{
|
{
|
||||||
@@ -16,7 +18,17 @@ namespace Content.Server.Body.Components
|
|||||||
public static string DefaultBloodSolutionName = "bloodstream";
|
public static string DefaultBloodSolutionName = "bloodstream";
|
||||||
public static string DefaultBloodTemporarySolutionName = "bloodstreamTemporary";
|
public static string DefaultBloodTemporarySolutionName = "bloodstreamTemporary";
|
||||||
|
|
||||||
public float AccumulatedFrametime = 0.0f;
|
/// <summary>
|
||||||
|
/// The next time that blood level will be updated and bloodloss damage dealt.
|
||||||
|
/// </summary>
|
||||||
|
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||||
|
public TimeSpan NextUpdate;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The interval at which this component updates.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public TimeSpan UpdateInterval = TimeSpan.FromSeconds(3);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// How much is this entity currently bleeding?
|
/// How much is this entity currently bleeding?
|
||||||
@@ -32,7 +44,7 @@ namespace Content.Server.Body.Components
|
|||||||
public float BleedAmount;
|
public float BleedAmount;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// How much should bleeding should be reduced every update interval?
|
/// How much should bleeding be reduced every update interval?
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
public float BleedReductionAmount = 0.33f;
|
public float BleedReductionAmount = 0.33f;
|
||||||
@@ -63,18 +75,12 @@ namespace Content.Server.Body.Components
|
|||||||
[DataField(required: true)]
|
[DataField(required: true)]
|
||||||
public DamageSpecifier BloodlossHealDamage = new();
|
public DamageSpecifier BloodlossHealDamage = new();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// How frequently should this bloodstream update, in seconds?
|
|
||||||
/// </summary>
|
|
||||||
[DataField]
|
|
||||||
public float UpdateInterval = 3.0f;
|
|
||||||
|
|
||||||
// TODO shouldn't be hardcoded, should just use some organ simulation like bone marrow or smth.
|
// TODO shouldn't be hardcoded, should just use some organ simulation like bone marrow or smth.
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// How much reagent of blood should be restored each update interval?
|
/// How much reagent of blood should be restored each update interval?
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
public float BloodRefreshAmount = 1.0f;
|
public FixedPoint2 BloodRefreshAmount = 1.0f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// How much blood needs to be in the temporary solution in order to create a puddle?
|
/// How much blood needs to be in the temporary solution in order to create a puddle?
|
||||||
@@ -89,8 +95,8 @@ namespace Content.Server.Body.Components
|
|||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// For example, piercing damage is increased while poison damage is nullified entirely.
|
/// For example, piercing damage is increased while poison damage is nullified entirely.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[DataField(customTypeSerializer:typeof(PrototypeIdSerializer<DamageModifierSetPrototype>))]
|
[DataField]
|
||||||
public string DamageBleedModifiers = "BloodlossHuman";
|
public ProtoId<DamageModifierSetPrototype> DamageBleedModifiers = "BloodlossHuman";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The sound to be played when a weapon instantly deals blood loss damage.
|
/// The sound to be played when a weapon instantly deals blood loss damage.
|
||||||
@@ -126,7 +132,7 @@ namespace Content.Server.Body.Components
|
|||||||
/// Slime-people might use slime as their blood or something like that.
|
/// Slime-people might use slime as their blood or something like that.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[DataField]
|
[DataField]
|
||||||
public string BloodReagent = "Blood";
|
public ProtoId<ReagentPrototype> BloodReagent = "Blood";
|
||||||
|
|
||||||
/// <summary>Name/Key that <see cref="BloodSolution"/> is indexed by.</summary>
|
/// <summary>Name/Key that <see cref="BloodSolution"/> is indexed by.</summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
@@ -164,6 +170,6 @@ namespace Content.Server.Body.Components
|
|||||||
/// Variable that stores the amount of status time added by having a low blood level.
|
/// Variable that stores the amount of status time added by having a low blood level.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
public float StatusTime;
|
public TimeSpan StatusTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System.Threading;
|
|
||||||
namespace Content.Server.Body.Components
|
namespace Content.Server.Body.Components
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -7,14 +6,17 @@ namespace Content.Server.Body.Components
|
|||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public sealed partial class InternalsComponent : Component
|
public sealed partial class InternalsComponent : Component
|
||||||
{
|
{
|
||||||
[ViewVariables] public EntityUid? GasTankEntity { get; set; }
|
[ViewVariables]
|
||||||
[ViewVariables] public EntityUid? BreathToolEntity { get; set; }
|
public EntityUid? GasTankEntity;
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
public EntityUid? BreathToolEntity;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Toggle Internals delay (seconds) when the target is not you.
|
/// Toggle Internals delay when the target is not you.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
[DataField("delay")]
|
[DataField]
|
||||||
public float Delay = 3;
|
public TimeSpan Delay = TimeSpan.FromSeconds(3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Content.Server.Atmos;
|
using Content.Server.Atmos;
|
||||||
using Content.Server.Body.Systems;
|
using Content.Server.Body.Systems;
|
||||||
using Content.Shared.Alert;
|
using Content.Shared.Alert;
|
||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
@@ -11,7 +11,7 @@ public sealed partial class LungComponent : Component
|
|||||||
{
|
{
|
||||||
[DataField]
|
[DataField]
|
||||||
[Access(typeof(LungSystem), Other = AccessPermissions.ReadExecute)] // FIXME Friends
|
[Access(typeof(LungSystem), Other = AccessPermissions.ReadExecute)] // FIXME Friends
|
||||||
public GasMixture Air { get; set; } = new()
|
public GasMixture Air = new()
|
||||||
{
|
{
|
||||||
Volume = 6,
|
Volume = 6,
|
||||||
Temperature = Atmospherics.NormalBodyTemperature
|
Temperature = Atmospherics.NormalBodyTemperature
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
using Content.Server.Body.Systems;
|
using Content.Server.Body.Systems;
|
||||||
using Content.Shared.Body.Prototypes;
|
using Content.Shared.Body.Prototypes;
|
||||||
using Content.Shared.FixedPoint;
|
using Content.Shared.FixedPoint;
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||||
|
|
||||||
namespace Content.Server.Body.Components
|
namespace Content.Server.Body.Components
|
||||||
{
|
{
|
||||||
@@ -12,20 +12,24 @@ namespace Content.Server.Body.Components
|
|||||||
[RegisterComponent, Access(typeof(MetabolizerSystem))]
|
[RegisterComponent, Access(typeof(MetabolizerSystem))]
|
||||||
public sealed partial class MetabolizerComponent : Component
|
public sealed partial class MetabolizerComponent : Component
|
||||||
{
|
{
|
||||||
public float AccumulatedFrametime = 0.0f;
|
/// <summary>
|
||||||
|
/// The next time that reagents will be metabolized.
|
||||||
|
/// </summary>
|
||||||
|
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||||
|
public TimeSpan NextUpdate;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// How often to metabolize reagents, in seconds.
|
/// How often to metabolize reagents.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[DataField]
|
[DataField]
|
||||||
public float UpdateFrequency = 1.0f;
|
public TimeSpan UpdateInterval = TimeSpan.FromSeconds(1);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// From which solution will this metabolizer attempt to metabolize chemicals
|
/// From which solution will this metabolizer attempt to metabolize chemicals
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("solution")]
|
[DataField("solution")]
|
||||||
public string SolutionName { get; set; } = BloodstreamComponent.DefaultChemicalsSolutionName;
|
public string SolutionName = BloodstreamComponent.DefaultChemicalsSolutionName;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Does this component use a solution on it's parent entity (the body) or itself
|
/// Does this component use a solution on it's parent entity (the body) or itself
|
||||||
@@ -39,9 +43,9 @@ namespace Content.Server.Body.Components
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// List of metabolizer types that this organ is. ex. Human, Slime, Felinid, w/e.
|
/// List of metabolizer types that this organ is. ex. Human, Slime, Felinid, w/e.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField(customTypeSerializer:typeof(PrototypeIdHashSetSerializer<MetabolizerTypePrototype>))]
|
[DataField]
|
||||||
[Access(typeof(MetabolizerSystem), Other = AccessPermissions.ReadExecute)] // FIXME Friends
|
[Access(typeof(MetabolizerSystem), Other = AccessPermissions.ReadExecute)] // FIXME Friends
|
||||||
public HashSet<string>? MetabolizerTypes = null;
|
public HashSet<ProtoId<MetabolizerTypePrototype>>? MetabolizerTypes = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Should this metabolizer remove chemicals that have no metabolisms defined?
|
/// Should this metabolizer remove chemicals that have no metabolisms defined?
|
||||||
@@ -72,8 +76,8 @@ namespace Content.Server.Body.Components
|
|||||||
[DataDefinition]
|
[DataDefinition]
|
||||||
public sealed partial class MetabolismGroupEntry
|
public sealed partial class MetabolismGroupEntry
|
||||||
{
|
{
|
||||||
[DataField(required: true, customTypeSerializer:typeof(PrototypeIdSerializer<MetabolismGroupPrototype>))]
|
[DataField(required: true)]
|
||||||
public string Id = default!;
|
public ProtoId<MetabolismGroupPrototype> Id = default!;
|
||||||
|
|
||||||
[DataField("rateModifier")]
|
[DataField("rateModifier")]
|
||||||
public FixedPoint2 MetabolismRateModifier = 1.0;
|
public FixedPoint2 MetabolismRateModifier = 1.0;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Content.Server.Body.Systems;
|
using Content.Server.Body.Systems;
|
||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||||
|
|
||||||
namespace Content.Server.Body.Components
|
namespace Content.Server.Body.Components
|
||||||
{
|
{
|
||||||
@@ -7,36 +8,49 @@ namespace Content.Server.Body.Components
|
|||||||
public sealed partial class RespiratorComponent : Component
|
public sealed partial class RespiratorComponent : Component
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Saturation level. Reduced by CycleDelay each tick.
|
/// The next time that this body will inhale or exhale.
|
||||||
|
/// </summary>
|
||||||
|
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||||
|
public TimeSpan NextUpdate;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The interval between updates. Each update is either inhale or exhale,
|
||||||
|
/// so a full cycle takes twice as long.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public TimeSpan UpdateInterval = TimeSpan.FromSeconds(2);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Saturation level. Reduced by UpdateInterval each tick.
|
||||||
/// Can be thought of as 'how many seconds you have until you start suffocating' in this configuration.
|
/// Can be thought of as 'how many seconds you have until you start suffocating' in this configuration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("saturation")]
|
[DataField]
|
||||||
public float Saturation = 5.0f;
|
public float Saturation = 5.0f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// At what level of saturation will you begin to suffocate?
|
/// At what level of saturation will you begin to suffocate?
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("suffocationThreshold")]
|
[DataField]
|
||||||
public float SuffocationThreshold;
|
public float SuffocationThreshold;
|
||||||
|
|
||||||
[DataField("maxSaturation")]
|
[DataField]
|
||||||
public float MaxSaturation = 5.0f;
|
public float MaxSaturation = 5.0f;
|
||||||
|
|
||||||
[DataField("minSaturation")]
|
[DataField]
|
||||||
public float MinSaturation = -2.0f;
|
public float MinSaturation = -2.0f;
|
||||||
|
|
||||||
// TODO HYPEROXIA?
|
// TODO HYPEROXIA?
|
||||||
|
|
||||||
[DataField("damage", required: true)]
|
[DataField(required: true)]
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
public DamageSpecifier Damage = default!;
|
public DamageSpecifier Damage = default!;
|
||||||
|
|
||||||
[DataField("damageRecovery", required: true)]
|
[DataField(required: true)]
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
public DamageSpecifier DamageRecovery = default!;
|
public DamageSpecifier DamageRecovery = default!;
|
||||||
|
|
||||||
[DataField("gaspPopupCooldown")]
|
[DataField]
|
||||||
public TimeSpan GaspPopupCooldown { get; private set; } = TimeSpan.FromSeconds(8);
|
public TimeSpan GaspPopupCooldown = TimeSpan.FromSeconds(8);
|
||||||
|
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public TimeSpan LastGaspPopupTime;
|
public TimeSpan LastGaspPopupTime;
|
||||||
@@ -55,11 +69,6 @@ namespace Content.Server.Body.Components
|
|||||||
|
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public RespiratorStatus Status = RespiratorStatus.Inhaling;
|
public RespiratorStatus Status = RespiratorStatus.Inhaling;
|
||||||
|
|
||||||
[DataField("cycleDelay")]
|
|
||||||
public float CycleDelay = 2.0f;
|
|
||||||
|
|
||||||
public float AccumulatedFrametime;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,26 @@
|
|||||||
using Content.Server.Body.Systems;
|
using Content.Server.Body.Systems;
|
||||||
using Content.Server.Nutrition.EntitySystems;
|
using Content.Server.Nutrition.EntitySystems;
|
||||||
using Content.Shared.Chemistry.Components;
|
using Content.Shared.Chemistry.Components;
|
||||||
using Content.Shared.Chemistry.Reagent;
|
using Content.Shared.Chemistry.Reagent;
|
||||||
using Content.Shared.Whitelist;
|
using Content.Shared.Whitelist;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||||
|
|
||||||
namespace Content.Server.Body.Components
|
namespace Content.Server.Body.Components
|
||||||
{
|
{
|
||||||
[RegisterComponent, Access(typeof(StomachSystem), typeof(FoodSystem))]
|
[RegisterComponent, Access(typeof(StomachSystem), typeof(FoodSystem))]
|
||||||
public sealed partial class StomachComponent : Component
|
public sealed partial class StomachComponent : Component
|
||||||
{
|
{
|
||||||
public float AccumulatedFrameTime;
|
/// <summary>
|
||||||
|
/// The next time that the stomach will try to digest its contents.
|
||||||
|
/// </summary>
|
||||||
|
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||||
|
public TimeSpan NextUpdate;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// How fast should this component update, in seconds?
|
/// The interval at which this stomach digests its contents.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
public float UpdateInterval = 1.0f;
|
public TimeSpan UpdateInterval = TimeSpan.FromSeconds(1);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The solution inside of this stomach this transfers reagents to the body.
|
/// The solution inside of this stomach this transfers reagents to the body.
|
||||||
@@ -30,11 +35,11 @@ namespace Content.Server.Body.Components
|
|||||||
public string BodySolutionName = BloodstreamComponent.DefaultChemicalsSolutionName;
|
public string BodySolutionName = BloodstreamComponent.DefaultChemicalsSolutionName;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Time in seconds between reagents being ingested and them being
|
/// Time between reagents being ingested and them being
|
||||||
/// transferred to <see cref="BloodstreamComponent"/>
|
/// transferred to <see cref="BloodstreamComponent"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
public float DigestionDelay = 20;
|
public TimeSpan DigestionDelay = TimeSpan.FromSeconds(20);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A whitelist for what special-digestible-required foods this stomach is capable of eating.
|
/// A whitelist for what special-digestible-required foods this stomach is capable of eating.
|
||||||
@@ -54,15 +59,15 @@ namespace Content.Server.Body.Components
|
|||||||
public sealed class ReagentDelta
|
public sealed class ReagentDelta
|
||||||
{
|
{
|
||||||
public readonly ReagentQuantity ReagentQuantity;
|
public readonly ReagentQuantity ReagentQuantity;
|
||||||
public float Lifetime { get; private set; }
|
public TimeSpan Lifetime { get; private set; }
|
||||||
|
|
||||||
public ReagentDelta(ReagentQuantity reagentQuantity)
|
public ReagentDelta(ReagentQuantity reagentQuantity)
|
||||||
{
|
{
|
||||||
ReagentQuantity = reagentQuantity;
|
ReagentQuantity = reagentQuantity;
|
||||||
Lifetime = 0.0f;
|
Lifetime = TimeSpan.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Increment(float delta) => Lifetime += delta;
|
public void Increment(TimeSpan delta) => Lifetime += delta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Content.Server.Body.Systems;
|
using Content.Server.Body.Systems;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||||
|
|
||||||
namespace Content.Server.Body.Components;
|
namespace Content.Server.Body.Components;
|
||||||
|
|
||||||
@@ -6,48 +7,58 @@ namespace Content.Server.Body.Components;
|
|||||||
[Access(typeof(ThermalRegulatorSystem))]
|
[Access(typeof(ThermalRegulatorSystem))]
|
||||||
public sealed partial class ThermalRegulatorComponent : Component
|
public sealed partial class ThermalRegulatorComponent : Component
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The next time that the body will regulate its heat.
|
||||||
|
/// </summary>
|
||||||
|
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||||
|
public TimeSpan NextUpdate;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The interval at which thermal regulation is processed.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public TimeSpan UpdateInterval = TimeSpan.FromSeconds(1);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Heat generated due to metabolism. It's generated via metabolism
|
/// Heat generated due to metabolism. It's generated via metabolism
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("metabolismHeat")]
|
[DataField]
|
||||||
public float MetabolismHeat { get; private set; }
|
public float MetabolismHeat;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Heat output via radiation.
|
/// Heat output via radiation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("radiatedHeat")]
|
[DataField]
|
||||||
public float RadiatedHeat { get; private set; }
|
public float RadiatedHeat;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Maximum heat regulated via sweat
|
/// Maximum heat regulated via sweat
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("sweatHeatRegulation")]
|
[DataField]
|
||||||
public float SweatHeatRegulation { get; private set; }
|
public float SweatHeatRegulation;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Maximum heat regulated via shivering
|
/// Maximum heat regulated via shivering
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("shiveringHeatRegulation")]
|
[DataField]
|
||||||
public float ShiveringHeatRegulation { get; private set; }
|
public float ShiveringHeatRegulation;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Amount of heat regulation that represents thermal regulation processes not
|
/// Amount of heat regulation that represents thermal regulation processes not
|
||||||
/// explicitly coded.
|
/// explicitly coded.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("implicitHeatRegulation")]
|
[DataField]
|
||||||
public float ImplicitHeatRegulation { get; private set; }
|
public float ImplicitHeatRegulation;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Normal body temperature
|
/// Normal body temperature
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("normalBodyTemperature")]
|
[DataField]
|
||||||
public float NormalBodyTemperature { get; private set; }
|
public float NormalBodyTemperature;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deviation from normal temperature for body to start thermal regulation
|
/// Deviation from normal temperature for body to start thermal regulation
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("thermalRegulationTemperatureThreshold")]
|
[DataField]
|
||||||
public float ThermalRegulationTemperatureThreshold { get; private set; }
|
public float ThermalRegulationTemperatureThreshold;
|
||||||
|
|
||||||
public float AccumulatedFrametime;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ using Content.Shared.Damage;
|
|||||||
using Content.Shared.Damage.Prototypes;
|
using Content.Shared.Damage.Prototypes;
|
||||||
using Content.Shared.Drunk;
|
using Content.Shared.Drunk;
|
||||||
using Content.Shared.FixedPoint;
|
using Content.Shared.FixedPoint;
|
||||||
using Content.Shared.IdentityManagement;
|
|
||||||
using Content.Shared.Mobs.Systems;
|
using Content.Shared.Mobs.Systems;
|
||||||
using Content.Shared.Popups;
|
using Content.Shared.Popups;
|
||||||
using Content.Shared.Rejuvenate;
|
using Content.Shared.Rejuvenate;
|
||||||
@@ -21,11 +20,13 @@ using Content.Shared.Speech.EntitySystems;
|
|||||||
using Robust.Server.Audio;
|
using Robust.Server.Audio;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.Server.Body.Systems;
|
namespace Content.Server.Body.Systems;
|
||||||
|
|
||||||
public sealed class BloodstreamSystem : EntitySystem
|
public sealed class BloodstreamSystem : EntitySystem
|
||||||
{
|
{
|
||||||
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||||
[Dependency] private readonly AudioSystem _audio = default!;
|
[Dependency] private readonly AudioSystem _audio = default!;
|
||||||
@@ -44,6 +45,8 @@ public sealed class BloodstreamSystem : EntitySystem
|
|||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
SubscribeLocalEvent<BloodstreamComponent, ComponentInit>(OnComponentInit);
|
SubscribeLocalEvent<BloodstreamComponent, ComponentInit>(OnComponentInit);
|
||||||
|
SubscribeLocalEvent<BloodstreamComponent, MapInitEvent>(OnMapInit);
|
||||||
|
SubscribeLocalEvent<BloodstreamComponent, EntityUnpausedEvent>(OnUnpaused);
|
||||||
SubscribeLocalEvent<BloodstreamComponent, DamageChangedEvent>(OnDamageChanged);
|
SubscribeLocalEvent<BloodstreamComponent, DamageChangedEvent>(OnDamageChanged);
|
||||||
SubscribeLocalEvent<BloodstreamComponent, HealthBeingExaminedEvent>(OnHealthBeingExamined);
|
SubscribeLocalEvent<BloodstreamComponent, HealthBeingExaminedEvent>(OnHealthBeingExamined);
|
||||||
SubscribeLocalEvent<BloodstreamComponent, BeingGibbedEvent>(OnBeingGibbed);
|
SubscribeLocalEvent<BloodstreamComponent, BeingGibbedEvent>(OnBeingGibbed);
|
||||||
@@ -53,6 +56,16 @@ public sealed class BloodstreamSystem : EntitySystem
|
|||||||
SubscribeLocalEvent<BloodstreamComponent, RejuvenateEvent>(OnRejuvenate);
|
SubscribeLocalEvent<BloodstreamComponent, RejuvenateEvent>(OnRejuvenate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnMapInit(Entity<BloodstreamComponent> ent, ref MapInitEvent args)
|
||||||
|
{
|
||||||
|
ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.UpdateInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUnpaused(Entity<BloodstreamComponent> ent, ref EntityUnpausedEvent args)
|
||||||
|
{
|
||||||
|
ent.Comp.NextUpdate += args.PausedTime;
|
||||||
|
}
|
||||||
|
|
||||||
private void OnReactionAttempt(Entity<BloodstreamComponent> entity, ref ReactionAttemptEvent args)
|
private void OnReactionAttempt(Entity<BloodstreamComponent> entity, ref ReactionAttemptEvent args)
|
||||||
{
|
{
|
||||||
if (args.Cancelled)
|
if (args.Cancelled)
|
||||||
@@ -83,7 +96,9 @@ public sealed class BloodstreamSystem : EntitySystem
|
|||||||
if (args.Name != entity.Comp.BloodSolutionName
|
if (args.Name != entity.Comp.BloodSolutionName
|
||||||
&& args.Name != entity.Comp.ChemicalSolutionName
|
&& args.Name != entity.Comp.ChemicalSolutionName
|
||||||
&& args.Name != entity.Comp.BloodTemporarySolutionName)
|
&& args.Name != entity.Comp.BloodTemporarySolutionName)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
OnReactionAttempt(entity, ref args.Event);
|
OnReactionAttempt(entity, ref args.Event);
|
||||||
}
|
}
|
||||||
@@ -95,12 +110,10 @@ public sealed class BloodstreamSystem : EntitySystem
|
|||||||
var query = EntityQueryEnumerator<BloodstreamComponent>();
|
var query = EntityQueryEnumerator<BloodstreamComponent>();
|
||||||
while (query.MoveNext(out var uid, out var bloodstream))
|
while (query.MoveNext(out var uid, out var bloodstream))
|
||||||
{
|
{
|
||||||
bloodstream.AccumulatedFrametime += frameTime;
|
if (_gameTiming.CurTime < bloodstream.NextUpdate)
|
||||||
|
|
||||||
if (bloodstream.AccumulatedFrametime < bloodstream.UpdateInterval)
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
bloodstream.AccumulatedFrametime -= bloodstream.UpdateInterval;
|
bloodstream.NextUpdate += bloodstream.UpdateInterval;
|
||||||
|
|
||||||
if (!_solutionContainerSystem.ResolveSolution(uid, bloodstream.BloodSolutionName, ref bloodstream.BloodSolution, out var bloodSolution))
|
if (!_solutionContainerSystem.ResolveSolution(uid, bloodstream.BloodSolutionName, ref bloodstream.BloodSolution, out var bloodSolution))
|
||||||
continue;
|
continue;
|
||||||
@@ -128,13 +141,17 @@ public sealed class BloodstreamSystem : EntitySystem
|
|||||||
// bloodloss damage is based on the base value, and modified by how low your blood level is.
|
// bloodloss damage is based on the base value, and modified by how low your blood level is.
|
||||||
var amt = bloodstream.BloodlossDamage / (0.1f + bloodPercentage);
|
var amt = bloodstream.BloodlossDamage / (0.1f + bloodPercentage);
|
||||||
|
|
||||||
_damageableSystem.TryChangeDamage(uid, amt, false, false);
|
_damageableSystem.TryChangeDamage(uid, amt,
|
||||||
|
ignoreResistances: false, interruptsDoAfters: false);
|
||||||
|
|
||||||
// Apply dizziness as a symptom of bloodloss.
|
// Apply dizziness as a symptom of bloodloss.
|
||||||
// The effect is applied in a way that it will never be cleared without being healthy.
|
// The effect is applied in a way that it will never be cleared without being healthy.
|
||||||
// Multiplying by 2 is arbitrary but works for this case, it just prevents the time from running out
|
// Multiplying by 2 is arbitrary but works for this case, it just prevents the time from running out
|
||||||
_drunkSystem.TryApplyDrunkenness(uid, bloodstream.UpdateInterval*2, false);
|
_drunkSystem.TryApplyDrunkenness(
|
||||||
_stutteringSystem.DoStutter(uid, TimeSpan.FromSeconds(bloodstream.UpdateInterval*2), false);
|
uid,
|
||||||
|
(float) bloodstream.UpdateInterval.TotalSeconds * 2,
|
||||||
|
applySlur: false);
|
||||||
|
_stutteringSystem.DoStutter(uid, bloodstream.UpdateInterval * 2, refresh: false);
|
||||||
|
|
||||||
// storing the drunk and stutter time so we can remove it independently from other effects additions
|
// storing the drunk and stutter time so we can remove it independently from other effects additions
|
||||||
bloodstream.StatusTime += bloodstream.UpdateInterval * 2;
|
bloodstream.StatusTime += bloodstream.UpdateInterval * 2;
|
||||||
@@ -142,13 +159,16 @@ public sealed class BloodstreamSystem : EntitySystem
|
|||||||
else if (!_mobStateSystem.IsDead(uid))
|
else if (!_mobStateSystem.IsDead(uid))
|
||||||
{
|
{
|
||||||
// If they're healthy, we'll try and heal some bloodloss instead.
|
// If they're healthy, we'll try and heal some bloodloss instead.
|
||||||
_damageableSystem.TryChangeDamage(uid, bloodstream.BloodlossHealDamage * bloodPercentage, true, false);
|
_damageableSystem.TryChangeDamage(
|
||||||
|
uid,
|
||||||
|
bloodstream.BloodlossHealDamage * bloodPercentage,
|
||||||
|
ignoreResistances: true, interruptsDoAfters: false);
|
||||||
|
|
||||||
// Remove the drunk effect when healthy. Should only remove the amount of drunk and stutter added by low blood level
|
// Remove the drunk effect when healthy. Should only remove the amount of drunk and stutter added by low blood level
|
||||||
_drunkSystem.TryRemoveDrunkenessTime(uid, bloodstream.StatusTime);
|
_drunkSystem.TryRemoveDrunkenessTime(uid, bloodstream.StatusTime.TotalSeconds);
|
||||||
_stutteringSystem.DoRemoveStutterTime(uid, bloodstream.StatusTime);
|
_stutteringSystem.DoRemoveStutterTime(uid, bloodstream.StatusTime.TotalSeconds);
|
||||||
// Reset the drunk and stutter time to zero
|
// Reset the drunk and stutter time to zero
|
||||||
bloodstream.StatusTime = 0;
|
bloodstream.StatusTime = TimeSpan.Zero;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -167,17 +187,15 @@ public sealed class BloodstreamSystem : EntitySystem
|
|||||||
bloodSolution.AddReagent(entity.Comp.BloodReagent, entity.Comp.BloodMaxVolume - bloodSolution.Volume);
|
bloodSolution.AddReagent(entity.Comp.BloodReagent, entity.Comp.BloodMaxVolume - bloodSolution.Volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDamageChanged(EntityUid uid, BloodstreamComponent component, DamageChangedEvent args)
|
private void OnDamageChanged(Entity<BloodstreamComponent> ent, ref DamageChangedEvent args)
|
||||||
|
{
|
||||||
|
if (args.DamageDelta is null || !args.DamageIncreased)
|
||||||
{
|
{
|
||||||
if (args.DamageDelta is null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// definitely don't make them bleed if they got healed
|
|
||||||
if (!args.DamageIncreased)
|
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO probably cache this or something. humans get hurt a lot
|
// TODO probably cache this or something. humans get hurt a lot
|
||||||
if (!_prototypeManager.TryIndex<DamageModifierSetPrototype>(component.DamageBleedModifiers, out var modifiers))
|
if (!_prototypeManager.TryIndex<DamageModifierSetPrototype>(ent.Comp.DamageBleedModifiers, out var modifiers))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var bloodloss = DamageSpecifier.ApplyModifierSet(args.DamageDelta, modifiers);
|
var bloodloss = DamageSpecifier.ApplyModifierSet(args.DamageDelta, modifiers);
|
||||||
@@ -186,10 +204,10 @@ public sealed class BloodstreamSystem : EntitySystem
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// Does the calculation of how much bleed rate should be added/removed, then applies it
|
// Does the calculation of how much bleed rate should be added/removed, then applies it
|
||||||
var oldBleedAmount = component.BleedAmount;
|
var oldBleedAmount = ent.Comp.BleedAmount;
|
||||||
var total = bloodloss.GetTotal();
|
var total = bloodloss.GetTotal();
|
||||||
var totalFloat = total.Float();
|
var totalFloat = total.Float();
|
||||||
TryModifyBleedAmount(uid, totalFloat, component);
|
TryModifyBleedAmount(ent, totalFloat, ent);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Critical hit. Causes target to lose blood, using the bleed rate modifier of the weapon, currently divided by 5
|
/// Critical hit. Causes target to lose blood, using the bleed rate modifier of the weapon, currently divided by 5
|
||||||
@@ -199,8 +217,8 @@ public sealed class BloodstreamSystem : EntitySystem
|
|||||||
var prob = Math.Clamp(totalFloat / 25, 0, 1);
|
var prob = Math.Clamp(totalFloat / 25, 0, 1);
|
||||||
if (totalFloat > 0 && _robustRandom.Prob(prob))
|
if (totalFloat > 0 && _robustRandom.Prob(prob))
|
||||||
{
|
{
|
||||||
TryModifyBloodLevel(uid, (-total) / 5, component);
|
TryModifyBloodLevel(ent, (-total) / 5, ent);
|
||||||
_audio.PlayPvs(component.InstantBloodSound, uid);
|
_audio.PlayPvs(ent.Comp.InstantBloodSound, ent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Heat damage will cauterize, causing the bleed rate to be reduced.
|
// Heat damage will cauterize, causing the bleed rate to be reduced.
|
||||||
@@ -210,53 +228,52 @@ public sealed class BloodstreamSystem : EntitySystem
|
|||||||
// because it's burn damage that cauterized their wounds.
|
// because it's burn damage that cauterized their wounds.
|
||||||
|
|
||||||
// We'll play a special sound and popup for feedback.
|
// We'll play a special sound and popup for feedback.
|
||||||
_audio.PlayPvs(component.BloodHealedSound, uid);
|
_audio.PlayPvs(ent.Comp.BloodHealedSound, ent);
|
||||||
_popupSystem.PopupEntity(Loc.GetString("bloodstream-component-wounds-cauterized"), uid,
|
_popupSystem.PopupEntity(Loc.GetString("bloodstream-component-wounds-cauterized"), ent,
|
||||||
uid, PopupType.Medium);
|
ent, PopupType.Medium);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Shows text on health examine, based on bleed rate and blood level.
|
/// Shows text on health examine, based on bleed rate and blood level.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void OnHealthBeingExamined(EntityUid uid, BloodstreamComponent component, HealthBeingExaminedEvent args)
|
private void OnHealthBeingExamined(Entity<BloodstreamComponent> ent, ref HealthBeingExaminedEvent args)
|
||||||
{
|
{
|
||||||
// Shows profusely bleeding at half the max bleed rate.
|
// Shows profusely bleeding at half the max bleed rate.
|
||||||
if (component.BleedAmount > component.MaxBleedAmount / 2)
|
if (ent.Comp.BleedAmount > ent.Comp.MaxBleedAmount / 2)
|
||||||
{
|
{
|
||||||
args.Message.PushNewline();
|
args.Message.PushNewline();
|
||||||
args.Message.AddMarkup(Loc.GetString("bloodstream-component-profusely-bleeding", ("target", Identity.Entity(uid, EntityManager))));
|
args.Message.AddMarkup(Loc.GetString("bloodstream-component-profusely-bleeding", ("target", ent.Owner)));
|
||||||
}
|
}
|
||||||
// Shows bleeding message when bleeding, but less than profusely.
|
// Shows bleeding message when bleeding, but less than profusely.
|
||||||
else if (component.BleedAmount > 0)
|
else if (ent.Comp.BleedAmount > 0)
|
||||||
{
|
{
|
||||||
args.Message.PushNewline();
|
args.Message.PushNewline();
|
||||||
args.Message.AddMarkup(Loc.GetString("bloodstream-component-bleeding", ("target", Identity.Entity(uid, EntityManager))));
|
args.Message.AddMarkup(Loc.GetString("bloodstream-component-bleeding", ("target", ent.Owner)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the mob's blood level is below the damage threshhold, the pale message is added.
|
// If the mob's blood level is below the damage threshhold, the pale message is added.
|
||||||
if (GetBloodLevelPercentage(uid, component) < component.BloodlossThreshold)
|
if (GetBloodLevelPercentage(ent, ent) < ent.Comp.BloodlossThreshold)
|
||||||
{
|
{
|
||||||
args.Message.PushNewline();
|
args.Message.PushNewline();
|
||||||
args.Message.AddMarkup(Loc.GetString("bloodstream-component-looks-pale", ("target", Identity.Entity(uid, EntityManager))));
|
args.Message.AddMarkup(Loc.GetString("bloodstream-component-looks-pale", ("target", ent.Owner)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnBeingGibbed(EntityUid uid, BloodstreamComponent component, BeingGibbedEvent args)
|
private void OnBeingGibbed(Entity<BloodstreamComponent> ent, ref BeingGibbedEvent args)
|
||||||
{
|
{
|
||||||
SpillAllSolutions(uid, component);
|
SpillAllSolutions(ent, ent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnApplyMetabolicMultiplier(EntityUid uid, BloodstreamComponent component, ApplyMetabolicMultiplierEvent args)
|
private void OnApplyMetabolicMultiplier(
|
||||||
|
Entity<BloodstreamComponent> ent,
|
||||||
|
ref ApplyMetabolicMultiplierEvent args)
|
||||||
{
|
{
|
||||||
if (args.Apply)
|
if (args.Apply)
|
||||||
{
|
{
|
||||||
component.UpdateInterval *= args.Multiplier;
|
ent.Comp.UpdateInterval *= args.Multiplier;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
component.UpdateInterval /= args.Multiplier;
|
ent.Comp.UpdateInterval /= args.Multiplier;
|
||||||
// Reset the accumulator properly
|
|
||||||
if (component.AccumulatedFrametime >= component.UpdateInterval)
|
|
||||||
component.AccumulatedFrametime = component.UpdateInterval;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnRejuvenate(Entity<BloodstreamComponent> entity, ref RejuvenateEvent args)
|
private void OnRejuvenate(Entity<BloodstreamComponent> entity, ref RejuvenateEvent args)
|
||||||
@@ -275,21 +292,15 @@ public sealed class BloodstreamSystem : EntitySystem
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool TryAddToChemicals(EntityUid uid, Solution solution, BloodstreamComponent? component = null)
|
public bool TryAddToChemicals(EntityUid uid, Solution solution, BloodstreamComponent? component = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(uid, ref component, false))
|
return Resolve(uid, ref component, logMissing: false)
|
||||||
return false;
|
&& _solutionContainerSystem.ResolveSolution(uid, component.ChemicalSolutionName, ref component.ChemicalSolution)
|
||||||
|
&& _solutionContainerSystem.TryAddSolution(component.ChemicalSolution.Value, solution);
|
||||||
if (!_solutionContainerSystem.ResolveSolution(uid, component.ChemicalSolutionName, ref component.ChemicalSolution))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return _solutionContainerSystem.TryAddSolution(component.ChemicalSolution.Value, solution);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool FlushChemicals(EntityUid uid, string excludedReagentID, FixedPoint2 quantity, BloodstreamComponent? component = null)
|
public bool FlushChemicals(EntityUid uid, string excludedReagentID, FixedPoint2 quantity, BloodstreamComponent? component = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(uid, ref component, false))
|
if (!Resolve(uid, ref component, logMissing: false)
|
||||||
return false;
|
|| !_solutionContainerSystem.ResolveSolution(uid, component.ChemicalSolutionName, ref component.ChemicalSolution, out var chemSolution))
|
||||||
|
|
||||||
if (!_solutionContainerSystem.ResolveSolution(uid, component.ChemicalSolutionName, ref component.ChemicalSolution, out var chemSolution))
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
for (var i = chemSolution.Contents.Count - 1; i >= 0; i--)
|
for (var i = chemSolution.Contents.Count - 1; i >= 0; i--)
|
||||||
@@ -306,11 +317,11 @@ public sealed class BloodstreamSystem : EntitySystem
|
|||||||
|
|
||||||
public float GetBloodLevelPercentage(EntityUid uid, BloodstreamComponent? component = null)
|
public float GetBloodLevelPercentage(EntityUid uid, BloodstreamComponent? component = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(uid, ref component))
|
if (!Resolve(uid, ref component)
|
||||||
return 0.0f;
|
|| !_solutionContainerSystem.ResolveSolution(uid, component.BloodSolutionName, ref component.BloodSolution, out var bloodSolution))
|
||||||
|
{
|
||||||
if (!_solutionContainerSystem.ResolveSolution(uid, component.BloodSolutionName, ref component.BloodSolution, out var bloodSolution))
|
|
||||||
return 0.0f;
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
return bloodSolution.FillFraction;
|
return bloodSolution.FillFraction;
|
||||||
}
|
}
|
||||||
@@ -328,11 +339,11 @@ public sealed class BloodstreamSystem : EntitySystem
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool TryModifyBloodLevel(EntityUid uid, FixedPoint2 amount, BloodstreamComponent? component = null)
|
public bool TryModifyBloodLevel(EntityUid uid, FixedPoint2 amount, BloodstreamComponent? component = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(uid, ref component, false))
|
if (!Resolve(uid, ref component, logMissing: false)
|
||||||
return false;
|
|| !_solutionContainerSystem.ResolveSolution(uid, component.BloodSolutionName, ref component.BloodSolution))
|
||||||
|
{
|
||||||
if (!_solutionContainerSystem.ResolveSolution(uid, component.BloodSolutionName, ref component.BloodSolution))
|
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (amount >= 0)
|
if (amount >= 0)
|
||||||
return _solutionContainerSystem.TryAddReagent(component.BloodSolution.Value, component.BloodReagent, amount, out _);
|
return _solutionContainerSystem.TryAddReagent(component.BloodSolution.Value, component.BloodReagent, amount, out _);
|
||||||
@@ -356,9 +367,9 @@ public sealed class BloodstreamSystem : EntitySystem
|
|||||||
tempSolution.AddSolution(temp, _prototypeManager);
|
tempSolution.AddSolution(temp, _prototypeManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_puddleSystem.TrySpillAt(uid, tempSolution, out var puddleUid, false))
|
if (_puddleSystem.TrySpillAt(uid, tempSolution, out var puddleUid, sound: false))
|
||||||
{
|
{
|
||||||
_forensicsSystem.TransferDna(puddleUid, uid, false);
|
_forensicsSystem.TransferDna(puddleUid, uid, canDnaBeCleaned: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
tempSolution.RemoveAllSolution();
|
tempSolution.RemoveAllSolution();
|
||||||
@@ -374,7 +385,7 @@ public sealed class BloodstreamSystem : EntitySystem
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool TryModifyBleedAmount(EntityUid uid, float amount, BloodstreamComponent? component = null)
|
public bool TryModifyBleedAmount(EntityUid uid, float amount, BloodstreamComponent? component = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(uid, ref component, false))
|
if (!Resolve(uid, ref component, logMissing: false))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
component.BleedAmount += amount;
|
component.BleedAmount += amount;
|
||||||
@@ -424,7 +435,7 @@ public sealed class BloodstreamSystem : EntitySystem
|
|||||||
|
|
||||||
if (_puddleSystem.TrySpillAt(uid, tempSol, out var puddleUid))
|
if (_puddleSystem.TrySpillAt(uid, tempSol, out var puddleUid))
|
||||||
{
|
{
|
||||||
_forensicsSystem.TransferDna(puddleUid, uid, false);
|
_forensicsSystem.TransferDna(puddleUid, uid, canDnaBeCleaned: false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,11 +444,11 @@ public sealed class BloodstreamSystem : EntitySystem
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void ChangeBloodReagent(EntityUid uid, string reagent, BloodstreamComponent? component = null)
|
public void ChangeBloodReagent(EntityUid uid, string reagent, BloodstreamComponent? component = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(uid, ref component, false))
|
if (!Resolve(uid, ref component, logMissing: false)
|
||||||
return;
|
|| reagent == component.BloodReagent)
|
||||||
|
{
|
||||||
if (reagent == component.BloodReagent)
|
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!_solutionContainerSystem.ResolveSolution(uid, component.BloodSolutionName, ref component.BloodSolution, out var bloodSolution))
|
if (!_solutionContainerSystem.ResolveSolution(uid, component.BloodSolutionName, ref component.BloodSolution, out var bloodSolution))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,23 +1,19 @@
|
|||||||
using Content.Server.Body.Components;
|
using Content.Server.Body.Components;
|
||||||
using Content.Server.GameTicking;
|
using Content.Server.GameTicking;
|
||||||
using Content.Server.Humanoid;
|
using Content.Server.Humanoid;
|
||||||
using Content.Server.Kitchen.Components;
|
|
||||||
using Content.Shared.Body.Components;
|
using Content.Shared.Body.Components;
|
||||||
using Content.Shared.Body.Part;
|
using Content.Shared.Body.Part;
|
||||||
using Content.Shared.Body.Systems;
|
using Content.Shared.Body.Systems;
|
||||||
using Content.Shared.Humanoid;
|
using Content.Shared.Humanoid;
|
||||||
using Content.Shared.Kitchen.Components;
|
|
||||||
using Content.Shared.Mind;
|
using Content.Shared.Mind;
|
||||||
using Content.Shared.Mobs.Systems;
|
using Content.Shared.Mobs.Systems;
|
||||||
using Content.Shared.Movement.Events;
|
using Content.Shared.Movement.Events;
|
||||||
|
using Content.Shared.Movement.Systems;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Audio.Systems;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Content.Shared.Gibbing.Components;
|
|
||||||
using Content.Shared.Movement.Systems;
|
|
||||||
using Robust.Shared.Audio.Systems;
|
|
||||||
|
|
||||||
namespace Content.Server.Body.Systems;
|
namespace Content.Server.Body.Systems;
|
||||||
|
|
||||||
@@ -28,7 +24,6 @@ public sealed class BodySystem : SharedBodySystem
|
|||||||
[Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!;
|
[Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!;
|
||||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
|
||||||
[Dependency] private readonly SharedMindSystem _mindSystem = default!;
|
[Dependency] private readonly SharedMindSystem _mindSystem = default!;
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
|
||||||
@@ -40,7 +35,7 @@ public sealed class BodySystem : SharedBodySystem
|
|||||||
SubscribeLocalEvent<BodyComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
|
SubscribeLocalEvent<BodyComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnRelayMoveInput(EntityUid uid, BodyComponent component, ref MoveInputEvent args)
|
private void OnRelayMoveInput(Entity<BodyComponent> ent, ref MoveInputEvent args)
|
||||||
{
|
{
|
||||||
// If they haven't actually moved then ignore it.
|
// If they haven't actually moved then ignore it.
|
||||||
if ((args.Component.HeldMoveButtons &
|
if ((args.Component.HeldMoveButtons &
|
||||||
@@ -49,62 +44,61 @@ public sealed class BodySystem : SharedBodySystem
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_mobState.IsDead(uid) && _mindSystem.TryGetMind(uid, out var mindId, out var mind))
|
if (_mobState.IsDead(ent) && _mindSystem.TryGetMind(ent, out var mindId, out var mind))
|
||||||
{
|
{
|
||||||
mind.TimeOfDeath ??= _gameTiming.RealTime;
|
mind.TimeOfDeath ??= _gameTiming.RealTime;
|
||||||
_ticker.OnGhostAttempt(mindId, true, mind: mind);
|
_ticker.OnGhostAttempt(mindId, canReturnGlobal: true, mind: mind);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnApplyMetabolicMultiplier(EntityUid uid, BodyComponent component,
|
private void OnApplyMetabolicMultiplier(
|
||||||
ApplyMetabolicMultiplierEvent args)
|
Entity<BodyComponent> ent,
|
||||||
|
ref ApplyMetabolicMultiplierEvent args)
|
||||||
{
|
{
|
||||||
foreach (var organ in GetBodyOrgans(uid, component))
|
foreach (var organ in GetBodyOrgans(ent, ent))
|
||||||
{
|
{
|
||||||
RaiseLocalEvent(organ.Id, args);
|
RaiseLocalEvent(organ.Id, ref args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void AddPart(
|
protected override void AddPart(
|
||||||
EntityUid bodyUid,
|
Entity<BodyComponent?> bodyEnt,
|
||||||
EntityUid partUid,
|
Entity<BodyPartComponent> partEnt,
|
||||||
string slotId,
|
string slotId)
|
||||||
BodyPartComponent component,
|
|
||||||
BodyComponent? bodyComp = null)
|
|
||||||
{
|
{
|
||||||
// TODO: Predict this probably.
|
// TODO: Predict this probably.
|
||||||
base.AddPart(bodyUid, partUid, slotId, component, bodyComp);
|
base.AddPart(bodyEnt, partEnt, slotId);
|
||||||
|
|
||||||
if (TryComp<HumanoidAppearanceComponent>(bodyUid, out var humanoid))
|
if (TryComp<HumanoidAppearanceComponent>(bodyEnt, out var humanoid))
|
||||||
{
|
{
|
||||||
var layer = component.ToHumanoidLayers();
|
var layer = partEnt.Comp.ToHumanoidLayers();
|
||||||
if (layer != null)
|
if (layer != null)
|
||||||
{
|
{
|
||||||
var layers = HumanoidVisualLayersExtension.Sublayers(layer.Value);
|
var layers = HumanoidVisualLayersExtension.Sublayers(layer.Value);
|
||||||
_humanoidSystem.SetLayersVisibility(bodyUid, layers, true, true, humanoid);
|
_humanoidSystem.SetLayersVisibility(
|
||||||
|
bodyEnt, layers, visible: true, permanent: true, humanoid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void RemovePart(
|
protected override void RemovePart(
|
||||||
EntityUid bodyUid,
|
Entity<BodyComponent?> bodyEnt,
|
||||||
EntityUid partUid,
|
Entity<BodyPartComponent> partEnt,
|
||||||
string slotId,
|
string slotId)
|
||||||
BodyPartComponent component,
|
|
||||||
BodyComponent? bodyComp = null)
|
|
||||||
{
|
{
|
||||||
base.RemovePart(bodyUid, partUid, slotId, component, bodyComp);
|
base.RemovePart(bodyEnt, partEnt, slotId);
|
||||||
|
|
||||||
if (!TryComp<HumanoidAppearanceComponent>(bodyUid, out var humanoid))
|
if (!TryComp<HumanoidAppearanceComponent>(bodyEnt, out var humanoid))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var layer = component.ToHumanoidLayers();
|
var layer = partEnt.Comp.ToHumanoidLayers();
|
||||||
|
|
||||||
if (layer == null)
|
if (layer is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var layers = HumanoidVisualLayersExtension.Sublayers(layer.Value);
|
var layers = HumanoidVisualLayersExtension.Sublayers(layer.Value);
|
||||||
_humanoidSystem.SetLayersVisibility(bodyUid, layers, false, true, humanoid);
|
_humanoidSystem.SetLayersVisibility(
|
||||||
|
bodyEnt, layers, visible: false, permanent: true, humanoid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override HashSet<EntityUid> GibBody(
|
public override HashSet<EntityUid> GibBody(
|
||||||
@@ -118,19 +112,23 @@ public sealed class BodySystem : SharedBodySystem
|
|||||||
SoundSpecifier? gibSoundOverride = null
|
SoundSpecifier? gibSoundOverride = null
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if (!Resolve(bodyId, ref body, false))
|
if (!Resolve(bodyId, ref body, logMissing: false)
|
||||||
return new HashSet<EntityUid>();
|
|| TerminatingOrDeleted(bodyId)
|
||||||
|
|| EntityManager.IsQueuedForDeletion(bodyId))
|
||||||
if (TerminatingOrDeleted(bodyId) || EntityManager.IsQueuedForDeletion(bodyId))
|
{
|
||||||
return new HashSet<EntityUid>();
|
return new HashSet<EntityUid>();
|
||||||
|
}
|
||||||
|
|
||||||
var xform = Transform(bodyId);
|
var xform = Transform(bodyId);
|
||||||
if (xform.MapUid == null)
|
if (xform.MapUid is null)
|
||||||
return new HashSet<EntityUid>();
|
return new HashSet<EntityUid>();
|
||||||
|
|
||||||
var gibs = base.GibBody(bodyId, gibOrgans, body, launchGibs: launchGibs,
|
var gibs = base.GibBody(bodyId, gibOrgans, body, launchGibs: launchGibs,
|
||||||
splatDirection: splatDirection, splatModifier: splatModifier, splatCone:splatCone);
|
splatDirection: splatDirection, splatModifier: splatModifier, splatCone:splatCone);
|
||||||
RaiseLocalEvent(bodyId, new BeingGibbedEvent(gibs));
|
|
||||||
|
var ev = new BeingGibbedEvent(gibs);
|
||||||
|
RaiseLocalEvent(bodyId, ref ev);
|
||||||
|
|
||||||
QueueDel(bodyId);
|
QueueDel(bodyId);
|
||||||
|
|
||||||
return gibs;
|
return gibs;
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ namespace Content.Server.Body.Systems
|
|||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
SubscribeLocalEvent<BrainComponent, AddedToPartInBodyEvent>((uid, _, args) => HandleMind(args.Body, uid));
|
SubscribeLocalEvent<BrainComponent, OrganAddedToBodyEvent>((uid, _, args) => HandleMind(args.Body, uid));
|
||||||
SubscribeLocalEvent<BrainComponent, RemovedFromPartInBodyEvent>((uid, _, args) => HandleMind(uid, args.OldBody));
|
SubscribeLocalEvent<BrainComponent, OrganRemovedFromBodyEvent>((uid, _, args) => HandleMind(uid, args.OldBody));
|
||||||
SubscribeLocalEvent<BrainComponent, PointAttemptEvent>(OnPointAttempt);
|
SubscribeLocalEvent<BrainComponent, PointAttemptEvent>(OnPointAttempt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ namespace Content.Server.Body.Systems
|
|||||||
_mindSystem.TransferTo(mindId, newEntity, mind: mind);
|
_mindSystem.TransferTo(mindId, newEntity, mind: mind);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPointAttempt(EntityUid uid, BrainComponent component, PointAttemptEvent args)
|
private void OnPointAttempt(Entity<BrainComponent> ent, ref PointAttemptEvent args)
|
||||||
{
|
{
|
||||||
args.Cancel();
|
args.Cancel();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,12 +18,10 @@ namespace Content.Server.Body.Systems;
|
|||||||
|
|
||||||
public sealed class InternalsSystem : EntitySystem
|
public sealed class InternalsSystem : EntitySystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
|
||||||
[Dependency] private readonly AlertsSystem _alerts = default!;
|
[Dependency] private readonly AlertsSystem _alerts = default!;
|
||||||
[Dependency] private readonly AtmosphereSystem _atmos = default!;
|
[Dependency] private readonly AtmosphereSystem _atmos = default!;
|
||||||
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||||
[Dependency] private readonly GasTankSystem _gasTank = default!;
|
[Dependency] private readonly GasTankSystem _gasTank = default!;
|
||||||
[Dependency] private readonly HandsSystem _hands = default!;
|
|
||||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||||
|
|
||||||
@@ -40,16 +38,20 @@ public sealed class InternalsSystem : EntitySystem
|
|||||||
SubscribeLocalEvent<InternalsComponent, InternalsDoAfterEvent>(OnDoAfter);
|
SubscribeLocalEvent<InternalsComponent, InternalsDoAfterEvent>(OnDoAfter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnGetInteractionVerbs(EntityUid uid, InternalsComponent component, GetVerbsEvent<InteractionVerb> args)
|
private void OnGetInteractionVerbs(
|
||||||
|
Entity<InternalsComponent> ent,
|
||||||
|
ref GetVerbsEvent<InteractionVerb> args)
|
||||||
{
|
{
|
||||||
if (!args.CanAccess || !args.CanInteract || args.Hands == null)
|
if (!args.CanAccess || !args.CanInteract || args.Hands is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
var user = args.User;
|
||||||
|
|
||||||
InteractionVerb verb = new()
|
InteractionVerb verb = new()
|
||||||
{
|
{
|
||||||
Act = () =>
|
Act = () =>
|
||||||
{
|
{
|
||||||
ToggleInternals(uid, args.User, false, component);
|
ToggleInternals(ent, user, force: false, ent);
|
||||||
},
|
},
|
||||||
Message = Loc.GetString("action-description-internals-toggle"),
|
Message = Loc.GetString("action-description-internals-toggle"),
|
||||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/dot.svg.192dpi.png")),
|
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/dot.svg.192dpi.png")),
|
||||||
@@ -59,9 +61,13 @@ public sealed class InternalsSystem : EntitySystem
|
|||||||
args.Verbs.Add(verb);
|
args.Verbs.Add(verb);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ToggleInternals(EntityUid uid, EntityUid user, bool force, InternalsComponent? internals = null)
|
public void ToggleInternals(
|
||||||
|
EntityUid uid,
|
||||||
|
EntityUid user,
|
||||||
|
bool force,
|
||||||
|
InternalsComponent? internals = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(uid, ref internals, false))
|
if (!Resolve(uid, ref internals, logMissing: false))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Toggle off if they're on
|
// Toggle off if they're on
|
||||||
@@ -73,12 +79,12 @@ public sealed class InternalsSystem : EntitySystem
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
StartToggleInternalsDoAfter(user, uid, internals);
|
StartToggleInternalsDoAfter(user, (uid, internals));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If they're not on then check if we have a mask to use
|
// If they're not on then check if we have a mask to use
|
||||||
if (internals.BreathToolEntity == null)
|
if (internals.BreathToolEntity is null)
|
||||||
{
|
{
|
||||||
_popupSystem.PopupEntity(Loc.GetString("internals-no-breath-tool"), uid, user);
|
_popupSystem.PopupEntity(Loc.GetString("internals-no-breath-tool"), uid, user);
|
||||||
return;
|
return;
|
||||||
@@ -86,7 +92,7 @@ public sealed class InternalsSystem : EntitySystem
|
|||||||
|
|
||||||
var tank = FindBestGasTank(uid);
|
var tank = FindBestGasTank(uid);
|
||||||
|
|
||||||
if (tank == null)
|
if (tank is null)
|
||||||
{
|
{
|
||||||
_popupSystem.PopupEntity(Loc.GetString("internals-no-tank"), uid, user);
|
_popupSystem.PopupEntity(Loc.GetString("internals-no-tank"), uid, user);
|
||||||
return;
|
return;
|
||||||
@@ -94,20 +100,20 @@ public sealed class InternalsSystem : EntitySystem
|
|||||||
|
|
||||||
if (!force)
|
if (!force)
|
||||||
{
|
{
|
||||||
StartToggleInternalsDoAfter(user, uid, internals);
|
StartToggleInternalsDoAfter(user, (uid, internals));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_gasTank.ConnectToInternals(tank.Value);
|
_gasTank.ConnectToInternals(tank.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void StartToggleInternalsDoAfter(EntityUid user, EntityUid target, InternalsComponent internals)
|
private void StartToggleInternalsDoAfter(EntityUid user, Entity<InternalsComponent> targetEnt)
|
||||||
{
|
{
|
||||||
// Is the target not you? If yes, use a do-after to give them time to respond.
|
// Is the target not you? If yes, use a do-after to give them time to respond.
|
||||||
var isUser = user == target;
|
var isUser = user == targetEnt.Owner;
|
||||||
var delay = !isUser ? internals.Delay : 0f;
|
var delay = !isUser ? targetEnt.Comp.Delay : TimeSpan.Zero;
|
||||||
|
|
||||||
_doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, user, delay, new InternalsDoAfterEvent(), target, target: target)
|
_doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, user, delay, new InternalsDoAfterEvent(), targetEnt, target: targetEnt)
|
||||||
{
|
{
|
||||||
BreakOnDamage = true,
|
BreakOnDamage = true,
|
||||||
BreakOnMove = true,
|
BreakOnMove = true,
|
||||||
@@ -115,41 +121,40 @@ public sealed class InternalsSystem : EntitySystem
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDoAfter(EntityUid uid, InternalsComponent component, InternalsDoAfterEvent args)
|
private void OnDoAfter(Entity<InternalsComponent> ent, ref InternalsDoAfterEvent args)
|
||||||
{
|
{
|
||||||
if (args.Cancelled || args.Handled)
|
if (args.Cancelled || args.Handled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ToggleInternals(uid, args.User, true, component);
|
ToggleInternals(ent, args.User, force: true, ent);
|
||||||
|
|
||||||
args.Handled = true;
|
args.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnInternalsStartup(EntityUid uid, InternalsComponent component, ComponentStartup args)
|
private void OnInternalsStartup(Entity<InternalsComponent> ent, ref ComponentStartup args)
|
||||||
{
|
{
|
||||||
_alerts.ShowAlert(uid, AlertType.Internals, GetSeverity(component));
|
_alerts.ShowAlert(ent, AlertType.Internals, GetSeverity(ent));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnInternalsShutdown(EntityUid uid, InternalsComponent component, ComponentShutdown args)
|
private void OnInternalsShutdown(Entity<InternalsComponent> ent, ref ComponentShutdown args)
|
||||||
{
|
{
|
||||||
_alerts.ClearAlert(uid, AlertType.Internals);
|
_alerts.ClearAlert(ent, AlertType.Internals);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnInhaleLocation(EntityUid uid, InternalsComponent component, InhaleLocationEvent args)
|
private void OnInhaleLocation(Entity<InternalsComponent> ent, ref InhaleLocationEvent args)
|
||||||
{
|
{
|
||||||
if (AreInternalsWorking(component))
|
if (AreInternalsWorking(ent))
|
||||||
{
|
{
|
||||||
var gasTank = Comp<GasTankComponent>(component.GasTankEntity!.Value);
|
var gasTank = Comp<GasTankComponent>(ent.Comp.GasTankEntity!.Value);
|
||||||
args.Gas = _gasTank.RemoveAirVolume((component.GasTankEntity.Value, gasTank), Atmospherics.BreathVolume);
|
args.Gas = _gasTank.RemoveAirVolume((ent.Comp.GasTankEntity.Value, gasTank), Atmospherics.BreathVolume);
|
||||||
// TODO: Should listen to gas tank updates instead I guess?
|
// TODO: Should listen to gas tank updates instead I guess?
|
||||||
_alerts.ShowAlert(uid, AlertType.Internals, GetSeverity(component));
|
_alerts.ShowAlert(ent, AlertType.Internals, GetSeverity(ent));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public void DisconnectBreathTool(Entity<InternalsComponent> ent)
|
public void DisconnectBreathTool(Entity<InternalsComponent> ent)
|
||||||
{
|
{
|
||||||
var (owner, component) = ent;
|
var old = ent.Comp.BreathToolEntity;
|
||||||
var old = component.BreathToolEntity;
|
ent.Comp.BreathToolEntity = null;
|
||||||
component.BreathToolEntity = null;
|
|
||||||
|
|
||||||
if (TryComp(old, out BreathToolComponent? breathTool))
|
if (TryComp(old, out BreathToolComponent? breathTool))
|
||||||
{
|
{
|
||||||
@@ -157,24 +162,23 @@ public sealed class InternalsSystem : EntitySystem
|
|||||||
DisconnectTank(ent);
|
DisconnectTank(ent);
|
||||||
}
|
}
|
||||||
|
|
||||||
_alerts.ShowAlert(owner, AlertType.Internals, GetSeverity(component));
|
_alerts.ShowAlert(ent, AlertType.Internals, GetSeverity(ent));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ConnectBreathTool(Entity<InternalsComponent> ent, EntityUid toolEntity)
|
public void ConnectBreathTool(Entity<InternalsComponent> ent, EntityUid toolEntity)
|
||||||
{
|
{
|
||||||
var (owner, component) = ent;
|
if (TryComp(ent.Comp.BreathToolEntity, out BreathToolComponent? tool))
|
||||||
if (TryComp(component.BreathToolEntity, out BreathToolComponent? tool))
|
|
||||||
{
|
{
|
||||||
_atmos.DisconnectInternals(tool);
|
_atmos.DisconnectInternals(tool);
|
||||||
}
|
}
|
||||||
|
|
||||||
component.BreathToolEntity = toolEntity;
|
ent.Comp.BreathToolEntity = toolEntity;
|
||||||
_alerts.ShowAlert(owner, AlertType.Internals, GetSeverity(component));
|
_alerts.ShowAlert(ent, AlertType.Internals, GetSeverity(ent));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DisconnectTank(InternalsComponent? component)
|
public void DisconnectTank(InternalsComponent? component)
|
||||||
{
|
{
|
||||||
if (component == null)
|
if (component is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (TryComp(component.GasTankEntity, out GasTankComponent? tank))
|
if (TryComp(component.GasTankEntity, out GasTankComponent? tank))
|
||||||
@@ -186,46 +190,47 @@ public sealed class InternalsSystem : EntitySystem
|
|||||||
|
|
||||||
public bool TryConnectTank(Entity<InternalsComponent> ent, EntityUid tankEntity)
|
public bool TryConnectTank(Entity<InternalsComponent> ent, EntityUid tankEntity)
|
||||||
{
|
{
|
||||||
var component = ent.Comp;
|
if (ent.Comp.BreathToolEntity is null)
|
||||||
if (component.BreathToolEntity == null)
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (TryComp(component.GasTankEntity, out GasTankComponent? tank))
|
if (TryComp(ent.Comp.GasTankEntity, out GasTankComponent? tank))
|
||||||
_gasTank.DisconnectFromInternals((component.GasTankEntity.Value, tank));
|
_gasTank.DisconnectFromInternals((ent.Comp.GasTankEntity.Value, tank));
|
||||||
|
|
||||||
component.GasTankEntity = tankEntity;
|
ent.Comp.GasTankEntity = tankEntity;
|
||||||
_alerts.ShowAlert(ent, AlertType.Internals, GetSeverity(component));
|
_alerts.ShowAlert(ent, AlertType.Internals, GetSeverity(ent));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool AreInternalsWorking(EntityUid uid, InternalsComponent? component = null)
|
public bool AreInternalsWorking(EntityUid uid, InternalsComponent? component = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(uid, ref component, false))
|
return Resolve(uid, ref component, logMissing: false)
|
||||||
return false;
|
&& AreInternalsWorking(component);
|
||||||
|
|
||||||
return AreInternalsWorking(component);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool AreInternalsWorking(InternalsComponent component)
|
public bool AreInternalsWorking(InternalsComponent component)
|
||||||
{
|
{
|
||||||
return TryComp(component.BreathToolEntity, out BreathToolComponent? breathTool) &&
|
return TryComp(component.BreathToolEntity, out BreathToolComponent? breathTool)
|
||||||
breathTool.IsFunctional &&
|
&& breathTool.IsFunctional
|
||||||
TryComp(component.GasTankEntity, out GasTankComponent? _);
|
&& HasComp<GasTankComponent>(component.GasTankEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
private short GetSeverity(InternalsComponent component)
|
private short GetSeverity(InternalsComponent component)
|
||||||
{
|
{
|
||||||
if (component.BreathToolEntity == null || !AreInternalsWorking(component))
|
if (component.BreathToolEntity is null || !AreInternalsWorking(component))
|
||||||
return 2;
|
return 2;
|
||||||
|
|
||||||
// If pressure in the tank is below low pressure threshhold, flash warning on internals UI
|
// If pressure in the tank is below low pressure threshhold, flash warning on internals UI
|
||||||
if (TryComp<GasTankComponent>(component.GasTankEntity, out var gasTank) && gasTank.IsLowPressure)
|
if (TryComp<GasTankComponent>(component.GasTankEntity, out var gasTank)
|
||||||
|
&& gasTank.IsLowPressure)
|
||||||
|
{
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Entity<GasTankComponent>? FindBestGasTank(Entity<HandsComponent?, InventoryComponent?, ContainerManagerComponent?> user)
|
public Entity<GasTankComponent>? FindBestGasTank(
|
||||||
|
Entity<HandsComponent?, InventoryComponent?, ContainerManagerComponent?> user)
|
||||||
{
|
{
|
||||||
// Prioritise
|
// Prioritise
|
||||||
// 1. back equipped tanks
|
// 1. back equipped tanks
|
||||||
@@ -233,17 +238,17 @@ public sealed class InternalsSystem : EntitySystem
|
|||||||
// 3. in-hand tanks
|
// 3. in-hand tanks
|
||||||
// 4. pocket/belt tanks
|
// 4. pocket/belt tanks
|
||||||
|
|
||||||
if (!Resolve(user.Owner, ref user.Comp1, ref user.Comp2, ref user.Comp3))
|
if (!Resolve(user, ref user.Comp1, ref user.Comp2, ref user.Comp3))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (_inventory.TryGetSlotEntity(user.Owner, "back", out var backEntity, user.Comp2, user.Comp3) &&
|
if (_inventory.TryGetSlotEntity(user, "back", out var backEntity, user.Comp2, user.Comp3) &&
|
||||||
TryComp<GasTankComponent>(backEntity, out var backGasTank) &&
|
TryComp<GasTankComponent>(backEntity, out var backGasTank) &&
|
||||||
_gasTank.CanConnectToInternals(backGasTank))
|
_gasTank.CanConnectToInternals(backGasTank))
|
||||||
{
|
{
|
||||||
return (backEntity.Value, backGasTank);
|
return (backEntity.Value, backGasTank);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_inventory.TryGetSlotEntity(user.Owner, "suitstorage", out var entity, user.Comp2, user.Comp3) &&
|
if (_inventory.TryGetSlotEntity(user, "suitstorage", out var entity, user.Comp2, user.Comp3) &&
|
||||||
TryComp<GasTankComponent>(entity, out var gasTank) &&
|
TryComp<GasTankComponent>(entity, out var gasTank) &&
|
||||||
_gasTank.CanConnectToInternals(gasTank))
|
_gasTank.CanConnectToInternals(gasTank))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Content.Server.Atmos.Components;
|
using Content.Server.Atmos.Components;
|
||||||
using Content.Server.Atmos.EntitySystems;
|
using Content.Server.Atmos.EntitySystems;
|
||||||
using Content.Server.Body.Components;
|
using Content.Server.Body.Components;
|
||||||
using Content.Server.Chemistry.Containers.EntitySystems;
|
using Content.Server.Chemistry.Containers.EntitySystems;
|
||||||
@@ -26,21 +26,24 @@ public sealed class LungSystem : EntitySystem
|
|||||||
SubscribeLocalEvent<BreathToolComponent, ItemMaskToggledEvent>(OnMaskToggled);
|
SubscribeLocalEvent<BreathToolComponent, ItemMaskToggledEvent>(OnMaskToggled);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnGotUnequipped(EntityUid uid, BreathToolComponent component, GotUnequippedEvent args)
|
private void OnGotUnequipped(Entity<BreathToolComponent> ent, ref GotUnequippedEvent args)
|
||||||
{
|
{
|
||||||
_atmosphereSystem.DisconnectInternals(component);
|
_atmosphereSystem.DisconnectInternals(ent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnGotEquipped(EntityUid uid, BreathToolComponent component, GotEquippedEvent args)
|
private void OnGotEquipped(Entity<BreathToolComponent> ent, ref GotEquippedEvent args)
|
||||||
{
|
{
|
||||||
|
if ((args.SlotFlags & ent.Comp.AllowedSlots) == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if ((args.SlotFlags & component.AllowedSlots) == 0) return;
|
ent.Comp.IsFunctional = true;
|
||||||
component.IsFunctional = true;
|
|
||||||
|
|
||||||
if (TryComp(args.Equipee, out InternalsComponent? internals))
|
if (TryComp(args.Equipee, out InternalsComponent? internals))
|
||||||
{
|
{
|
||||||
component.ConnectedInternalsEntity = args.Equipee;
|
ent.Comp.ConnectedInternalsEntity = args.Equipee;
|
||||||
_internals.ConnectBreathTool((args.Equipee, internals), uid);
|
_internals.ConnectBreathTool((args.Equipee, internals), ent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +84,7 @@ public sealed class LungSystem : EntitySystem
|
|||||||
if (moles <= 0)
|
if (moles <= 0)
|
||||||
continue;
|
continue;
|
||||||
var reagent = _atmosphereSystem.GasReagents[i];
|
var reagent = _atmosphereSystem.GasReagents[i];
|
||||||
if (reagent == null) continue;
|
if (reagent is null) continue;
|
||||||
|
|
||||||
var amount = moles * Atmospherics.BreathMolesToReagentMultiplier;
|
var amount = moles * Atmospherics.BreathMolesToReagentMultiplier;
|
||||||
solution.AddReagent(reagent, amount);
|
solution.AddReagent(reagent, amount);
|
||||||
|
|||||||
@@ -12,11 +12,13 @@ using Content.Shared.Mobs.Systems;
|
|||||||
using Robust.Shared.Collections;
|
using Robust.Shared.Collections;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.Server.Body.Systems
|
namespace Content.Server.Body.Systems
|
||||||
{
|
{
|
||||||
public sealed class MetabolizerSystem : EntitySystem
|
public sealed class MetabolizerSystem : EntitySystem
|
||||||
{
|
{
|
||||||
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||||
@@ -34,9 +36,21 @@ namespace Content.Server.Body.Systems
|
|||||||
_solutionQuery = GetEntityQuery<SolutionContainerManagerComponent>();
|
_solutionQuery = GetEntityQuery<SolutionContainerManagerComponent>();
|
||||||
|
|
||||||
SubscribeLocalEvent<MetabolizerComponent, ComponentInit>(OnMetabolizerInit);
|
SubscribeLocalEvent<MetabolizerComponent, ComponentInit>(OnMetabolizerInit);
|
||||||
|
SubscribeLocalEvent<MetabolizerComponent, MapInitEvent>(OnMapInit);
|
||||||
|
SubscribeLocalEvent<MetabolizerComponent, EntityUnpausedEvent>(OnUnpaused);
|
||||||
SubscribeLocalEvent<MetabolizerComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
|
SubscribeLocalEvent<MetabolizerComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnMapInit(Entity<MetabolizerComponent> ent, ref MapInitEvent args)
|
||||||
|
{
|
||||||
|
ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.UpdateInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUnpaused(Entity<MetabolizerComponent> ent, ref EntityUnpausedEvent args)
|
||||||
|
{
|
||||||
|
ent.Comp.NextUpdate += args.PausedTime;
|
||||||
|
}
|
||||||
|
|
||||||
private void OnMetabolizerInit(Entity<MetabolizerComponent> entity, ref ComponentInit args)
|
private void OnMetabolizerInit(Entity<MetabolizerComponent> entity, ref ComponentInit args)
|
||||||
{
|
{
|
||||||
if (!entity.Comp.SolutionOnBody)
|
if (!entity.Comp.SolutionOnBody)
|
||||||
@@ -49,19 +63,17 @@ namespace Content.Server.Body.Systems
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnApplyMetabolicMultiplier(EntityUid uid, MetabolizerComponent component,
|
private void OnApplyMetabolicMultiplier(
|
||||||
ApplyMetabolicMultiplierEvent args)
|
Entity<MetabolizerComponent> ent,
|
||||||
|
ref ApplyMetabolicMultiplierEvent args)
|
||||||
{
|
{
|
||||||
if (args.Apply)
|
if (args.Apply)
|
||||||
{
|
{
|
||||||
component.UpdateFrequency *= args.Multiplier;
|
ent.Comp.UpdateInterval *= args.Multiplier;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
component.UpdateFrequency /= args.Multiplier;
|
ent.Comp.UpdateInterval /= args.Multiplier;
|
||||||
// Reset the accumulator properly
|
|
||||||
if (component.AccumulatedFrametime >= component.UpdateFrequency)
|
|
||||||
component.AccumulatedFrametime = component.UpdateFrequency;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
public override void Update(float frameTime)
|
||||||
@@ -78,50 +90,52 @@ namespace Content.Server.Body.Systems
|
|||||||
|
|
||||||
foreach (var (uid, metab) in metabolizers)
|
foreach (var (uid, metab) in metabolizers)
|
||||||
{
|
{
|
||||||
metab.AccumulatedFrametime += frameTime;
|
|
||||||
|
|
||||||
// Only update as frequently as it should
|
// Only update as frequently as it should
|
||||||
if (metab.AccumulatedFrametime < metab.UpdateFrequency)
|
if (_gameTiming.CurTime < metab.NextUpdate)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
metab.AccumulatedFrametime -= metab.UpdateFrequency;
|
metab.NextUpdate += metab.UpdateInterval;
|
||||||
TryMetabolize(uid, metab);
|
TryMetabolize((uid, metab));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TryMetabolize(EntityUid uid, MetabolizerComponent meta, OrganComponent? organ = null)
|
private void TryMetabolize(Entity<MetabolizerComponent, OrganComponent?, SolutionContainerManagerComponent?> ent)
|
||||||
{
|
{
|
||||||
_organQuery.Resolve(uid, ref organ, false);
|
_organQuery.Resolve(ent, ref ent.Comp2, logMissing: false);
|
||||||
|
|
||||||
// First step is get the solution we actually care about
|
// First step is get the solution we actually care about
|
||||||
|
var solutionName = ent.Comp1.SolutionName;
|
||||||
Solution? solution = null;
|
Solution? solution = null;
|
||||||
Entity<SolutionComponent>? soln = default!;
|
Entity<SolutionComponent>? soln = default!;
|
||||||
EntityUid? solutionEntityUid = null;
|
EntityUid? solutionEntityUid = null;
|
||||||
|
|
||||||
SolutionContainerManagerComponent? manager = null;
|
if (ent.Comp1.SolutionOnBody)
|
||||||
|
|
||||||
if (meta.SolutionOnBody)
|
|
||||||
{
|
{
|
||||||
if (organ?.Body is { } body)
|
if (ent.Comp2?.Body is { } body)
|
||||||
{
|
{
|
||||||
if (!_solutionQuery.Resolve(body, ref manager, false))
|
if (!_solutionQuery.Resolve(body, ref ent.Comp3, logMissing: false))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_solutionContainerSystem.TryGetSolution((body, manager), meta.SolutionName, out soln, out solution);
|
_solutionContainerSystem.TryGetSolution((body, ent.Comp3), solutionName, out soln, out solution);
|
||||||
solutionEntityUid = body;
|
solutionEntityUid = body;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!_solutionQuery.Resolve(uid, ref manager, false))
|
if (!_solutionQuery.Resolve(ent, ref ent.Comp3, logMissing: false))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_solutionContainerSystem.TryGetSolution((uid, manager), meta.SolutionName, out soln, out solution);
|
_solutionContainerSystem.TryGetSolution((ent, ent), solutionName, out soln, out solution);
|
||||||
solutionEntityUid = uid;
|
solutionEntityUid = ent;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (solutionEntityUid == null || soln is null || solution is null || solution.Contents.Count == 0)
|
if (solutionEntityUid is null
|
||||||
|
|| soln is null
|
||||||
|
|| solution is null
|
||||||
|
|| solution.Contents.Count == 0)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// randomize the reagent list so we don't have any weird quirks
|
// randomize the reagent list so we don't have any weird quirks
|
||||||
// like alphabetical order or insertion order mattering for processing
|
// like alphabetical order or insertion order mattering for processing
|
||||||
@@ -135,9 +149,9 @@ namespace Content.Server.Body.Systems
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
var mostToRemove = FixedPoint2.Zero;
|
var mostToRemove = FixedPoint2.Zero;
|
||||||
if (proto.Metabolisms == null)
|
if (proto.Metabolisms is null)
|
||||||
{
|
{
|
||||||
if (meta.RemoveEmpty)
|
if (ent.Comp1.RemoveEmpty)
|
||||||
{
|
{
|
||||||
solution.RemoveReagent(reagent, FixedPoint2.New(1));
|
solution.RemoveReagent(reagent, FixedPoint2.New(1));
|
||||||
}
|
}
|
||||||
@@ -146,15 +160,15 @@ namespace Content.Server.Body.Systems
|
|||||||
}
|
}
|
||||||
|
|
||||||
// we're done here entirely if this is true
|
// we're done here entirely if this is true
|
||||||
if (reagents >= meta.MaxReagentsProcessable)
|
if (reagents >= ent.Comp1.MaxReagentsProcessable)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
||||||
// loop over all our groups and see which ones apply
|
// loop over all our groups and see which ones apply
|
||||||
if (meta.MetabolismGroups == null)
|
if (ent.Comp1.MetabolismGroups is null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
foreach (var group in meta.MetabolismGroups)
|
foreach (var group in ent.Comp1.MetabolismGroups)
|
||||||
{
|
{
|
||||||
if (!proto.Metabolisms.TryGetValue(group.Id, out var entry))
|
if (!proto.Metabolisms.TryGetValue(group.Id, out var entry))
|
||||||
continue;
|
continue;
|
||||||
@@ -169,14 +183,14 @@ namespace Content.Server.Body.Systems
|
|||||||
// if it's possible for them to be dead, and they are,
|
// if it's possible for them to be dead, and they are,
|
||||||
// then we shouldn't process any effects, but should probably
|
// then we shouldn't process any effects, but should probably
|
||||||
// still remove reagents
|
// still remove reagents
|
||||||
if (EntityManager.TryGetComponent<MobStateComponent>(solutionEntityUid.Value, out var state))
|
if (TryComp<MobStateComponent>(solutionEntityUid.Value, out var state))
|
||||||
{
|
{
|
||||||
if (!proto.WorksOnTheDead && _mobStateSystem.IsDead(solutionEntityUid.Value, state))
|
if (!proto.WorksOnTheDead && _mobStateSystem.IsDead(solutionEntityUid.Value, state))
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var actualEntity = organ?.Body ?? solutionEntityUid.Value;
|
var actualEntity = ent.Comp2?.Body ?? solutionEntityUid.Value;
|
||||||
var args = new ReagentEffectArgs(actualEntity, uid, solution, proto, mostToRemove,
|
var args = new ReagentEffectArgs(actualEntity, ent, solution, proto, mostToRemove,
|
||||||
EntityManager, null, scale);
|
EntityManager, null, scale);
|
||||||
|
|
||||||
// do all effects, if conditions apply
|
// do all effects, if conditions apply
|
||||||
@@ -187,8 +201,14 @@ namespace Content.Server.Body.Systems
|
|||||||
|
|
||||||
if (effect.ShouldLog)
|
if (effect.ShouldLog)
|
||||||
{
|
{
|
||||||
_adminLogger.Add(LogType.ReagentEffect, effect.LogImpact,
|
_adminLogger.Add(
|
||||||
$"Metabolism effect {effect.GetType().Name:effect} of reagent {proto.LocalizedName:reagent} applied on entity {actualEntity:entity} at {Transform(actualEntity).Coordinates:coordinates}");
|
LogType.ReagentEffect,
|
||||||
|
effect.LogImpact,
|
||||||
|
$"Metabolism effect {effect.GetType().Name:effect}"
|
||||||
|
+ $" of reagent {proto.LocalizedName:reagent}"
|
||||||
|
+ $" applied on entity {actualEntity:entity}"
|
||||||
|
+ $" at {Transform(actualEntity).Coordinates:coordinates}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
effect.Effect(args);
|
effect.Effect(args);
|
||||||
@@ -209,15 +229,25 @@ namespace Content.Server.Body.Systems
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ApplyMetabolicMultiplierEvent : EntityEventArgs
|
[ByRefEvent]
|
||||||
|
public readonly record struct ApplyMetabolicMultiplierEvent(
|
||||||
|
EntityUid Uid,
|
||||||
|
float Multiplier,
|
||||||
|
bool Apply)
|
||||||
{
|
{
|
||||||
// The entity whose metabolism is being modified
|
/// <summary>
|
||||||
public EntityUid Uid;
|
/// The entity whose metabolism is being modified.
|
||||||
|
/// </summary>
|
||||||
|
public readonly EntityUid Uid = Uid;
|
||||||
|
|
||||||
// What the metabolism's update rate will be multiplied by
|
/// <summary>
|
||||||
public float Multiplier;
|
/// What the metabolism's update rate will be multiplied by.
|
||||||
|
/// </summary>
|
||||||
|
public readonly float Multiplier = Multiplier;
|
||||||
|
|
||||||
// Apply this multiplier or ignore / reset it?
|
/// <summary>
|
||||||
public bool Apply;
|
/// If true, apply the multiplier. If false, revert it.
|
||||||
|
/// </summary>
|
||||||
|
public readonly bool Apply = Apply;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,9 +35,21 @@ public sealed class RespiratorSystem : EntitySystem
|
|||||||
|
|
||||||
// We want to process lung reagents before we inhale new reagents.
|
// We want to process lung reagents before we inhale new reagents.
|
||||||
UpdatesAfter.Add(typeof(MetabolizerSystem));
|
UpdatesAfter.Add(typeof(MetabolizerSystem));
|
||||||
|
SubscribeLocalEvent<RespiratorComponent, MapInitEvent>(OnMapInit);
|
||||||
|
SubscribeLocalEvent<RespiratorComponent, EntityUnpausedEvent>(OnUnpaused);
|
||||||
SubscribeLocalEvent<RespiratorComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
|
SubscribeLocalEvent<RespiratorComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnMapInit(Entity<RespiratorComponent> ent, ref MapInitEvent args)
|
||||||
|
{
|
||||||
|
ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.UpdateInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUnpaused(Entity<RespiratorComponent> ent, ref EntityUnpausedEvent args)
|
||||||
|
{
|
||||||
|
ent.Comp.NextUpdate += args.PausedTime;
|
||||||
|
}
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
public override void Update(float frameTime)
|
||||||
{
|
{
|
||||||
base.Update(frameTime);
|
base.Update(frameTime);
|
||||||
@@ -45,17 +57,15 @@ public sealed class RespiratorSystem : EntitySystem
|
|||||||
var query = EntityQueryEnumerator<RespiratorComponent, BodyComponent>();
|
var query = EntityQueryEnumerator<RespiratorComponent, BodyComponent>();
|
||||||
while (query.MoveNext(out var uid, out var respirator, out var body))
|
while (query.MoveNext(out var uid, out var respirator, out var body))
|
||||||
{
|
{
|
||||||
|
if (_gameTiming.CurTime < respirator.NextUpdate)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
respirator.NextUpdate += respirator.UpdateInterval;
|
||||||
|
|
||||||
if (_mobState.IsDead(uid))
|
if (_mobState.IsDead(uid))
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
respirator.AccumulatedFrametime += frameTime;
|
UpdateSaturation(uid, -(float) respirator.UpdateInterval.TotalSeconds, respirator);
|
||||||
|
|
||||||
if (respirator.AccumulatedFrametime < respirator.CycleDelay)
|
|
||||||
continue;
|
|
||||||
respirator.AccumulatedFrametime -= respirator.CycleDelay;
|
|
||||||
UpdateSaturation(uid, -respirator.CycleDelay, respirator);
|
|
||||||
|
|
||||||
if (!_mobState.IsIncapacitated(uid)) // cannot breathe in crit.
|
if (!_mobState.IsIncapacitated(uid)) // cannot breathe in crit.
|
||||||
{
|
{
|
||||||
@@ -80,30 +90,30 @@ public sealed class RespiratorSystem : EntitySystem
|
|||||||
_popupSystem.PopupEntity(Loc.GetString("lung-behavior-gasp"), uid);
|
_popupSystem.PopupEntity(Loc.GetString("lung-behavior-gasp"), uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
TakeSuffocationDamage(uid, respirator);
|
TakeSuffocationDamage((uid, respirator));
|
||||||
respirator.SuffocationCycles += 1;
|
respirator.SuffocationCycles += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
StopSuffocation(uid, respirator);
|
StopSuffocation((uid, respirator));
|
||||||
respirator.SuffocationCycles = 0;
|
respirator.SuffocationCycles = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Inhale(EntityUid uid, BodyComponent? body = null)
|
public void Inhale(EntityUid uid, BodyComponent? body = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(uid, ref body, false))
|
if (!Resolve(uid, ref body, logMissing: false))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var organs = _bodySystem.GetBodyOrganComponents<LungComponent>(uid, body);
|
var organs = _bodySystem.GetBodyOrganComponents<LungComponent>(uid, body);
|
||||||
|
|
||||||
// Inhale gas
|
// Inhale gas
|
||||||
var ev = new InhaleLocationEvent();
|
var ev = new InhaleLocationEvent();
|
||||||
RaiseLocalEvent(uid, ev);
|
RaiseLocalEvent(uid, ref ev, broadcast: false);
|
||||||
|
|
||||||
ev.Gas ??= _atmosSys.GetContainingMixture(uid, false, true);
|
ev.Gas ??= _atmosSys.GetContainingMixture(uid, excite: true);
|
||||||
|
|
||||||
if (ev.Gas == null)
|
if (ev.Gas is null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -122,7 +132,7 @@ public sealed class RespiratorSystem : EntitySystem
|
|||||||
|
|
||||||
public void Exhale(EntityUid uid, BodyComponent? body = null)
|
public void Exhale(EntityUid uid, BodyComponent? body = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(uid, ref body, false))
|
if (!Resolve(uid, ref body, logMissing: false))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var organs = _bodySystem.GetBodyOrganComponents<LungComponent>(uid, body);
|
var organs = _bodySystem.GetBodyOrganComponents<LungComponent>(uid, body);
|
||||||
@@ -130,11 +140,11 @@ public sealed class RespiratorSystem : EntitySystem
|
|||||||
// exhale gas
|
// exhale gas
|
||||||
|
|
||||||
var ev = new ExhaleLocationEvent();
|
var ev = new ExhaleLocationEvent();
|
||||||
RaiseLocalEvent(uid, ev, false);
|
RaiseLocalEvent(uid, ref ev, broadcast: false);
|
||||||
|
|
||||||
if (ev.Gas == null)
|
if (ev.Gas is null)
|
||||||
{
|
{
|
||||||
ev.Gas = _atmosSys.GetContainingMixture(uid, false, true);
|
ev.Gas = _atmosSys.GetContainingMixture(uid, excite: true);
|
||||||
|
|
||||||
// Walls and grids without atmos comp return null. I guess it makes sense to not be able to exhale in walls,
|
// Walls and grids without atmos comp return null. I guess it makes sense to not be able to exhale in walls,
|
||||||
// but this also means you cannot exhale on some grids.
|
// but this also means you cannot exhale on some grids.
|
||||||
@@ -154,37 +164,37 @@ public sealed class RespiratorSystem : EntitySystem
|
|||||||
_atmosSys.Merge(ev.Gas, outGas);
|
_atmosSys.Merge(ev.Gas, outGas);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TakeSuffocationDamage(EntityUid uid, RespiratorComponent respirator)
|
private void TakeSuffocationDamage(Entity<RespiratorComponent> ent)
|
||||||
{
|
{
|
||||||
if (respirator.SuffocationCycles == 2)
|
if (ent.Comp.SuffocationCycles == 2)
|
||||||
_adminLogger.Add(LogType.Asphyxiation, $"{ToPrettyString(uid):entity} started suffocating");
|
_adminLogger.Add(LogType.Asphyxiation, $"{ToPrettyString(ent):entity} started suffocating");
|
||||||
|
|
||||||
if (respirator.SuffocationCycles >= respirator.SuffocationCycleThreshold)
|
if (ent.Comp.SuffocationCycles >= ent.Comp.SuffocationCycleThreshold)
|
||||||
{
|
{
|
||||||
// TODO: This is not going work with multiple different lungs, if that ever becomes a possibility
|
// TODO: This is not going work with multiple different lungs, if that ever becomes a possibility
|
||||||
var organs = _bodySystem.GetBodyOrganComponents<LungComponent>(uid);
|
var organs = _bodySystem.GetBodyOrganComponents<LungComponent>(ent);
|
||||||
foreach (var (comp, _) in organs)
|
foreach (var (comp, _) in organs)
|
||||||
{
|
{
|
||||||
_alertsSystem.ShowAlert(uid, comp.Alert);
|
_alertsSystem.ShowAlert(ent, comp.Alert);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_damageableSys.TryChangeDamage(uid, respirator.Damage, false, false);
|
_damageableSys.TryChangeDamage(ent, ent.Comp.Damage, interruptsDoAfters: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void StopSuffocation(EntityUid uid, RespiratorComponent respirator)
|
private void StopSuffocation(Entity<RespiratorComponent> ent)
|
||||||
{
|
{
|
||||||
if (respirator.SuffocationCycles >= 2)
|
if (ent.Comp.SuffocationCycles >= 2)
|
||||||
_adminLogger.Add(LogType.Asphyxiation, $"{ToPrettyString(uid):entity} stopped suffocating");
|
_adminLogger.Add(LogType.Asphyxiation, $"{ToPrettyString(ent):entity} stopped suffocating");
|
||||||
|
|
||||||
// TODO: This is not going work with multiple different lungs, if that ever becomes a possibility
|
// TODO: This is not going work with multiple different lungs, if that ever becomes a possibility
|
||||||
var organs = _bodySystem.GetBodyOrganComponents<LungComponent>(uid);
|
var organs = _bodySystem.GetBodyOrganComponents<LungComponent>(ent);
|
||||||
foreach (var (comp, _) in organs)
|
foreach (var (comp, _) in organs)
|
||||||
{
|
{
|
||||||
_alertsSystem.ClearAlert(uid, comp.Alert);
|
_alertsSystem.ClearAlert(ent, comp.Alert);
|
||||||
}
|
}
|
||||||
|
|
||||||
_damageableSys.TryChangeDamage(uid, respirator.DamageRecovery);
|
_damageableSys.TryChangeDamage(ent, ent.Comp.DamageRecovery);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateSaturation(EntityUid uid, float amount,
|
public void UpdateSaturation(EntityUid uid, float amount,
|
||||||
@@ -198,35 +208,29 @@ public sealed class RespiratorSystem : EntitySystem
|
|||||||
Math.Clamp(respirator.Saturation, respirator.MinSaturation, respirator.MaxSaturation);
|
Math.Clamp(respirator.Saturation, respirator.MinSaturation, respirator.MaxSaturation);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnApplyMetabolicMultiplier(EntityUid uid, RespiratorComponent component,
|
private void OnApplyMetabolicMultiplier(
|
||||||
ApplyMetabolicMultiplierEvent args)
|
Entity<RespiratorComponent> ent,
|
||||||
|
ref ApplyMetabolicMultiplierEvent args)
|
||||||
{
|
{
|
||||||
if (args.Apply)
|
if (args.Apply)
|
||||||
{
|
{
|
||||||
component.CycleDelay *= args.Multiplier;
|
ent.Comp.UpdateInterval *= args.Multiplier;
|
||||||
component.Saturation *= args.Multiplier;
|
ent.Comp.Saturation *= args.Multiplier;
|
||||||
component.MaxSaturation *= args.Multiplier;
|
ent.Comp.MaxSaturation *= args.Multiplier;
|
||||||
component.MinSaturation *= args.Multiplier;
|
ent.Comp.MinSaturation *= args.Multiplier;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This way we don't have to worry about it breaking if the stasis bed component is destroyed
|
// This way we don't have to worry about it breaking if the stasis bed component is destroyed
|
||||||
component.CycleDelay /= args.Multiplier;
|
ent.Comp.UpdateInterval /= args.Multiplier;
|
||||||
component.Saturation /= args.Multiplier;
|
ent.Comp.Saturation /= args.Multiplier;
|
||||||
component.MaxSaturation /= args.Multiplier;
|
ent.Comp.MaxSaturation /= args.Multiplier;
|
||||||
component.MinSaturation /= args.Multiplier;
|
ent.Comp.MinSaturation /= args.Multiplier;
|
||||||
// Reset the accumulator properly
|
|
||||||
if (component.AccumulatedFrametime >= component.CycleDelay)
|
|
||||||
component.AccumulatedFrametime = component.CycleDelay;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class InhaleLocationEvent : EntityEventArgs
|
[ByRefEvent]
|
||||||
{
|
public record struct InhaleLocationEvent(GasMixture? Gas);
|
||||||
public GasMixture? Gas;
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class ExhaleLocationEvent : EntityEventArgs
|
[ByRefEvent]
|
||||||
{
|
public record struct ExhaleLocationEvent(GasMixture? Gas);
|
||||||
public GasMixture? Gas;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,32 +3,44 @@ using Content.Server.Chemistry.Containers.EntitySystems;
|
|||||||
using Content.Shared.Body.Organ;
|
using Content.Shared.Body.Organ;
|
||||||
using Content.Shared.Chemistry.Components;
|
using Content.Shared.Chemistry.Components;
|
||||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Server.Body.Systems
|
namespace Content.Server.Body.Systems
|
||||||
{
|
{
|
||||||
public sealed class StomachSystem : EntitySystem
|
public sealed class StomachSystem : EntitySystem
|
||||||
{
|
{
|
||||||
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
|
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
|
||||||
|
|
||||||
public const string DefaultSolutionName = "stomach";
|
public const string DefaultSolutionName = "stomach";
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
|
SubscribeLocalEvent<StomachComponent, MapInitEvent>(OnMapInit);
|
||||||
|
SubscribeLocalEvent<StomachComponent, EntityUnpausedEvent>(OnUnpaused);
|
||||||
SubscribeLocalEvent<StomachComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
|
SubscribeLocalEvent<StomachComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnMapInit(Entity<StomachComponent> ent, ref MapInitEvent args)
|
||||||
|
{
|
||||||
|
ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.UpdateInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUnpaused(Entity<StomachComponent> ent, ref EntityUnpausedEvent args)
|
||||||
|
{
|
||||||
|
ent.Comp.NextUpdate += args.PausedTime;
|
||||||
|
}
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
public override void Update(float frameTime)
|
||||||
{
|
{
|
||||||
var query = EntityQueryEnumerator<StomachComponent, OrganComponent, SolutionContainerManagerComponent>();
|
var query = EntityQueryEnumerator<StomachComponent, OrganComponent, SolutionContainerManagerComponent>();
|
||||||
while (query.MoveNext(out var uid, out var stomach, out var organ, out var sol))
|
while (query.MoveNext(out var uid, out var stomach, out var organ, out var sol))
|
||||||
{
|
{
|
||||||
stomach.AccumulatedFrameTime += frameTime;
|
if (_gameTiming.CurTime < stomach.NextUpdate)
|
||||||
|
|
||||||
if (stomach.AccumulatedFrameTime < stomach.UpdateInterval)
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
stomach.AccumulatedFrameTime -= stomach.UpdateInterval;
|
stomach.NextUpdate += stomach.UpdateInterval;
|
||||||
|
|
||||||
// Get our solutions
|
// Get our solutions
|
||||||
if (!_solutionContainerSystem.ResolveSolution((uid, sol), DefaultSolutionName, ref stomach.Solution, out var stomachSolution))
|
if (!_solutionContainerSystem.ResolveSolution((uid, sol), DefaultSolutionName, ref stomach.Solution, out var stomachSolution))
|
||||||
@@ -70,49 +82,44 @@ namespace Content.Server.Body.Systems
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnApplyMetabolicMultiplier(EntityUid uid, StomachComponent component,
|
private void OnApplyMetabolicMultiplier(
|
||||||
ApplyMetabolicMultiplierEvent args)
|
Entity<StomachComponent> ent,
|
||||||
|
ref ApplyMetabolicMultiplierEvent args)
|
||||||
{
|
{
|
||||||
if (args.Apply)
|
if (args.Apply)
|
||||||
{
|
{
|
||||||
component.UpdateInterval *= args.Multiplier;
|
ent.Comp.UpdateInterval *= args.Multiplier;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This way we don't have to worry about it breaking if the stasis bed component is destroyed
|
// This way we don't have to worry about it breaking if the stasis bed component is destroyed
|
||||||
component.UpdateInterval /= args.Multiplier;
|
ent.Comp.UpdateInterval /= args.Multiplier;
|
||||||
// Reset the accumulator properly
|
|
||||||
if (component.AccumulatedFrameTime >= component.UpdateInterval)
|
|
||||||
component.AccumulatedFrameTime = component.UpdateInterval;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CanTransferSolution(EntityUid uid, Solution solution,
|
public bool CanTransferSolution(
|
||||||
|
EntityUid uid,
|
||||||
|
Solution solution,
|
||||||
StomachComponent? stomach = null,
|
StomachComponent? stomach = null,
|
||||||
SolutionContainerManagerComponent? solutions = null)
|
SolutionContainerManagerComponent? solutions = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(uid, ref stomach, ref solutions, false))
|
return Resolve(uid, ref stomach, ref solutions, logMissing: false)
|
||||||
return false;
|
&& _solutionContainerSystem.ResolveSolution((uid, solutions), DefaultSolutionName, ref stomach.Solution, out var stomachSolution)
|
||||||
|
|
||||||
if (!_solutionContainerSystem.ResolveSolution((uid, solutions), DefaultSolutionName, ref stomach.Solution, out var stomachSolution))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// TODO: For now no partial transfers. Potentially change by design
|
// TODO: For now no partial transfers. Potentially change by design
|
||||||
if (!stomachSolution.CanAddSolution(solution))
|
&& stomachSolution.CanAddSolution(solution);
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryTransferSolution(EntityUid uid, Solution solution,
|
public bool TryTransferSolution(
|
||||||
|
EntityUid uid,
|
||||||
|
Solution solution,
|
||||||
StomachComponent? stomach = null,
|
StomachComponent? stomach = null,
|
||||||
SolutionContainerManagerComponent? solutions = null)
|
SolutionContainerManagerComponent? solutions = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(uid, ref stomach, ref solutions, false))
|
if (!Resolve(uid, ref stomach, ref solutions, logMissing: false)
|
||||||
return false;
|
|| !_solutionContainerSystem.ResolveSolution((uid, solutions), DefaultSolutionName, ref stomach.Solution)
|
||||||
|
|
||||||
if (!_solutionContainerSystem.ResolveSolution((uid, solutions), DefaultSolutionName, ref stomach.Solution)
|
|
||||||
|| !CanTransferSolution(uid, solution, stomach, solutions))
|
|| !CanTransferSolution(uid, solution, stomach, solutions))
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
_solutionContainerSystem.TryAddSolution(stomach.Solution.Value, solution);
|
_solutionContainerSystem.TryAddSolution(stomach.Solution.Value, solution);
|
||||||
// Add each reagent to ReagentDeltas. Used to track how long each reagent has been in the stomach
|
// Add each reagent to ReagentDeltas. Used to track how long each reagent has been in the stomach
|
||||||
|
|||||||
@@ -1,73 +1,95 @@
|
|||||||
using Content.Server.Body.Components;
|
using Content.Server.Body.Components;
|
||||||
using Content.Server.Temperature.Components;
|
using Content.Server.Temperature.Components;
|
||||||
using Content.Server.Temperature.Systems;
|
using Content.Server.Temperature.Systems;
|
||||||
using Content.Shared.ActionBlocker;
|
using Content.Shared.ActionBlocker;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.Server.Body.Systems;
|
namespace Content.Server.Body.Systems;
|
||||||
|
|
||||||
public sealed class ThermalRegulatorSystem : EntitySystem
|
public sealed class ThermalRegulatorSystem : EntitySystem
|
||||||
{
|
{
|
||||||
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
[Dependency] private readonly TemperatureSystem _tempSys = default!;
|
[Dependency] private readonly TemperatureSystem _tempSys = default!;
|
||||||
[Dependency] private readonly ActionBlockerSystem _actionBlockerSys = default!;
|
[Dependency] private readonly ActionBlockerSystem _actionBlockerSys = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<ThermalRegulatorComponent, MapInitEvent>(OnMapInit);
|
||||||
|
SubscribeLocalEvent<ThermalRegulatorComponent, EntityUnpausedEvent>(OnUnpaused);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMapInit(Entity<ThermalRegulatorComponent> ent, ref MapInitEvent args)
|
||||||
|
{
|
||||||
|
ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.UpdateInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUnpaused(Entity<ThermalRegulatorComponent> ent, ref EntityUnpausedEvent args)
|
||||||
|
{
|
||||||
|
ent.Comp.NextUpdate += args.PausedTime;
|
||||||
|
}
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
public override void Update(float frameTime)
|
||||||
{
|
{
|
||||||
var query = EntityQueryEnumerator<ThermalRegulatorComponent>();
|
var query = EntityQueryEnumerator<ThermalRegulatorComponent>();
|
||||||
while (query.MoveNext(out var uid, out var regulator))
|
while (query.MoveNext(out var uid, out var regulator))
|
||||||
{
|
{
|
||||||
regulator.AccumulatedFrametime += frameTime;
|
if (_gameTiming.CurTime < regulator.NextUpdate)
|
||||||
if (regulator.AccumulatedFrametime < 1)
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
regulator.AccumulatedFrametime -= 1;
|
regulator.NextUpdate += regulator.UpdateInterval;
|
||||||
ProcessThermalRegulation(uid, regulator);
|
ProcessThermalRegulation((uid, regulator));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processes thermal regulation for a mob
|
/// Processes thermal regulation for a mob
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void ProcessThermalRegulation(EntityUid uid, ThermalRegulatorComponent comp)
|
private void ProcessThermalRegulation(Entity<ThermalRegulatorComponent, TemperatureComponent?> ent)
|
||||||
{
|
{
|
||||||
if (!EntityManager.TryGetComponent(uid, out TemperatureComponent? temperatureComponent)) return;
|
if (!Resolve(ent, ref ent.Comp2, logMissing: false))
|
||||||
|
return;
|
||||||
|
|
||||||
var totalMetabolismTempChange = comp.MetabolismHeat - comp.RadiatedHeat;
|
var totalMetabolismTempChange = ent.Comp1.MetabolismHeat - ent.Comp1.RadiatedHeat;
|
||||||
|
|
||||||
// implicit heat regulation
|
// implicit heat regulation
|
||||||
var tempDiff = Math.Abs(temperatureComponent.CurrentTemperature - comp.NormalBodyTemperature);
|
var tempDiff = Math.Abs(ent.Comp2.CurrentTemperature - ent.Comp1.NormalBodyTemperature);
|
||||||
var heatCapacity = _tempSys.GetHeatCapacity(uid, temperatureComponent);
|
var heatCapacity = _tempSys.GetHeatCapacity(ent, ent);
|
||||||
var targetHeat = tempDiff * heatCapacity;
|
var targetHeat = tempDiff * heatCapacity;
|
||||||
if (temperatureComponent.CurrentTemperature > comp.NormalBodyTemperature)
|
if (ent.Comp2.CurrentTemperature > ent.Comp1.NormalBodyTemperature)
|
||||||
{
|
{
|
||||||
totalMetabolismTempChange -= Math.Min(targetHeat, comp.ImplicitHeatRegulation);
|
totalMetabolismTempChange -= Math.Min(targetHeat, ent.Comp1.ImplicitHeatRegulation);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
totalMetabolismTempChange += Math.Min(targetHeat, comp.ImplicitHeatRegulation);
|
totalMetabolismTempChange += Math.Min(targetHeat, ent.Comp1.ImplicitHeatRegulation);
|
||||||
}
|
}
|
||||||
|
|
||||||
_tempSys.ChangeHeat(uid, totalMetabolismTempChange, true, temperatureComponent);
|
_tempSys.ChangeHeat(ent, totalMetabolismTempChange, ignoreHeatResistance: true, ent);
|
||||||
|
|
||||||
// recalc difference and target heat
|
// recalc difference and target heat
|
||||||
tempDiff = Math.Abs(temperatureComponent.CurrentTemperature - comp.NormalBodyTemperature);
|
tempDiff = Math.Abs(ent.Comp2.CurrentTemperature - ent.Comp1.NormalBodyTemperature);
|
||||||
targetHeat = tempDiff * heatCapacity;
|
targetHeat = tempDiff * heatCapacity;
|
||||||
|
|
||||||
// if body temperature is not within comfortable, thermal regulation
|
// if body temperature is not within comfortable, thermal regulation
|
||||||
// processes starts
|
// processes starts
|
||||||
if (tempDiff > comp.ThermalRegulationTemperatureThreshold)
|
if (tempDiff > ent.Comp1.ThermalRegulationTemperatureThreshold)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (temperatureComponent.CurrentTemperature > comp.NormalBodyTemperature)
|
if (ent.Comp2.CurrentTemperature > ent.Comp1.NormalBodyTemperature)
|
||||||
{
|
{
|
||||||
if (!_actionBlockerSys.CanSweat(uid)) return;
|
if (!_actionBlockerSys.CanSweat(ent))
|
||||||
_tempSys.ChangeHeat(uid, -Math.Min(targetHeat, comp.SweatHeatRegulation), true,
|
return;
|
||||||
temperatureComponent);
|
|
||||||
|
_tempSys.ChangeHeat(ent, -Math.Min(targetHeat, ent.Comp1.SweatHeatRegulation), ignoreHeatResistance: true, ent);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!_actionBlockerSys.CanShiver(uid)) return;
|
if (!_actionBlockerSys.CanShiver(ent))
|
||||||
_tempSys.ChangeHeat(uid, Math.Min(targetHeat, comp.ShiveringHeatRegulation), true,
|
return;
|
||||||
temperatureComponent);
|
|
||||||
|
_tempSys.ChangeHeat(ent, Math.Min(targetHeat, ent.Comp1.ShiveringHeatRegulation), ignoreHeatResistance: true, ent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,17 +101,17 @@ namespace Content.Server.Hands.Systems
|
|||||||
|
|
||||||
private void HandleBodyPartAdded(EntityUid uid, HandsComponent component, ref BodyPartAddedEvent args)
|
private void HandleBodyPartAdded(EntityUid uid, HandsComponent component, ref BodyPartAddedEvent args)
|
||||||
{
|
{
|
||||||
if (args.Part.PartType != BodyPartType.Hand)
|
if (args.Part.Comp.PartType != BodyPartType.Hand)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// If this annoys you, which it should.
|
// If this annoys you, which it should.
|
||||||
// Ping Smugleaf.
|
// Ping Smugleaf.
|
||||||
var location = args.Part.Symmetry switch
|
var location = args.Part.Comp.Symmetry switch
|
||||||
{
|
{
|
||||||
BodyPartSymmetry.None => HandLocation.Middle,
|
BodyPartSymmetry.None => HandLocation.Middle,
|
||||||
BodyPartSymmetry.Left => HandLocation.Left,
|
BodyPartSymmetry.Left => HandLocation.Left,
|
||||||
BodyPartSymmetry.Right => HandLocation.Right,
|
BodyPartSymmetry.Right => HandLocation.Right,
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(args.Part.Symmetry))
|
_ => throw new ArgumentOutOfRangeException(nameof(args.Part.Comp.Symmetry))
|
||||||
};
|
};
|
||||||
|
|
||||||
AddHand(uid, args.Slot, location);
|
AddHand(uid, args.Slot, location);
|
||||||
@@ -119,7 +119,7 @@ namespace Content.Server.Hands.Systems
|
|||||||
|
|
||||||
private void HandleBodyPartRemoved(EntityUid uid, HandsComponent component, ref BodyPartRemovedEvent args)
|
private void HandleBodyPartRemoved(EntityUid uid, HandsComponent component, ref BodyPartRemovedEvent args)
|
||||||
{
|
{
|
||||||
if (args.Part.PartType != BodyPartType.Hand)
|
if (args.Part.Comp.PartType != BodyPartType.Hand)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
RemoveHand(uid, args.Slot);
|
RemoveHand(uid, args.Slot);
|
||||||
|
|||||||
@@ -19,10 +19,10 @@ public sealed partial class NymphSystem : EntitySystem
|
|||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
SubscribeLocalEvent<NymphComponent, RemovedFromPartInBodyEvent>(OnRemovedFromPart);
|
SubscribeLocalEvent<NymphComponent, OrganRemovedFromBodyEvent>(OnRemovedFromPart);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnRemovedFromPart(EntityUid uid, NymphComponent comp, RemovedFromPartInBodyEvent args)
|
private void OnRemovedFromPart(EntityUid uid, NymphComponent comp, ref OrganRemovedFromBodyEvent args)
|
||||||
{
|
{
|
||||||
if (!_timing.IsFirstTimePredicted)
|
if (!_timing.IsFirstTimePredicted)
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ namespace Content.Shared.ActionBlocker
|
|||||||
public bool CanShiver(EntityUid uid)
|
public bool CanShiver(EntityUid uid)
|
||||||
{
|
{
|
||||||
var ev = new ShiverAttemptEvent(uid);
|
var ev = new ShiverAttemptEvent(uid);
|
||||||
RaiseLocalEvent(uid, ev);
|
RaiseLocalEvent(uid, ref ev);
|
||||||
|
|
||||||
return !ev.Cancelled;
|
return !ev.Cancelled;
|
||||||
}
|
}
|
||||||
@@ -210,7 +210,7 @@ namespace Content.Shared.ActionBlocker
|
|||||||
public bool CanSweat(EntityUid uid)
|
public bool CanSweat(EntityUid uid)
|
||||||
{
|
{
|
||||||
var ev = new SweatAttemptEvent(uid);
|
var ev = new SweatAttemptEvent(uid);
|
||||||
RaiseLocalEvent(uid, ev);
|
RaiseLocalEvent(uid, ref ev);
|
||||||
|
|
||||||
return !ev.Cancelled;
|
return !ev.Cancelled;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,61 +1,28 @@
|
|||||||
namespace Content.Shared.Body.Events
|
namespace Content.Shared.Body.Events;
|
||||||
{
|
|
||||||
// All of these events are raised on a mechanism entity when added/removed to a body in different
|
// All of these events are raised on a mechanism entity when added/removed to a body in different
|
||||||
// ways.
|
// ways.
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Raised on a mechanism when it is added to a body part.
|
/// Raised on a mechanism when it is added to a body part.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class AddedToPartEvent : EntityEventArgs
|
[ByRefEvent]
|
||||||
{
|
public readonly record struct OrganAddedEvent(EntityUid Part);
|
||||||
public EntityUid Part;
|
|
||||||
|
|
||||||
public AddedToPartEvent(EntityUid part)
|
|
||||||
{
|
|
||||||
Part = part;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Raised on a mechanism when it is added to a body part within a body.
|
/// Raised on a mechanism when it is added to a body part within a body.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class AddedToPartInBodyEvent : EntityEventArgs
|
[ByRefEvent]
|
||||||
{
|
public readonly record struct OrganAddedToBodyEvent(EntityUid Body, EntityUid Part);
|
||||||
public EntityUid Body;
|
|
||||||
public EntityUid Part;
|
|
||||||
|
|
||||||
public AddedToPartInBodyEvent(EntityUid body, EntityUid part)
|
|
||||||
{
|
|
||||||
Body = body;
|
|
||||||
Part = part;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Raised on a mechanism when it is removed from a body part.
|
/// Raised on a mechanism when it is removed from a body part.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class RemovedFromPartEvent : EntityEventArgs
|
[ByRefEvent]
|
||||||
{
|
public readonly record struct OrganRemovedEvent(EntityUid OldPart);
|
||||||
public EntityUid OldPart;
|
|
||||||
|
|
||||||
public RemovedFromPartEvent(EntityUid oldPart)
|
|
||||||
{
|
|
||||||
OldPart = oldPart;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Raised on a mechanism when it is removed from a body part within a body.
|
/// Raised on a mechanism when it is removed from a body part within a body.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class RemovedFromPartInBodyEvent : EntityEventArgs
|
[ByRefEvent]
|
||||||
{
|
public readonly record struct OrganRemovedFromBodyEvent(EntityUid OldBody, EntityUid OldPart);
|
||||||
public EntityUid OldBody;
|
|
||||||
public EntityUid OldPart;
|
|
||||||
|
|
||||||
public RemovedFromPartInBodyEvent(EntityUid oldBody, EntityUid oldPart)
|
|
||||||
{
|
|
||||||
OldBody = oldBody;
|
|
||||||
OldPart = oldPart;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
namespace Content.Shared.Body.Events
|
namespace Content.Shared.Body.Events;
|
||||||
{
|
|
||||||
public sealed class ShiverAttemptEvent : CancellableEntityEventArgs
|
|
||||||
{
|
|
||||||
public ShiverAttemptEvent(EntityUid uid)
|
|
||||||
{
|
|
||||||
Uid = uid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public EntityUid Uid { get; }
|
[ByRefEvent]
|
||||||
}
|
public record struct ShiverAttemptEvent(EntityUid Uid)
|
||||||
|
{
|
||||||
|
public readonly EntityUid Uid = Uid;
|
||||||
|
public bool Cancelled = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
namespace Content.Shared.Body.Events
|
namespace Content.Shared.Body.Events;
|
||||||
{
|
|
||||||
public sealed class SweatAttemptEvent : CancellableEntityEventArgs
|
|
||||||
{
|
|
||||||
public SweatAttemptEvent(EntityUid uid)
|
|
||||||
{
|
|
||||||
Uid = uid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public EntityUid Uid { get; }
|
[ByRefEvent]
|
||||||
}
|
public record struct SweatAttemptEvent(EntityUid Uid)
|
||||||
|
{
|
||||||
|
public readonly EntityUid Uid = Uid;
|
||||||
|
public bool Cancelled = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Content.Shared.Body.Systems;
|
using Content.Shared.Body.Systems;
|
||||||
using Robust.Shared.Containers;
|
using Robust.Shared.Containers;
|
||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
|
|
||||||
@@ -11,6 +11,6 @@ public sealed partial class OrganComponent : Component
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Relevant body this organ is attached to.
|
/// Relevant body this organ is attached to.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("body"), AutoNetworkedField]
|
[DataField, AutoNetworkedField]
|
||||||
public EntityUid? Body;
|
public EntityUid? Body;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
namespace Content.Shared.Body.Part;
|
namespace Content.Shared.Body.Part;
|
||||||
|
|
||||||
[ByRefEvent]
|
[ByRefEvent]
|
||||||
public readonly record struct BodyPartAddedEvent(string Slot, BodyPartComponent Part);
|
public readonly record struct BodyPartAddedEvent(string Slot, Entity<BodyPartComponent> Part);
|
||||||
|
|
||||||
[ByRefEvent]
|
[ByRefEvent]
|
||||||
public readonly record struct BodyPartRemovedEvent(string Slot, BodyPartComponent Part);
|
public readonly record struct BodyPartRemovedEvent(string Slot, Entity<BodyPartComponent> Part);
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ using Content.Shared.Gibbing.Components;
|
|||||||
using Content.Shared.Gibbing.Events;
|
using Content.Shared.Gibbing.Events;
|
||||||
using Content.Shared.Gibbing.Systems;
|
using Content.Shared.Gibbing.Systems;
|
||||||
using Content.Shared.Inventory;
|
using Content.Shared.Inventory;
|
||||||
using Content.Shared.Inventory.Events;
|
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Audio.Systems;
|
using Robust.Shared.Audio.Systems;
|
||||||
using Robust.Shared.Containers;
|
using Robust.Shared.Containers;
|
||||||
@@ -30,8 +29,10 @@ public partial class SharedBodySystem
|
|||||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||||
[Dependency] private readonly GibbingSystem _gibbingSystem = default!;
|
[Dependency] private readonly GibbingSystem _gibbingSystem = default!;
|
||||||
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
||||||
|
|
||||||
private const float GibletLaunchImpulse = 8;
|
private const float GibletLaunchImpulse = 8;
|
||||||
private const float GibletLaunchImpulseVariance = 3;
|
private const float GibletLaunchImpulseVariance = 3;
|
||||||
|
|
||||||
private void InitializeBody()
|
private void InitializeBody()
|
||||||
{
|
{
|
||||||
// Body here to handle root body parts.
|
// Body here to handle root body parts.
|
||||||
@@ -43,7 +44,7 @@ public partial class SharedBodySystem
|
|||||||
SubscribeLocalEvent<BodyComponent, CanDragEvent>(OnBodyCanDrag);
|
SubscribeLocalEvent<BodyComponent, CanDragEvent>(OnBodyCanDrag);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnBodyInserted(EntityUid uid, BodyComponent component, EntInsertedIntoContainerMessage args)
|
private void OnBodyInserted(Entity<BodyComponent> ent, ref EntInsertedIntoContainerMessage args)
|
||||||
{
|
{
|
||||||
// Root body part?
|
// Root body part?
|
||||||
var slotId = args.Container.ID;
|
var slotId = args.Container.ID;
|
||||||
@@ -51,21 +52,21 @@ public partial class SharedBodySystem
|
|||||||
if (slotId != BodyRootContainerId)
|
if (slotId != BodyRootContainerId)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var entity = args.Entity;
|
var insertedUid = args.Entity;
|
||||||
|
|
||||||
if (TryComp(entity, out BodyPartComponent? childPart))
|
if (TryComp(insertedUid, out BodyPartComponent? part))
|
||||||
{
|
{
|
||||||
AddPart(uid, entity, slotId, childPart);
|
AddPart((ent, ent), (insertedUid, part), slotId);
|
||||||
RecursiveBodyUpdate(entity, uid, childPart);
|
RecursiveBodyUpdate((insertedUid, part), ent);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TryComp(entity, out OrganComponent? organ))
|
if (TryComp(insertedUid, out OrganComponent? organ))
|
||||||
{
|
{
|
||||||
AddOrgan(entity, uid, uid, organ);
|
AddOrgan((insertedUid, organ), ent, ent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnBodyRemoved(EntityUid uid, BodyComponent component, EntRemovedFromContainerMessage args)
|
private void OnBodyRemoved(Entity<BodyComponent> ent, ref EntRemovedFromContainerMessage args)
|
||||||
{
|
{
|
||||||
// Root body part?
|
// Root body part?
|
||||||
var slotId = args.Container.ID;
|
var slotId = args.Container.ID;
|
||||||
@@ -73,55 +74,55 @@ public partial class SharedBodySystem
|
|||||||
if (slotId != BodyRootContainerId)
|
if (slotId != BodyRootContainerId)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var entity = args.Entity;
|
var removedUid = args.Entity;
|
||||||
DebugTools.Assert(!TryComp(entity, out BodyPartComponent? b) || b.Body == uid);
|
DebugTools.Assert(!TryComp(removedUid, out BodyPartComponent? b) || b.Body == ent);
|
||||||
DebugTools.Assert(!TryComp(entity, out OrganComponent? o) || o.Body == uid);
|
DebugTools.Assert(!TryComp(removedUid, out OrganComponent? o) || o.Body == ent);
|
||||||
|
|
||||||
if (TryComp(entity, out BodyPartComponent? childPart))
|
if (TryComp(removedUid, out BodyPartComponent? part))
|
||||||
{
|
{
|
||||||
RemovePart(uid, entity, slotId, childPart);
|
RemovePart((ent, ent), (removedUid, part), slotId);
|
||||||
RecursiveBodyUpdate(entity, null, childPart);
|
RecursiveBodyUpdate((removedUid, part), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TryComp(entity, out OrganComponent? organ))
|
if (TryComp(removedUid, out OrganComponent? organ))
|
||||||
RemoveOrgan(entity, uid, organ);
|
RemoveOrgan((removedUid, organ), ent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnBodyInit(EntityUid bodyId, BodyComponent body, ComponentInit args)
|
private void OnBodyInit(Entity<BodyComponent> ent, ref ComponentInit args)
|
||||||
{
|
{
|
||||||
// Setup the initial container.
|
// Setup the initial container.
|
||||||
body.RootContainer = Containers.EnsureContainer<ContainerSlot>(bodyId, BodyRootContainerId);
|
ent.Comp.RootContainer = Containers.EnsureContainer<ContainerSlot>(ent, BodyRootContainerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnBodyMapInit(EntityUid bodyId, BodyComponent body, MapInitEvent args)
|
private void OnBodyMapInit(Entity<BodyComponent> ent, ref MapInitEvent args)
|
||||||
{
|
{
|
||||||
if (body.Prototype == null)
|
if (ent.Comp.Prototype is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// One-time setup
|
// One-time setup
|
||||||
// Obviously can't run in Init to avoid double-spawns on save / load.
|
// Obviously can't run in Init to avoid double-spawns on save / load.
|
||||||
var prototype = Prototypes.Index(body.Prototype.Value);
|
var prototype = Prototypes.Index(ent.Comp.Prototype.Value);
|
||||||
MapInitBody(bodyId, prototype);
|
MapInitBody(ent, prototype);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MapInitBody(EntityUid bodyEntity, BodyPrototype prototype)
|
private void MapInitBody(EntityUid bodyEntity, BodyPrototype prototype)
|
||||||
{
|
{
|
||||||
var protoRoot = prototype.Slots[prototype.Root];
|
var protoRoot = prototype.Slots[prototype.Root];
|
||||||
if (protoRoot.Part == null)
|
if (protoRoot.Part is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// This should already handle adding the entity to the root.
|
// This should already handle adding the entity to the root.
|
||||||
var rootPartEntity = SpawnInContainerOrDrop(protoRoot.Part, bodyEntity, BodyRootContainerId);
|
var rootPartUid = SpawnInContainerOrDrop(protoRoot.Part, bodyEntity, BodyRootContainerId);
|
||||||
var rootPart = Comp<BodyPartComponent>(rootPartEntity);
|
var rootPart = Comp<BodyPartComponent>(rootPartUid);
|
||||||
rootPart.Body = bodyEntity;
|
rootPart.Body = bodyEntity;
|
||||||
Dirty(rootPartEntity, rootPart);
|
Dirty(rootPartUid, rootPart);
|
||||||
|
|
||||||
// Setup the rest of the body entities.
|
// Setup the rest of the body entities.
|
||||||
SetupOrgans(rootPartEntity, rootPart, protoRoot.Organs);
|
SetupOrgans((rootPartUid, rootPart), protoRoot.Organs);
|
||||||
MapInitParts(rootPartEntity, prototype);
|
MapInitParts(rootPartUid, prototype);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnBodyCanDrag(EntityUid uid, BodyComponent component, ref CanDragEvent args)
|
private void OnBodyCanDrag(Entity<BodyComponent> ent, ref CanDragEvent args)
|
||||||
{
|
{
|
||||||
args.Handled = true;
|
args.Handled = true;
|
||||||
}
|
}
|
||||||
@@ -169,7 +170,7 @@ public partial class SharedBodySystem
|
|||||||
var partSlot = CreatePartSlot(parentEntity, connection, childPartComponent.PartType, parentPartComponent);
|
var partSlot = CreatePartSlot(parentEntity, connection, childPartComponent.PartType, parentPartComponent);
|
||||||
var cont = Containers.GetContainer(parentEntity, GetPartSlotContainerId(connection));
|
var cont = Containers.GetContainer(parentEntity, GetPartSlotContainerId(connection));
|
||||||
|
|
||||||
if (partSlot == null || !Containers.Insert(childPart, cont))
|
if (partSlot is null || !Containers.Insert(childPart, cont))
|
||||||
{
|
{
|
||||||
Log.Error($"Could not create slot for connection {connection} in body {prototype.ID}");
|
Log.Error($"Could not create slot for connection {connection} in body {prototype.ID}");
|
||||||
QueueDel(childPart);
|
QueueDel(childPart);
|
||||||
@@ -177,7 +178,7 @@ public partial class SharedBodySystem
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add organs
|
// Add organs
|
||||||
SetupOrgans(childPart, childPartComponent, connectionSlot.Organs);
|
SetupOrgans((childPart, childPartComponent), connectionSlot.Organs);
|
||||||
|
|
||||||
// Enqueue it so we can also get its neighbors.
|
// Enqueue it so we can also get its neighbors.
|
||||||
frontier.Enqueue(connection);
|
frontier.Enqueue(connection);
|
||||||
@@ -185,16 +186,16 @@ public partial class SharedBodySystem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetupOrgans(EntityUid partId, BodyPartComponent partComponent, Dictionary<string, string> organs)
|
private void SetupOrgans(Entity<BodyPartComponent> ent, Dictionary<string, string> organs)
|
||||||
{
|
{
|
||||||
foreach (var (organSlotId, organProto) in organs)
|
foreach (var (organSlotId, organProto) in organs)
|
||||||
{
|
{
|
||||||
var slot = CreateOrganSlot(organSlotId, partId, partComponent);
|
var slot = CreateOrganSlot((ent, ent), organSlotId);
|
||||||
SpawnInContainerOrDrop(organProto, partId, GetOrganContainerId(organSlotId));
|
SpawnInContainerOrDrop(organProto, ent, GetOrganContainerId(organSlotId));
|
||||||
|
|
||||||
if (slot == null)
|
if (slot is null)
|
||||||
{
|
{
|
||||||
Log.Error($"Could not create organ for slot {organSlotId} in {ToPrettyString(partId)}");
|
Log.Error($"Could not create organ for slot {organSlotId} in {ToPrettyString(ent)}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -202,12 +203,14 @@ public partial class SharedBodySystem
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets all body containers on this entity including the root one.
|
/// Gets all body containers on this entity including the root one.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<BaseContainer> GetBodyContainers(EntityUid id, BodyComponent? body = null,
|
public IEnumerable<BaseContainer> GetBodyContainers(
|
||||||
|
EntityUid id,
|
||||||
|
BodyComponent? body = null,
|
||||||
BodyPartComponent? rootPart = null)
|
BodyPartComponent? rootPart = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(id, ref body, false) ||
|
if (!Resolve(id, ref body, logMissing: false)
|
||||||
body.RootContainer.ContainedEntity == null ||
|
|| body.RootContainer.ContainedEntity is null
|
||||||
!Resolve(body.RootContainer.ContainedEntity.Value, ref rootPart))
|
|| !Resolve(body.RootContainer.ContainedEntity.Value, ref rootPart))
|
||||||
{
|
{
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
@@ -223,13 +226,15 @@ public partial class SharedBodySystem
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets all child body parts of this entity, including the root entity.
|
/// Gets all child body parts of this entity, including the root entity.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<(EntityUid Id, BodyPartComponent Component)> GetBodyChildren(EntityUid? id, BodyComponent? body = null,
|
public IEnumerable<(EntityUid Id, BodyPartComponent Component)> GetBodyChildren(
|
||||||
|
EntityUid? id,
|
||||||
|
BodyComponent? body = null,
|
||||||
BodyPartComponent? rootPart = null)
|
BodyPartComponent? rootPart = null)
|
||||||
{
|
{
|
||||||
if (id == null ||
|
if (id is null
|
||||||
!Resolve(id.Value, ref body, false) ||
|
|| !Resolve(id.Value, ref body, logMissing: false)
|
||||||
body.RootContainer.ContainedEntity == null ||
|
|| body.RootContainer.ContainedEntity is null
|
||||||
!Resolve(body.RootContainer.ContainedEntity.Value, ref rootPart))
|
|| !Resolve(body.RootContainer.ContainedEntity.Value, ref rootPart))
|
||||||
{
|
{
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
@@ -240,9 +245,11 @@ public partial class SharedBodySystem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<(EntityUid Id, OrganComponent Component)> GetBodyOrgans(EntityUid? bodyId, BodyComponent? body = null)
|
public IEnumerable<(EntityUid Id, OrganComponent Component)> GetBodyOrgans(
|
||||||
|
EntityUid? bodyId,
|
||||||
|
BodyComponent? body = null)
|
||||||
{
|
{
|
||||||
if (bodyId == null || !Resolve(bodyId.Value, ref body, false))
|
if (bodyId is null || !Resolve(bodyId.Value, ref body, logMissing: false))
|
||||||
yield break;
|
yield break;
|
||||||
|
|
||||||
foreach (var part in GetBodyChildren(bodyId, body))
|
foreach (var part in GetBodyChildren(bodyId, body))
|
||||||
@@ -260,10 +267,15 @@ public partial class SharedBodySystem
|
|||||||
/// <param name="bodyId"></param>
|
/// <param name="bodyId"></param>
|
||||||
/// <param name="body"></param>
|
/// <param name="body"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public IEnumerable<BodyPartSlot> GetBodyAllSlots(EntityUid bodyId, BodyComponent? body = null)
|
public IEnumerable<BodyPartSlot> GetBodyAllSlots(
|
||||||
|
EntityUid bodyId,
|
||||||
|
BodyComponent? body = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(bodyId, ref body, logMissing: false)
|
||||||
|
|| body.RootContainer.ContainedEntity is null)
|
||||||
{
|
{
|
||||||
if (!Resolve(bodyId, ref body, false) || body.RootContainer.ContainedEntity == null)
|
|
||||||
yield break;
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var slot in GetAllBodyPartSlots(body.RootContainer.ContainedEntity.Value))
|
foreach (var slot in GetAllBodyPartSlots(body.RootContainer.ContainedEntity.Value))
|
||||||
{
|
{
|
||||||
@@ -279,12 +291,11 @@ public partial class SharedBodySystem
|
|||||||
Vector2? splatDirection = null,
|
Vector2? splatDirection = null,
|
||||||
float splatModifier = 1,
|
float splatModifier = 1,
|
||||||
Angle splatCone = default,
|
Angle splatCone = default,
|
||||||
SoundSpecifier? gibSoundOverride = null
|
SoundSpecifier? gibSoundOverride = null)
|
||||||
)
|
|
||||||
{
|
{
|
||||||
var gibs = new HashSet<EntityUid>();
|
var gibs = new HashSet<EntityUid>();
|
||||||
|
|
||||||
if (!Resolve(bodyId, ref body, false))
|
if (!Resolve(bodyId, ref body, logMissing: false))
|
||||||
return gibs;
|
return gibs;
|
||||||
|
|
||||||
var root = GetRootPartOrNull(bodyId, body);
|
var root = GetRootPartOrNull(bodyId, body);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Content.Shared.Body.Components;
|
using Content.Shared.Body.Components;
|
||||||
using Content.Shared.Body.Events;
|
using Content.Shared.Body.Events;
|
||||||
using Content.Shared.Body.Organ;
|
using Content.Shared.Body.Organ;
|
||||||
@@ -9,41 +9,50 @@ namespace Content.Shared.Body.Systems;
|
|||||||
|
|
||||||
public partial class SharedBodySystem
|
public partial class SharedBodySystem
|
||||||
{
|
{
|
||||||
private void AddOrgan(EntityUid uid, EntityUid bodyUid, EntityUid parentPartUid, OrganComponent component)
|
private void AddOrgan(
|
||||||
|
Entity<OrganComponent> organEnt,
|
||||||
|
EntityUid bodyUid,
|
||||||
|
EntityUid parentPartUid)
|
||||||
{
|
{
|
||||||
component.Body = bodyUid;
|
organEnt.Comp.Body = bodyUid;
|
||||||
RaiseLocalEvent(uid, new AddedToPartEvent(parentPartUid));
|
var addedEv = new OrganAddedEvent(parentPartUid);
|
||||||
|
RaiseLocalEvent(organEnt, ref addedEv);
|
||||||
|
|
||||||
if (component.Body != null)
|
if (organEnt.Comp.Body is not null)
|
||||||
RaiseLocalEvent(uid, new AddedToPartInBodyEvent(component.Body.Value, parentPartUid));
|
{
|
||||||
|
var addedInBodyEv = new OrganAddedToBodyEvent(bodyUid, parentPartUid);
|
||||||
Dirty(uid, component);
|
RaiseLocalEvent(organEnt, ref addedInBodyEv);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RemoveOrgan(EntityUid uid, EntityUid parentPartUid, OrganComponent component)
|
Dirty(organEnt, organEnt.Comp);
|
||||||
{
|
|
||||||
RaiseLocalEvent(uid, new RemovedFromPartEvent(parentPartUid));
|
|
||||||
|
|
||||||
if (component.Body != null)
|
|
||||||
{
|
|
||||||
RaiseLocalEvent(uid, new RemovedFromPartInBodyEvent(component.Body.Value, parentPartUid));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
component.Body = null;
|
private void RemoveOrgan(Entity<OrganComponent> organEnt, EntityUid parentPartUid)
|
||||||
Dirty(uid, component);
|
{
|
||||||
|
var removedEv = new OrganRemovedEvent(parentPartUid);
|
||||||
|
RaiseLocalEvent(organEnt, ref removedEv);
|
||||||
|
|
||||||
|
if (organEnt.Comp.Body is { Valid: true } bodyUid)
|
||||||
|
{
|
||||||
|
var removedInBodyEv = new OrganRemovedFromBodyEvent(bodyUid, parentPartUid);
|
||||||
|
RaiseLocalEvent(organEnt, ref removedInBodyEv);
|
||||||
|
}
|
||||||
|
|
||||||
|
organEnt.Comp.Body = null;
|
||||||
|
Dirty(organEnt, organEnt.Comp);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the specified organ slot on the parent entity.
|
/// Creates the specified organ slot on the parent entity.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private OrganSlot? CreateOrganSlot(string slotId, EntityUid parent, BodyPartComponent? part = null)
|
private OrganSlot? CreateOrganSlot(Entity<BodyPartComponent?> parentEnt, string slotId)
|
||||||
{
|
{
|
||||||
if (!Resolve(parent, ref part, false))
|
if (!Resolve(parentEnt, ref parentEnt.Comp, logMissing: false))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
Containers.EnsureContainer<ContainerSlot>(parent, GetOrganContainerId(slotId));
|
Containers.EnsureContainer<ContainerSlot>(parentEnt, GetOrganContainerId(slotId));
|
||||||
var slot = new OrganSlot(slotId);
|
var slot = new OrganSlot(slotId);
|
||||||
part.Organs.Add(slotId, slot);
|
parentEnt.Comp.Organs.Add(slotId, slot);
|
||||||
return slot;
|
return slot;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +67,7 @@ public partial class SharedBodySystem
|
|||||||
{
|
{
|
||||||
slot = null;
|
slot = null;
|
||||||
|
|
||||||
if (parent == null || !Resolve(parent.Value, ref part, false))
|
if (parent is null || !Resolve(parent.Value, ref part, logMissing: false))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -71,7 +80,10 @@ public partial class SharedBodySystem
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns whether the slotId exists on the partId.
|
/// Returns whether the slotId exists on the partId.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool CanInsertOrgan(EntityUid partId, string slotId, BodyPartComponent? part = null)
|
public bool CanInsertOrgan(
|
||||||
|
EntityUid partId,
|
||||||
|
string slotId,
|
||||||
|
BodyPartComponent? part = null)
|
||||||
{
|
{
|
||||||
return Resolve(partId, ref part) && part.Organs.ContainsKey(slotId);
|
return Resolve(partId, ref part) && part.Organs.ContainsKey(slotId);
|
||||||
}
|
}
|
||||||
@@ -79,26 +91,32 @@ public partial class SharedBodySystem
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns whether the specified organ slot exists on the partId.
|
/// Returns whether the specified organ slot exists on the partId.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool CanInsertOrgan(EntityUid partId, OrganSlot slot, BodyPartComponent? part = null)
|
public bool CanInsertOrgan(
|
||||||
|
EntityUid partId,
|
||||||
|
OrganSlot slot,
|
||||||
|
BodyPartComponent? part = null)
|
||||||
{
|
{
|
||||||
return CanInsertOrgan(partId, slot.Id, part);
|
return CanInsertOrgan(partId, slot.Id, part);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool InsertOrgan(EntityUid partId, EntityUid organId, string slotId, BodyPartComponent? part = null, OrganComponent? organ = null)
|
public bool InsertOrgan(
|
||||||
|
EntityUid partId,
|
||||||
|
EntityUid organId,
|
||||||
|
string slotId,
|
||||||
|
BodyPartComponent? part = null,
|
||||||
|
OrganComponent? organ = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(organId, ref organ, false) ||
|
if (!Resolve(organId, ref organ, logMissing: false)
|
||||||
!Resolve(partId, ref part, false) ||
|
|| !Resolve(partId, ref part, logMissing: false)
|
||||||
!CanInsertOrgan(partId, slotId, part))
|
|| !CanInsertOrgan(partId, slotId, part))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var containerId = GetOrganContainerId(slotId);
|
var containerId = GetOrganContainerId(slotId);
|
||||||
|
|
||||||
if (!Containers.TryGetContainer(partId, containerId, out var container))
|
return Containers.TryGetContainer(partId, containerId, out var container)
|
||||||
return false;
|
&& Containers.Insert(organId, container);
|
||||||
|
|
||||||
return Containers.Insert(organId, container);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -111,10 +129,8 @@ public partial class SharedBodySystem
|
|||||||
|
|
||||||
var parent = container.Owner;
|
var parent = container.Owner;
|
||||||
|
|
||||||
if (!HasComp<BodyPartComponent>(parent))
|
return HasComp<BodyPartComponent>(parent)
|
||||||
return false;
|
&& Containers.Remove(organId, container);
|
||||||
|
|
||||||
return Containers.Remove(organId, container);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -126,8 +142,8 @@ public partial class SharedBodySystem
|
|||||||
BodyPartComponent? part = null,
|
BodyPartComponent? part = null,
|
||||||
OrganComponent? organ = null)
|
OrganComponent? organ = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(partId, ref part, false) ||
|
if (!Resolve(partId, ref part, logMissing: false)
|
||||||
!Resolve(organId, ref organ, false))
|
|| !Resolve(organId, ref organ, logMissing: false))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Shared.Body.Components;
|
using Content.Shared.Body.Components;
|
||||||
using Content.Shared.Body.Events;
|
using Content.Shared.Body.Events;
|
||||||
@@ -23,52 +23,52 @@ public partial class SharedBodySystem
|
|||||||
SubscribeLocalEvent<BodyPartComponent, EntRemovedFromContainerMessage>(OnBodyPartRemoved);
|
SubscribeLocalEvent<BodyPartComponent, EntRemovedFromContainerMessage>(OnBodyPartRemoved);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnBodyPartInserted(EntityUid uid, BodyPartComponent component, EntInsertedIntoContainerMessage args)
|
private void OnBodyPartInserted(Entity<BodyPartComponent> ent, ref EntInsertedIntoContainerMessage args)
|
||||||
{
|
{
|
||||||
// Body part inserted into another body part.
|
// Body part inserted into another body part.
|
||||||
var entity = args.Entity;
|
var insertedUid = args.Entity;
|
||||||
var slotId = args.Container.ID;
|
var slotId = args.Container.ID;
|
||||||
|
|
||||||
if (component.Body == null)
|
if (ent.Comp.Body is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (TryComp(entity, out BodyPartComponent? childPart))
|
if (TryComp(insertedUid, out BodyPartComponent? part))
|
||||||
{
|
{
|
||||||
AddPart(component.Body.Value, entity, slotId, childPart);
|
AddPart(ent.Comp.Body.Value, (insertedUid, part), slotId);
|
||||||
RecursiveBodyUpdate(entity, component.Body.Value, childPart);
|
RecursiveBodyUpdate((insertedUid, part), ent.Comp.Body.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TryComp(entity, out OrganComponent? organ))
|
if (TryComp(insertedUid, out OrganComponent? organ))
|
||||||
AddOrgan(entity, component.Body.Value, uid, organ);
|
AddOrgan((insertedUid, organ), ent.Comp.Body.Value, ent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnBodyPartRemoved(EntityUid uid, BodyPartComponent component, EntRemovedFromContainerMessage args)
|
private void OnBodyPartRemoved(Entity<BodyPartComponent> ent, ref EntRemovedFromContainerMessage args)
|
||||||
{
|
{
|
||||||
// Body part removed from another body part.
|
// Body part removed from another body part.
|
||||||
var entity = args.Entity;
|
var removedUid = args.Entity;
|
||||||
var slotId = args.Container.ID;
|
var slotId = args.Container.ID;
|
||||||
|
|
||||||
DebugTools.Assert(!TryComp(entity, out BodyPartComponent? b) || b.Body == component.Body);
|
DebugTools.Assert(!TryComp(removedUid, out BodyPartComponent? b) || b.Body == ent.Comp.Body);
|
||||||
DebugTools.Assert(!TryComp(entity, out OrganComponent? o) || o.Body == component.Body);
|
DebugTools.Assert(!TryComp(removedUid, out OrganComponent? o) || o.Body == ent.Comp.Body);
|
||||||
|
|
||||||
if (TryComp(entity, out BodyPartComponent? childPart) && childPart.Body != null)
|
if (TryComp(removedUid, out BodyPartComponent? part) && part.Body is not null)
|
||||||
{
|
{
|
||||||
RemovePart(childPart.Body.Value, entity, slotId, childPart);
|
RemovePart(part.Body.Value, (removedUid, part), slotId);
|
||||||
RecursiveBodyUpdate(entity, null, childPart);
|
RecursiveBodyUpdate((removedUid, part), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TryComp(entity, out OrganComponent? organ))
|
if (TryComp(removedUid, out OrganComponent? organ))
|
||||||
RemoveOrgan(entity, uid, organ);
|
RemoveOrgan((removedUid, organ), ent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RecursiveBodyUpdate(EntityUid uid, EntityUid? bodyUid, BodyPartComponent component)
|
private void RecursiveBodyUpdate(Entity<BodyPartComponent> ent, EntityUid? bodyUid)
|
||||||
{
|
{
|
||||||
component.Body = bodyUid;
|
ent.Comp.Body = bodyUid;
|
||||||
Dirty(uid, component);
|
Dirty(ent, ent.Comp);
|
||||||
|
|
||||||
foreach (var slotId in component.Organs.Keys)
|
foreach (var slotId in ent.Comp.Organs.Keys)
|
||||||
{
|
{
|
||||||
if (!Containers.TryGetContainer(uid, GetOrganContainerId(slotId), out var container))
|
if (!Containers.TryGetContainer(ent, GetOrganContainerId(slotId), out var container))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
foreach (var organ in container.ContainedEntities)
|
foreach (var organ in container.ContainedEntities)
|
||||||
@@ -78,105 +78,108 @@ public partial class SharedBodySystem
|
|||||||
|
|
||||||
Dirty(organ, organComp);
|
Dirty(organ, organComp);
|
||||||
|
|
||||||
if (organComp.Body != null)
|
if (organComp.Body is { Valid: true } oldBodyUid)
|
||||||
RaiseLocalEvent(organ, new RemovedFromPartInBodyEvent(organComp.Body.Value, uid));
|
{
|
||||||
|
var removedEv = new OrganRemovedFromBodyEvent(oldBodyUid, ent);
|
||||||
|
RaiseLocalEvent(organ, ref removedEv);
|
||||||
|
}
|
||||||
|
|
||||||
organComp.Body = bodyUid;
|
organComp.Body = bodyUid;
|
||||||
if (bodyUid != null)
|
if (bodyUid is not null)
|
||||||
RaiseLocalEvent(organ, new AddedToPartInBodyEvent(bodyUid.Value, uid));
|
{
|
||||||
|
var addedEv = new OrganAddedToBodyEvent(bodyUid.Value, ent);
|
||||||
|
RaiseLocalEvent(organ, ref addedEv);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var slotId in component.Children.Keys)
|
foreach (var slotId in ent.Comp.Children.Keys)
|
||||||
{
|
{
|
||||||
if (!Containers.TryGetContainer(uid, GetPartSlotContainerId(slotId), out var container))
|
if (!Containers.TryGetContainer(ent, GetPartSlotContainerId(slotId), out var container))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
foreach (var containedEnt in container.ContainedEntities)
|
foreach (var containedUid in container.ContainedEntities)
|
||||||
{
|
{
|
||||||
if (TryComp(containedEnt, out BodyPartComponent? childPart))
|
if (TryComp(containedUid, out BodyPartComponent? childPart))
|
||||||
RecursiveBodyUpdate(containedEnt, bodyUid, childPart);
|
RecursiveBodyUpdate((containedUid, childPart), bodyUid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void AddPart(
|
protected virtual void AddPart(
|
||||||
EntityUid bodyUid,
|
Entity<BodyComponent?> bodyEnt,
|
||||||
EntityUid partUid,
|
Entity<BodyPartComponent> partEnt,
|
||||||
string slotId,
|
string slotId)
|
||||||
BodyPartComponent component,
|
|
||||||
BodyComponent? bodyComp = null)
|
|
||||||
{
|
{
|
||||||
DebugTools.AssertOwner(partUid, component);
|
Dirty(partEnt, partEnt.Comp);
|
||||||
Dirty(partUid, component);
|
partEnt.Comp.Body = bodyEnt;
|
||||||
component.Body = bodyUid;
|
|
||||||
|
|
||||||
var ev = new BodyPartAddedEvent(slotId, component);
|
var ev = new BodyPartAddedEvent(slotId, partEnt);
|
||||||
RaiseLocalEvent(bodyUid, ref ev);
|
RaiseLocalEvent(bodyEnt, ref ev);
|
||||||
|
|
||||||
AddLeg(partUid, bodyUid, component, bodyComp);
|
AddLeg(partEnt, bodyEnt);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void RemovePart(
|
protected virtual void RemovePart(
|
||||||
EntityUid bodyUid,
|
Entity<BodyComponent?> bodyEnt,
|
||||||
EntityUid partUid,
|
Entity<BodyPartComponent> partEnt,
|
||||||
string slotId,
|
string slotId)
|
||||||
BodyPartComponent component,
|
|
||||||
BodyComponent? bodyComp = null)
|
|
||||||
{
|
{
|
||||||
DebugTools.AssertOwner(partUid, component);
|
Resolve(bodyEnt, ref bodyEnt.Comp, logMissing: false);
|
||||||
Resolve(bodyUid, ref bodyComp, false);
|
Dirty(partEnt, partEnt.Comp);
|
||||||
Dirty(partUid, component);
|
partEnt.Comp.Body = null;
|
||||||
component.Body = null;
|
|
||||||
|
|
||||||
var ev = new BodyPartRemovedEvent(slotId, component);
|
var ev = new BodyPartRemovedEvent(slotId, partEnt);
|
||||||
RaiseLocalEvent(bodyUid, ref ev);
|
RaiseLocalEvent(bodyEnt, ref ev);
|
||||||
|
|
||||||
RemoveLeg(partUid, bodyUid, component);
|
RemoveLeg(partEnt, bodyEnt);
|
||||||
PartRemoveDamage(bodyUid, component, bodyComp);
|
PartRemoveDamage(bodyEnt, partEnt);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddLeg(EntityUid uid, EntityUid bodyUid, BodyPartComponent component, BodyComponent? bodyComp = null)
|
private void AddLeg(Entity<BodyPartComponent> legEnt, Entity<BodyComponent?> bodyEnt)
|
||||||
{
|
{
|
||||||
if (!Resolve(bodyUid, ref bodyComp, false))
|
if (!Resolve(bodyEnt, ref bodyEnt.Comp, logMissing: false))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (component.PartType == BodyPartType.Leg)
|
if (legEnt.Comp.PartType == BodyPartType.Leg)
|
||||||
{
|
{
|
||||||
bodyComp.LegEntities.Add(uid);
|
bodyEnt.Comp.LegEntities.Add(legEnt);
|
||||||
UpdateMovementSpeed(bodyUid);
|
UpdateMovementSpeed(bodyEnt);
|
||||||
Dirty(bodyUid, bodyComp);
|
Dirty(bodyEnt, bodyEnt.Comp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RemoveLeg(EntityUid uid, EntityUid bodyUid, BodyPartComponent component, BodyComponent? bodyComp = null)
|
private void RemoveLeg(Entity<BodyPartComponent> legEnt, Entity<BodyComponent?> bodyEnt)
|
||||||
{
|
{
|
||||||
if (!Resolve(bodyUid, ref bodyComp, false))
|
if (!Resolve(bodyEnt, ref bodyEnt.Comp, logMissing: false))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (component.PartType == BodyPartType.Leg)
|
if (legEnt.Comp.PartType == BodyPartType.Leg)
|
||||||
{
|
{
|
||||||
bodyComp.LegEntities.Remove(uid);
|
bodyEnt.Comp.LegEntities.Remove(legEnt);
|
||||||
UpdateMovementSpeed(bodyUid);
|
UpdateMovementSpeed(bodyEnt);
|
||||||
Dirty(bodyUid, bodyComp);
|
Dirty(bodyEnt, bodyEnt.Comp);
|
||||||
|
|
||||||
if (!bodyComp.LegEntities.Any())
|
if (!bodyEnt.Comp.LegEntities.Any())
|
||||||
{
|
{
|
||||||
Standing.Down(bodyUid);
|
Standing.Down(bodyEnt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PartRemoveDamage(EntityUid parent, BodyPartComponent component, BodyComponent? bodyComp = null)
|
private void PartRemoveDamage(Entity<BodyComponent?> bodyEnt, Entity<BodyPartComponent> partEnt)
|
||||||
{
|
{
|
||||||
if (!Resolve(parent, ref bodyComp, false))
|
if (!Resolve(bodyEnt, ref bodyEnt.Comp, logMissing: false))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!_timing.ApplyingState && component.IsVital && !GetBodyChildrenOfType(parent, component.PartType, bodyComp).Any())
|
if (!_timing.ApplyingState
|
||||||
|
&& partEnt.Comp.IsVital
|
||||||
|
&& !GetBodyChildrenOfType(bodyEnt, partEnt.Comp.PartType, bodyEnt.Comp).Any()
|
||||||
|
)
|
||||||
{
|
{
|
||||||
// TODO BODY SYSTEM KILL : remove this when wounding and required parts are implemented properly
|
// TODO BODY SYSTEM KILL : remove this when wounding and required parts are implemented properly
|
||||||
var damage = new DamageSpecifier(Prototypes.Index<DamageTypePrototype>("Bloodloss"), 300);
|
var damage = new DamageSpecifier(Prototypes.Index<DamageTypePrototype>("Bloodloss"), 300);
|
||||||
Damageable.TryChangeDamage(parent, damage);
|
Damageable.TryChangeDamage(bodyEnt, damage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,7 +215,8 @@ public partial class SharedBodySystem
|
|||||||
|
|
||||||
var parent = container.Owner;
|
var parent = container.Owner;
|
||||||
|
|
||||||
if (!TryComp<BodyPartComponent>(parent, out var parentBody) || !parentBody.Children.ContainsKey(slotId))
|
if (!TryComp<BodyPartComponent>(parent, out var parentBody)
|
||||||
|
|| !parentBody.Children.ContainsKey(slotId))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return (parent, slotId);
|
return (parent, slotId);
|
||||||
@@ -252,7 +256,7 @@ public partial class SharedBodySystem
|
|||||||
BodyPartType partType,
|
BodyPartType partType,
|
||||||
BodyPartComponent? part = null)
|
BodyPartComponent? part = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(partUid, ref part, false))
|
if (!Resolve(partUid, ref part, logMissing: false))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
Containers.EnsureContainer<ContainerSlot>(partUid, GetPartSlotContainerId(slotId));
|
Containers.EnsureContainer<ContainerSlot>(partUid, GetPartSlotContainerId(slotId));
|
||||||
@@ -275,8 +279,8 @@ public partial class SharedBodySystem
|
|||||||
{
|
{
|
||||||
slot = null;
|
slot = null;
|
||||||
|
|
||||||
if (partId == null ||
|
if (partId is null
|
||||||
!Resolve(partId.Value, ref part, false))
|
|| !Resolve(partId.Value, ref part, logMissing: false))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -310,24 +314,31 @@ public partial class SharedBodySystem
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns true if the partId is the root body container for the specified bodyId.
|
/// Returns true if the partId is the root body container for the specified bodyId.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsPartRoot(EntityUid bodyId, EntityUid partId, BodyComponent? body = null, BodyPartComponent? part = null)
|
public bool IsPartRoot(
|
||||||
|
EntityUid bodyId,
|
||||||
|
EntityUid partId,
|
||||||
|
BodyComponent? body = null,
|
||||||
|
BodyPartComponent? part = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(partId, ref part)|| !Resolve(bodyId, ref body))
|
return Resolve(partId, ref part)
|
||||||
return false;
|
&& Resolve(bodyId, ref body)
|
||||||
|
&& Containers.TryGetContainingContainer(bodyId, partId, out var container)
|
||||||
return Containers.TryGetContainingContainer(bodyId, partId, out var container) && container.ID == BodyRootContainerId;
|
&& container.ID == BodyRootContainerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns true if we can attach the partId to the bodyId as the root entity.
|
/// Returns true if we can attach the partId to the bodyId as the root entity.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool CanAttachToRoot(EntityUid bodyId, EntityUid partId, BodyComponent? body = null,
|
public bool CanAttachToRoot(
|
||||||
|
EntityUid bodyId,
|
||||||
|
EntityUid partId,
|
||||||
|
BodyComponent? body = null,
|
||||||
BodyPartComponent? part = null)
|
BodyPartComponent? part = null)
|
||||||
{
|
{
|
||||||
return Resolve(bodyId, ref body) &&
|
return Resolve(bodyId, ref body)
|
||||||
Resolve(partId, ref part) &&
|
&& Resolve(partId, ref part)
|
||||||
body.RootContainer.ContainedEntity == null &&
|
&& body.RootContainer.ContainedEntity is null
|
||||||
bodyId != part.Body;
|
&& bodyId != part.Body;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -335,8 +346,11 @@ public partial class SharedBodySystem
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public (EntityUid Entity, BodyPartComponent BodyPart)? GetRootPartOrNull(EntityUid bodyId, BodyComponent? body = null)
|
public (EntityUid Entity, BodyPartComponent BodyPart)? GetRootPartOrNull(EntityUid bodyId, BodyComponent? body = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(bodyId, ref body) || body.RootContainer.ContainedEntity == null)
|
if (!Resolve(bodyId, ref body)
|
||||||
|
|| body.RootContainer.ContainedEntity is null)
|
||||||
|
{
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (body.RootContainer.ContainedEntity.Value,
|
return (body.RootContainer.ContainedEntity.Value,
|
||||||
Comp<BodyPartComponent>(body.RootContainer.ContainedEntity.Value));
|
Comp<BodyPartComponent>(body.RootContainer.ContainedEntity.Value));
|
||||||
@@ -352,13 +366,9 @@ public partial class SharedBodySystem
|
|||||||
BodyPartComponent? parentPart = null,
|
BodyPartComponent? parentPart = null,
|
||||||
BodyPartComponent? part = null)
|
BodyPartComponent? part = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(partId, ref part, false) ||
|
return Resolve(partId, ref part, logMissing: false)
|
||||||
!Resolve(parentId, ref parentPart, false))
|
&& Resolve(parentId, ref parentPart, logMissing: false)
|
||||||
{
|
&& CanAttachPart(parentId, slot.Id, partId, parentPart, part);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return CanAttachPart(parentId, slot.Id, partId, parentPart, part);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -371,16 +381,12 @@ public partial class SharedBodySystem
|
|||||||
BodyPartComponent? parentPart = null,
|
BodyPartComponent? parentPart = null,
|
||||||
BodyPartComponent? part = null)
|
BodyPartComponent? part = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(partId, ref part, false) ||
|
return Resolve(partId, ref part, logMissing: false)
|
||||||
!Resolve(parentId, ref parentPart, false) ||
|
&& Resolve(parentId, ref parentPart, logMissing: false)
|
||||||
!parentPart.Children.TryGetValue(slotId, out var parentSlotData))
|
&& parentPart.Children.TryGetValue(slotId, out var parentSlotData)
|
||||||
{
|
&& part.PartType == parentSlotData.Type
|
||||||
return false;
|
&& Containers.TryGetContainer(parentId, GetPartSlotContainerId(slotId), out var container)
|
||||||
}
|
&& Containers.CanInsert(partId, container);
|
||||||
|
|
||||||
return part.PartType == parentSlotData.Type &&
|
|
||||||
Containers.TryGetContainer(parentId, GetPartSlotContainerId(slotId), out var container) &&
|
|
||||||
Containers.CanInsert(partId, container);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool AttachPartToRoot(
|
public bool AttachPartToRoot(
|
||||||
@@ -389,14 +395,10 @@ public partial class SharedBodySystem
|
|||||||
BodyComponent? body = null,
|
BodyComponent? body = null,
|
||||||
BodyPartComponent? part = null)
|
BodyPartComponent? part = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(bodyId, ref body) ||
|
return Resolve(bodyId, ref body)
|
||||||
!Resolve(partId, ref part) ||
|
&& Resolve(partId, ref part)
|
||||||
!CanAttachToRoot(bodyId, partId, body, part))
|
&& CanAttachToRoot(bodyId, partId, body, part)
|
||||||
{
|
&& Containers.Insert(partId, body.RootContainer);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Containers.Insert(partId, body.RootContainer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -413,13 +415,9 @@ public partial class SharedBodySystem
|
|||||||
BodyPartComponent? parentPart = null,
|
BodyPartComponent? parentPart = null,
|
||||||
BodyPartComponent? part = null)
|
BodyPartComponent? part = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(parentPartId, ref parentPart, false) ||
|
return Resolve(parentPartId, ref parentPart, logMissing: false)
|
||||||
!parentPart.Children.TryGetValue(slotId, out var slot))
|
&& parentPart.Children.TryGetValue(slotId, out var slot)
|
||||||
{
|
&& AttachPart(parentPartId, slot, partId, parentPart, part);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return AttachPart(parentPartId, slot, partId, parentPart, part);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -432,10 +430,10 @@ public partial class SharedBodySystem
|
|||||||
BodyPartComponent? parentPart = null,
|
BodyPartComponent? parentPart = null,
|
||||||
BodyPartComponent? part = null)
|
BodyPartComponent? part = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(parentPartId, ref parentPart, false) ||
|
if (!Resolve(parentPartId, ref parentPart, logMissing: false)
|
||||||
!Resolve(partId, ref part, false) ||
|
|| !Resolve(partId, ref part, logMissing: false)
|
||||||
!CanAttachPart(parentPartId, slot.Id, partId, parentPart, part) ||
|
|| !CanAttachPart(parentPartId, slot.Id, partId, parentPart, part)
|
||||||
!parentPart.Children.ContainsKey(slot.Id))
|
|| !parentPart.Children.ContainsKey(slot.Id))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -453,13 +451,16 @@ public partial class SharedBodySystem
|
|||||||
|
|
||||||
#region Misc
|
#region Misc
|
||||||
|
|
||||||
public void UpdateMovementSpeed(EntityUid bodyId, BodyComponent? body = null, MovementSpeedModifierComponent? movement = null)
|
public void UpdateMovementSpeed(
|
||||||
|
EntityUid bodyId,
|
||||||
|
BodyComponent? body = null,
|
||||||
|
MovementSpeedModifierComponent? movement = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(bodyId, ref body, ref movement, logMissing: false)
|
||||||
|
|| body.RequiredLegs <= 0)
|
||||||
{
|
{
|
||||||
if (!Resolve(bodyId, ref body, ref movement, false))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (body.RequiredLegs <= 0)
|
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var walkSpeed = 0f;
|
var walkSpeed = 0f;
|
||||||
var sprintSpeed = 0f;
|
var sprintSpeed = 0f;
|
||||||
@@ -488,7 +489,7 @@ public partial class SharedBodySystem
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<(EntityUid Id, OrganComponent Component)> GetPartOrgans(EntityUid partId, BodyPartComponent? part = null)
|
public IEnumerable<(EntityUid Id, OrganComponent Component)> GetPartOrgans(EntityUid partId, BodyPartComponent? part = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(partId, ref part, false))
|
if (!Resolve(partId, ref part, logMissing: false))
|
||||||
yield break;
|
yield break;
|
||||||
|
|
||||||
foreach (var slotId in part.Organs.Keys)
|
foreach (var slotId in part.Organs.Keys)
|
||||||
@@ -513,7 +514,7 @@ public partial class SharedBodySystem
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<BaseContainer> GetPartContainers(EntityUid id, BodyPartComponent? part = null)
|
public IEnumerable<BaseContainer> GetPartContainers(EntityUid id, BodyPartComponent? part = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(id, ref part, false) ||
|
if (!Resolve(id, ref part, logMissing: false) ||
|
||||||
part.Children.Count == 0)
|
part.Children.Count == 0)
|
||||||
{
|
{
|
||||||
yield break;
|
yield break;
|
||||||
@@ -541,9 +542,11 @@ public partial class SharedBodySystem
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns all body part components for this entity including itself.
|
/// Returns all body part components for this entity including itself.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<(EntityUid Id, BodyPartComponent Component)> GetBodyPartChildren(EntityUid partId, BodyPartComponent? part = null)
|
public IEnumerable<(EntityUid Id, BodyPartComponent Component)> GetBodyPartChildren(
|
||||||
|
EntityUid partId,
|
||||||
|
BodyPartComponent? part = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(partId, ref part, false))
|
if (!Resolve(partId, ref part, logMissing: false))
|
||||||
yield break;
|
yield break;
|
||||||
|
|
||||||
yield return (partId, part);
|
yield return (partId, part);
|
||||||
@@ -571,9 +574,11 @@ public partial class SharedBodySystem
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns all body part slots for this entity.
|
/// Returns all body part slots for this entity.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<BodyPartSlot> GetAllBodyPartSlots(EntityUid partId, BodyPartComponent? part = null)
|
public IEnumerable<BodyPartSlot> GetAllBodyPartSlots(
|
||||||
|
EntityUid partId,
|
||||||
|
BodyPartComponent? part = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(partId, ref part, false))
|
if (!Resolve(partId, ref part, logMissing: false))
|
||||||
yield break;
|
yield break;
|
||||||
|
|
||||||
foreach (var (slotId, slot) in part.Children)
|
foreach (var (slotId, slot) in part.Children)
|
||||||
@@ -601,7 +606,10 @@ public partial class SharedBodySystem
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns true if the bodyId has any parts of this type.
|
/// Returns true if the bodyId has any parts of this type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool BodyHasPartType(EntityUid bodyId, BodyPartType type, BodyComponent? body = null)
|
public bool BodyHasPartType(
|
||||||
|
EntityUid bodyId,
|
||||||
|
BodyPartType type,
|
||||||
|
BodyComponent? body = null)
|
||||||
{
|
{
|
||||||
return GetBodyChildrenOfType(bodyId, type, body).Any();
|
return GetBodyChildrenOfType(bodyId, type, body).Any();
|
||||||
}
|
}
|
||||||
@@ -615,8 +623,8 @@ public partial class SharedBodySystem
|
|||||||
BodyPartComponent? parent,
|
BodyPartComponent? parent,
|
||||||
BodyPartComponent? child)
|
BodyPartComponent? child)
|
||||||
{
|
{
|
||||||
if (!Resolve(parentId, ref parent, false) ||
|
if (!Resolve(parentId, ref parent, logMissing: false)
|
||||||
!Resolve(childId, ref child, false))
|
|| !Resolve(childId, ref child, logMissing: false))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -638,15 +646,11 @@ public partial class SharedBodySystem
|
|||||||
BodyComponent? body = null,
|
BodyComponent? body = null,
|
||||||
BodyPartComponent? part = null)
|
BodyPartComponent? part = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(bodyId, ref body, false) ||
|
return Resolve(bodyId, ref body, logMissing: false)
|
||||||
body.RootContainer.ContainedEntity == null ||
|
&& body.RootContainer.ContainedEntity is not null
|
||||||
!Resolve(partId, ref part, false) ||
|
&& Resolve(partId, ref part, logMissing: false)
|
||||||
!TryComp(body.RootContainer.ContainedEntity, out BodyPartComponent? rootPart))
|
&& TryComp(body.RootContainer.ContainedEntity, out BodyPartComponent? rootPart)
|
||||||
{
|
&& PartHasChild(body.RootContainer.ContainedEntity.Value, partId, rootPart, part);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return PartHasChild(body.RootContainer.ContainedEntity.Value, partId, rootPart, part);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<(EntityUid Id, BodyPartComponent Component)> GetBodyChildrenOfType(
|
public IEnumerable<(EntityUid Id, BodyPartComponent Component)> GetBodyChildrenOfType(
|
||||||
@@ -721,9 +725,11 @@ public partial class SharedBodySystem
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the parent body part and all immediate child body parts for the partId.
|
/// Gets the parent body part and all immediate child body parts for the partId.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<EntityUid> GetBodyPartAdjacentParts(EntityUid partId, BodyPartComponent? part = null)
|
public IEnumerable<EntityUid> GetBodyPartAdjacentParts(
|
||||||
|
EntityUid partId,
|
||||||
|
BodyPartComponent? part = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(partId, ref part, false))
|
if (!Resolve(partId, ref part, logMissing: false))
|
||||||
yield break;
|
yield break;
|
||||||
|
|
||||||
if (TryGetParentBodyPart(partId, out var parentUid, out _))
|
if (TryGetParentBodyPart(partId, out var parentUid, out _))
|
||||||
@@ -745,7 +751,7 @@ public partial class SharedBodySystem
|
|||||||
BodyPartComponent? part = null)
|
BodyPartComponent? part = null)
|
||||||
where T : IComponent
|
where T : IComponent
|
||||||
{
|
{
|
||||||
if (!Resolve(partId, ref part, false))
|
if (!Resolve(partId, ref part, logMissing: false))
|
||||||
yield break;
|
yield break;
|
||||||
|
|
||||||
var query = GetEntityQuery<T>();
|
var query = GetEntityQuery<T>();
|
||||||
@@ -762,7 +768,7 @@ public partial class SharedBodySystem
|
|||||||
BodyPartComponent? part = null)
|
BodyPartComponent? part = null)
|
||||||
where T : IComponent
|
where T : IComponent
|
||||||
{
|
{
|
||||||
if (!Resolve(partId, ref part, false))
|
if (!Resolve(partId, ref part, logMissing: false))
|
||||||
{
|
{
|
||||||
comps = null;
|
comps = null;
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
Reference in New Issue
Block a user