Make mechanism behaviors properly update, fix eating and drinking (#2472)

* Make mechanisms properly update and fix eating and drinking

* Remove outdated component ignores

* Fix nullable error

* Fix mechanism behavior events

* Remove unnecessary code
This commit is contained in:
DrSmugleaf
2020-11-02 11:37:37 +01:00
committed by GitHub
parent 015539dcdc
commit 6b4a39006e
28 changed files with 326 additions and 306 deletions

View File

@@ -1,12 +0,0 @@
using Content.Shared.GameObjects.Components.Body.Behavior;
using Robust.Shared.GameObjects;
namespace Content.Client.GameObjects.Components.Body.Mechanism
{
[RegisterComponent]
[ComponentReference(typeof(SharedHeartBehaviorComponent))]
public class HeartBehaviorComponent : SharedHeartBehaviorComponent
{
public override void Update(float frameTime) { }
}
}

View File

@@ -58,7 +58,6 @@
"Drink", "Drink",
"Food", "Food",
"FoodContainer", "FoodContainer",
"Stomach",
"Rotatable", "Rotatable",
"MagicMirror", "MagicMirror",
"FloorTile", "FloorTile",
@@ -180,10 +179,8 @@
"BreakableConstruction", "BreakableConstruction",
"GasCanister", "GasCanister",
"GasCanisterPort", "GasCanisterPort",
"Lung",
"Cleanable", "Cleanable",
"Configuration", "Configuration",
"Brain",
"PlantHolder", "PlantHolder",
"SeedExtractor", "SeedExtractor",
"Produce", "Produce",

View File

@@ -20,7 +20,7 @@ using Robust.Shared.Maths;
namespace Content.IntegrationTests.Tests.Body namespace Content.IntegrationTests.Tests.Body
{ {
[TestFixture] [TestFixture]
[TestOf(typeof(LungBehaviorComponent))] [TestOf(typeof(LungBehavior))]
public class LungTest : ContentIntegrationTest public class LungTest : ContentIntegrationTest
{ {
[Test] [Test]
@@ -39,7 +39,7 @@ namespace Content.IntegrationTests.Tests.Body
var human = entityManager.SpawnEntity("HumanMob_Content", MapCoordinates.Nullspace); var human = entityManager.SpawnEntity("HumanMob_Content", MapCoordinates.Nullspace);
Assert.That(human.TryGetComponent(out IBody body)); Assert.That(human.TryGetComponent(out IBody body));
Assert.That(body.TryGetMechanismBehaviors(out List<LungBehaviorComponent> lungs)); Assert.That(body.TryGetMechanismBehaviors(out List<LungBehavior> lungs));
Assert.That(lungs.Count, Is.EqualTo(1)); Assert.That(lungs.Count, Is.EqualTo(1));
Assert.That(human.TryGetComponent(out BloodstreamComponent bloodstream)); Assert.That(human.TryGetComponent(out BloodstreamComponent bloodstream));
@@ -141,7 +141,7 @@ namespace Content.IntegrationTests.Tests.Body
human = entityManager.SpawnEntity("HumanMob_Content", coordinates); human = entityManager.SpawnEntity("HumanMob_Content", coordinates);
Assert.True(human.TryGetComponent(out IBody body)); Assert.True(human.TryGetComponent(out IBody body));
Assert.True(body.HasMechanismBehavior<LungBehaviorComponent>()); Assert.True(body.HasMechanismBehavior<LungBehavior>());
Assert.True(human.TryGetComponent(out metabolism)); Assert.True(human.TryGetComponent(out metabolism));
Assert.False(metabolism.Suffocating); Assert.False(metabolism.Suffocating);
}); });

View File

@@ -1,12 +1,12 @@
#nullable enable #nullable enable
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Server.GameObjects.Components.Body.Behavior;
using Content.Shared.GameObjects.Components.Body; using Content.Shared.GameObjects.Components.Body;
using Content.Shared.GameObjects.Components.Body.Behavior; using Content.Shared.GameObjects.Components.Body.Behavior;
using Content.Shared.GameObjects.Components.Body.Mechanism; using Content.Shared.GameObjects.Components.Body.Mechanism;
using Content.Shared.GameObjects.Components.Body.Part; using Content.Shared.GameObjects.Components.Body.Part;
using NUnit.Framework; using NUnit.Framework;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Map; using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC; using Robust.Shared.IoC;
@@ -18,14 +18,11 @@ namespace Content.IntegrationTests.Tests.Body
[TestOf(typeof(SharedBodyComponent))] [TestOf(typeof(SharedBodyComponent))]
[TestOf(typeof(SharedBodyPartComponent))] [TestOf(typeof(SharedBodyPartComponent))]
[TestOf(typeof(SharedMechanismComponent))] [TestOf(typeof(SharedMechanismComponent))]
[TestOf(typeof(MechanismBehaviorComponent))] [TestOf(typeof(MechanismBehavior))]
public class MechanismBehaviorEventsTest : ContentIntegrationTest public class MechanismBehaviorEventsTest : ContentIntegrationTest
{ {
[RegisterComponent] private class TestMechanismBehavior : MechanismBehavior
private class TestBehaviorComponent : MechanismBehaviorComponent
{ {
public override string Name => nameof(MechanismBehaviorEventsTest) + "TestBehavior";
public bool WasAddedToBody; public bool WasAddedToBody;
public bool WasAddedToPart; public bool WasAddedToPart;
public bool WasAddedToPartInBody; public bool WasAddedToPartInBody;
@@ -33,8 +30,6 @@ namespace Content.IntegrationTests.Tests.Body
public bool WasRemovedFromPart; public bool WasRemovedFromPart;
public bool WasRemovedFromPartInBody; public bool WasRemovedFromPartInBody;
public override void Update(float frameTime) { }
public bool NoAdded() public bool NoAdded()
{ {
return !WasAddedToBody && !WasAddedToPart && !WasAddedToPartInBody; return !WasAddedToBody && !WasAddedToPart && !WasAddedToPartInBody;
@@ -111,13 +106,7 @@ namespace Content.IntegrationTests.Tests.Body
[Test] [Test]
public async Task EventsTest() public async Task EventsTest()
{ {
var server = StartServerDummyTicker(new ServerContentIntegrationOption var server = StartServerDummyTicker();
{
ContentBeforeIoC = () =>
{
IoCManager.Resolve<IComponentFactory>().Register<TestBehaviorComponent>();
}
});
await server.WaitAssertion(() => await server.WaitAssertion(() =>
{ {
@@ -141,68 +130,68 @@ namespace Content.IntegrationTests.Tests.Body
var mechanism = centerPart!.Mechanisms.First(); var mechanism = centerPart!.Mechanisms.First();
Assert.NotNull(mechanism); Assert.NotNull(mechanism);
var component = mechanism.Owner.AddComponent<TestBehaviorComponent>(); mechanism.EnsureBehavior<TestMechanismBehavior>(out var behavior);
Assert.False(component.WasAddedToBody); Assert.False(behavior.WasAddedToBody);
Assert.False(component.WasAddedToPart); Assert.False(behavior.WasAddedToPart);
Assert.That(component.WasAddedToPartInBody); Assert.That(behavior.WasAddedToPartInBody);
Assert.That(component.NoRemoved); Assert.That(behavior.NoRemoved);
component.ResetAll(); behavior.ResetAll();
Assert.That(component.NoAdded); Assert.That(behavior.NoAdded);
Assert.That(component.NoRemoved); Assert.That(behavior.NoRemoved);
centerPart.RemoveMechanism(mechanism); centerPart.RemoveMechanism(mechanism);
Assert.That(component.NoAdded); Assert.That(behavior.NoAdded);
Assert.False(component.WasRemovedFromBody); Assert.False(behavior.WasRemovedFromBody);
Assert.False(component.WasRemovedFromPart); Assert.False(behavior.WasRemovedFromPart);
Assert.That(component.WasRemovedFromPartInBody); Assert.That(behavior.WasRemovedFromPartInBody);
component.ResetAll(); behavior.ResetAll();
centerPart.TryAddMechanism(mechanism, true); centerPart.TryAddMechanism(mechanism, true);
Assert.False(component.WasAddedToBody); Assert.False(behavior.WasAddedToBody);
Assert.False(component.WasAddedToPart); Assert.False(behavior.WasAddedToPart);
Assert.That(component.WasAddedToPartInBody); Assert.That(behavior.WasAddedToPartInBody);
Assert.That(component.NoRemoved()); Assert.That(behavior.NoRemoved());
component.ResetAll(); behavior.ResetAll();
body.RemovePart(centerPart); body.RemovePart(centerPart);
Assert.That(component.NoAdded); Assert.That(behavior.NoAdded);
Assert.That(component.WasRemovedFromBody); Assert.That(behavior.WasRemovedFromBody);
Assert.False(component.WasRemovedFromPart); Assert.False(behavior.WasRemovedFromPart);
Assert.False(component.WasRemovedFromPartInBody); Assert.False(behavior.WasRemovedFromPartInBody);
component.ResetAll(); behavior.ResetAll();
centerPart.RemoveMechanism(mechanism); centerPart.RemoveMechanism(mechanism);
Assert.That(component.NoAdded); Assert.That(behavior.NoAdded);
Assert.False(component.WasRemovedFromBody); Assert.False(behavior.WasRemovedFromBody);
Assert.That(component.WasRemovedFromPart); Assert.That(behavior.WasRemovedFromPart);
Assert.False(component.WasRemovedFromPartInBody); Assert.False(behavior.WasRemovedFromPartInBody);
component.ResetAll(); behavior.ResetAll();
centerPart.TryAddMechanism(mechanism, true); centerPart.TryAddMechanism(mechanism, true);
Assert.False(component.WasAddedToBody); Assert.False(behavior.WasAddedToBody);
Assert.That(component.WasAddedToPart); Assert.That(behavior.WasAddedToPart);
Assert.False(component.WasAddedToPartInBody); Assert.False(behavior.WasAddedToPartInBody);
Assert.That(component.NoRemoved); Assert.That(behavior.NoRemoved);
component.ResetAll(); behavior.ResetAll();
body.TryAddPart(centerSlot!, centerPart, true); body.TryAddPart(centerSlot!, centerPart, true);
Assert.That(component.WasAddedToBody); Assert.That(behavior.WasAddedToBody);
Assert.False(component.WasAddedToPart); Assert.False(behavior.WasAddedToPart);
Assert.False(component.WasAddedToPartInBody); Assert.False(behavior.WasAddedToPartInBody);
Assert.That(component.NoRemoved); Assert.That(behavior.NoRemoved);
}); });
} }
} }

View File

@@ -0,0 +1,39 @@
#nullable enable
using Content.Server.GameObjects.Components.Nutrition;
using Content.Shared.GameObjects.Components.Nutrition;
using Robust.Server.Interfaces.Console;
using Robust.Server.Interfaces.Player;
namespace Content.Server.Commands
{
public class Hungry : IClientCommand
{
public string Command => "hungry";
public string Description => "Makes you hungry.";
public string Help => $"{Command}";
public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args)
{
if (player == null)
{
shell.SendText(player, "You cannot use this command unless you are a player.");
return;
}
if (player.AttachedEntity == null)
{
shell.SendText(player, "You cannot use this command without an entity.");
return;
}
if (!player.AttachedEntity.TryGetComponent(out HungerComponent? hunger))
{
shell.SendText(player, $"Your entity does not have a {nameof(HungerComponent)} component.");
return;
}
var hungryThreshold = hunger.HungerThresholds[HungerThreshold.Starving];
hunger.CurrentHunger = hungryThreshold;
}
}
}

View File

@@ -1,22 +1,14 @@
#nullable enable #nullable enable
using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Mobs;
using Content.Server.Mobs;
using Content.Shared.GameObjects.Components.Body; using Content.Shared.GameObjects.Components.Body;
using Content.Shared.GameObjects.Components.Body.Behavior;
using Content.Shared.GameObjects.Components.Body.Part; using Content.Shared.GameObjects.Components.Body.Part;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.ComponentDependencies;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Log;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Body.Behavior namespace Content.Server.GameObjects.Components.Body.Behavior
{ {
[RegisterComponent] public class BrainBehavior : MechanismBehavior
public class BrainBehaviorComponent : MechanismBehaviorComponent
{ {
public override string Name => "Brain";
protected override void OnAddedToBody(IBody body) protected override void OnAddedToBody(IBody body)
{ {
base.OnAddedToBody(body); base.OnAddedToBody(body);
@@ -66,9 +58,5 @@ namespace Content.Server.GameObjects.Components.Body.Behavior
oldMind.Mind?.TransferTo(newEntity); oldMind.Mind?.TransferTo(newEntity);
} }
public override void Update(float frameTime)
{
}
} }
} }

View File

@@ -1,20 +1,17 @@
using Content.Shared.GameObjects.Components.Body.Behavior; using Content.Shared.GameObjects.Components.Body.Behavior;
using Content.Shared.GameObjects.Components.Body.Networks; using Content.Shared.GameObjects.Components.Body.Networks;
using Robust.Shared.GameObjects;
namespace Content.Server.GameObjects.Components.Body.Behavior namespace Content.Server.GameObjects.Components.Body.Behavior
{ {
[RegisterComponent] public class HeartBehavior : MechanismBehavior
[ComponentReference(typeof(SharedHeartBehaviorComponent))]
public class HeartBehaviorComponent : SharedHeartBehaviorComponent
{ {
private float _accumulatedFrameTime; private float _accumulatedFrameTime;
public override void Update(float frameTime) public override void Update(float frameTime)
{ {
// TODO BODY do between pre and metabolism // TODO BODY do between pre and metabolism
if (Mechanism?.Body == null || if (Parent.Body == null ||
!Mechanism.Body.Owner.HasComponent<SharedBloodstreamComponent>()) !Parent.Body.Owner.HasComponent<SharedBloodstreamComponent>())
{ {
return; return;
} }

View File

@@ -8,19 +8,15 @@ using Content.Server.GameObjects.Components.Body.Respiratory;
using Content.Server.Utility; using Content.Server.Utility;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.GameObjects.Components.Body.Behavior; using Content.Shared.GameObjects.Components.Body.Behavior;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.Timing; using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Body.Behavior namespace Content.Server.GameObjects.Components.Body.Behavior
{ {
[RegisterComponent] public class LungBehavior : MechanismBehavior
[ComponentReference(typeof(SharedLungBehaviorComponent))]
public class LungBehaviorComponent : SharedLungBehaviorComponent
{ {
[Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IGameTiming _gameTiming = default!;
@@ -30,18 +26,24 @@ namespace Content.Server.GameObjects.Components.Body.Behavior
[ViewVariables] public GasMixture Air { get; set; } = default!; [ViewVariables] public GasMixture Air { get; set; } = default!;
[ViewVariables] public override float Temperature => Air.Temperature; [ViewVariables] public float Temperature => Air.Temperature;
[ViewVariables] public override float Volume => Air.Volume; [ViewVariables] public float Volume => Air.Volume;
[ViewVariables] public TimeSpan GaspPopupCooldown { get; private set; } [ViewVariables] public TimeSpan GaspPopupCooldown { get; private set; }
[ViewVariables] public LungStatus Status { get; set; }
[ViewVariables] public float CycleDelay { get; set; }
public override void ExposeData(ObjectSerializer serializer) public override void ExposeData(ObjectSerializer serializer)
{ {
base.ExposeData(serializer); base.ExposeData(serializer);
Air = new GasMixture {Temperature = Atmospherics.NormalBodyTemperature}; Air = new GasMixture {Temperature = Atmospherics.NormalBodyTemperature};
serializer.DataField(this, l => l.CycleDelay, "cycleDelay", 2);
serializer.DataReadWriteFunction( serializer.DataReadWriteFunction(
"volume", "volume",
6, 6,
@@ -61,7 +63,7 @@ namespace Content.Server.GameObjects.Components.Body.Behavior
() => GaspPopupCooldown.TotalSeconds); () => GaspPopupCooldown.TotalSeconds);
} }
public override void Gasp() public void Gasp()
{ {
if (_gameTiming.CurTime >= _lastGaspPopupTime + GaspPopupCooldown) if (_gameTiming.CurTime >= _lastGaspPopupTime + GaspPopupCooldown)
{ {
@@ -148,7 +150,7 @@ namespace Content.Server.GameObjects.Components.Body.Behavior
_accumulatedFrameTime = absoluteTime - delay; _accumulatedFrameTime = absoluteTime - delay;
} }
public override void Inhale(float frameTime) public void Inhale(float frameTime)
{ {
if (Body != null && Body.Owner.TryGetComponent(out InternalsComponent? internals) if (Body != null && Body.Owner.TryGetComponent(out InternalsComponent? internals)
&& internals.BreathToolEntity != null && internals.GasTankEntity != null && internals.BreathToolEntity != null && internals.GasTankEntity != null
@@ -176,7 +178,7 @@ namespace Content.Server.GameObjects.Components.Body.Behavior
ToBloodstream(Air); ToBloodstream(Air);
} }
public override void Exhale(float frameTime) public void Exhale(float frameTime)
{ {
if (!Owner.Transform.Coordinates.TryGetTileAir(out var tileAir)) if (!Owner.Transform.Coordinates.TryGetTileAir(out var tileAir))
{ {
@@ -218,4 +220,11 @@ namespace Content.Server.GameObjects.Components.Body.Behavior
Air.Merge(lungRemoved); Air.Merge(lungRemoved);
} }
} }
public enum LungStatus
{
None = 0,
Inhaling,
Exhaling
}
} }

View File

@@ -1,23 +1,33 @@
#nullable enable #nullable enable
using Content.Shared.GameObjects.Components.Body;
using Content.Shared.GameObjects.Components.Body.Behavior;
using Content.Shared.GameObjects.Components.Body.Mechanism; using Content.Shared.GameObjects.Components.Body.Mechanism;
using Content.Shared.GameObjects.Components.Body.Part; using Content.Shared.GameObjects.Components.Body.Part;
using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Serialization;
using Robust.Shared.Utility; using Robust.Shared.Utility;
namespace Content.Shared.GameObjects.Components.Body.Behavior namespace Content.Server.GameObjects.Components.Body.Behavior
{ {
public abstract class MechanismBehaviorComponent : Component, IMechanismBehavior public abstract class MechanismBehavior : IMechanismBehavior
{ {
public IBody? Body => Part?.Body; public IBody? Body => Part?.Body;
public IBodyPart? Part => Mechanism?.Part; public IBodyPart? Part => Parent.Part;
public IMechanism? Mechanism => Owner.GetComponentOrNull<IMechanism>(); public IMechanism Parent { get; private set; } = default!;
protected override void Startup() public IEntity Owner => Parent.Owner;
public virtual void ExposeData(ObjectSerializer serializer) { }
public virtual void Initialize(IMechanism parent)
{ {
base.Startup(); Parent = parent;
}
public virtual void Startup()
{
if (Part == null) if (Part == null)
{ {
return; return;
@@ -33,8 +43,6 @@ namespace Content.Shared.GameObjects.Components.Body.Behavior
} }
} }
public abstract void Update(float frameTime);
public void AddedToBody(IBody body) public void AddedToBody(IBody body)
{ {
DebugTools.AssertNotNull(Body); DebugTools.AssertNotNull(Body);
@@ -98,5 +106,7 @@ namespace Content.Shared.GameObjects.Components.Body.Behavior
protected virtual void OnRemovedFromPart(IBodyPart old) { } protected virtual void OnRemovedFromPart(IBodyPart old) { }
protected virtual void OnRemovedFromPartInBody(IBody oldBody, IBodyPart oldPart) { } protected virtual void OnRemovedFromPartInBody(IBody oldBody, IBodyPart oldPart) { }
public virtual void Update(float frameTime) { }
} }
} }

View File

@@ -2,33 +2,29 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using Content.Shared.GameObjects.Components.Body;
using Content.Shared.GameObjects.Components.Body.Behavior; using Content.Shared.GameObjects.Components.Body.Behavior;
using Content.Shared.GameObjects.Components.Body.Part; using Content.Shared.GameObjects.Components.Body.Part;
namespace Content.Shared.GameObjects.Components.Body.Mechanism namespace Content.Server.GameObjects.Components.Body.Behavior
{ {
public static class MechanismExtensions public static class MechanismExtensions
{ {
public static bool HasMechanismBehavior<T>(this IBody body) public static bool HasMechanismBehavior<T>(this IBody body) where T : IMechanismBehavior
{ {
return body.Parts.Values.Any(p => p.HasMechanismBehavior<T>()); return body.Parts.Values.Any(p => p.HasMechanismBehavior<T>());
} }
public static bool HasMechanismBehavior<T>(this IBodyPart part) public static bool HasMechanismBehavior<T>(this IBodyPart part) where T : IMechanismBehavior
{ {
return part.Mechanisms.Any(m => m.Owner.HasComponent<T>()); return part.Mechanisms.Any(m => m.HasBehavior<T>());
}
public static bool HasMechanismBehavior<T>(this IMechanism mechanism)
{
return mechanism.Owner.HasComponent<T>();
} }
public static IEnumerable<IMechanismBehavior> GetMechanismBehaviors(this IBody body) public static IEnumerable<IMechanismBehavior> GetMechanismBehaviors(this IBody body)
{ {
foreach (var part in body.Parts.Values) foreach (var part in body.Parts.Values)
foreach (var mechanism in part.Mechanisms) foreach (var mechanism in part.Mechanisms)
foreach (var behavior in mechanism.Owner.GetAllComponents<IMechanismBehavior>()) foreach (var behavior in mechanism.Behaviors.Values)
{ {
yield return behavior; yield return behavior;
} }
@@ -52,10 +48,11 @@ namespace Content.Shared.GameObjects.Components.Body.Mechanism
{ {
foreach (var part in body.Parts.Values) foreach (var part in body.Parts.Values)
foreach (var mechanism in part.Mechanisms) foreach (var mechanism in part.Mechanisms)
foreach (var behavior in mechanism.Behaviors.Values)
{ {
if (mechanism.Owner.TryGetComponent(out T? behavior)) if (behavior is T tBehavior)
{ {
yield return behavior; yield return tBehavior;
} }
} }
} }

View File

@@ -1,23 +1,23 @@
#nullable enable #nullable enable
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Content.Server.GameObjects.Components.Chemistry;
using Content.Shared.Chemistry; using Content.Shared.Chemistry;
using Content.Shared.GameObjects.Components.Body.Networks; using Content.Shared.GameObjects.Components.Body.Networks;
using Content.Shared.GameObjects.Components.Chemistry; using Content.Shared.GameObjects.Components.Chemistry;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Log;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
namespace Content.Shared.GameObjects.Components.Body.Behavior namespace Content.Server.GameObjects.Components.Body.Behavior
{ {
/// <summary> /// <summary>
/// Where reagents go when ingested. Tracks ingested reagents over time, and /// Where reagents go when ingested. Tracks ingested reagents over time, and
/// eventually transfers them to <see cref="SharedBloodstreamComponent"/> once digested. /// eventually transfers them to <see cref="SharedBloodstreamComponent"/> once digested.
/// </summary> /// </summary>
public abstract class SharedStomachBehaviorComponent : MechanismBehaviorComponent public class StomachBehavior : MechanismBehavior
{ {
public override string Name => "Stomach";
private float _accumulatedFrameTime; private float _accumulatedFrameTime;
/// <summary> /// <summary>
@@ -45,7 +45,7 @@ namespace Content.Shared.GameObjects.Components.Body.Behavior
_accumulatedFrameTime -= 1; _accumulatedFrameTime -= 1;
if (!Body.Owner.TryGetComponent(out SharedSolutionContainerComponent? solution) || if (!Owner.TryGetComponent(out SharedSolutionContainerComponent? solution) ||
!Body.Owner.TryGetComponent(out SharedBloodstreamComponent? bloodstream)) !Body.Owner.TryGetComponent(out SharedBloodstreamComponent? bloodstream))
{ {
return; return;
@@ -58,7 +58,7 @@ namespace Content.Shared.GameObjects.Components.Body.Behavior
foreach (var delta in _reagentDeltas.ToList()) foreach (var delta in _reagentDeltas.ToList())
{ {
//Increment lifetime of reagents //Increment lifetime of reagents
delta.Increment(frameTime); delta.Increment(1);
if (delta.Lifetime > _digestionDelay) if (delta.Lifetime > _digestionDelay)
{ {
solution.TryRemoveReagent(delta.ReagentId, delta.Quantity); solution.TryRemoveReagent(delta.ReagentId, delta.Quantity);
@@ -112,6 +112,18 @@ namespace Content.Shared.GameObjects.Components.Body.Behavior
serializer.DataField(ref _digestionDelay, "digestionDelay", 20); serializer.DataField(ref _digestionDelay, "digestionDelay", 20);
} }
public override void Startup()
{
base.Startup();
if (!Owner.EnsureComponent(out SolutionContainerComponent solution))
{
Logger.Warning($"Entity {Owner} at {Owner.Transform.MapPosition} didn't have a {nameof(SolutionContainerComponent)}");
}
solution.MaxVolume = InitialMaxVolume;
}
public bool CanTransferSolution(Solution solution) public bool CanTransferSolution(Solution solution)
{ {
if (!Owner.TryGetComponent(out SharedSolutionContainerComponent? solutionComponent)) if (!Owner.TryGetComponent(out SharedSolutionContainerComponent? solutionComponent))

View File

@@ -1,24 +0,0 @@
using Content.Server.GameObjects.Components.Chemistry;
using Content.Shared.GameObjects.Components.Body.Behavior;
using Robust.Shared.GameObjects;
using Robust.Shared.Log;
namespace Content.Server.GameObjects.Components.Body.Behavior
{
[RegisterComponent]
[ComponentReference(typeof(SharedStomachBehaviorComponent))]
public class StomachBehaviorComponent : SharedStomachBehaviorComponent
{
protected override void Startup()
{
base.Startup();
if (!Owner.EnsureComponent(out SolutionContainerComponent solution))
{
Logger.Warning($"Entity {Owner} at {Owner.Transform.MapPosition} didn't have a {nameof(SolutionContainerComponent)}");
}
solution.MaxVolume = InitialMaxVolume;
}
}
}

View File

@@ -84,7 +84,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
var trueTarget = target ?? user; var trueTarget = target ?? user;
if (!trueTarget.TryGetComponent(out IBody body) || if (!trueTarget.TryGetComponent(out IBody body) ||
!body.TryGetMechanismBehaviors<StomachBehaviorComponent>(out var stomachs)) !body.TryGetMechanismBehaviors<StomachBehavior>(out var stomachs))
{ {
return false; return false;
} }

View File

@@ -192,7 +192,7 @@ namespace Content.Server.GameObjects.Components.Metabolism
return; return;
} }
var lungs = _body.GetMechanismBehaviors<LungBehaviorComponent>().ToArray(); var lungs = _body.GetMechanismBehaviors<LungBehavior>().ToArray();
var needs = NeedsAndDeficit(frameTime); var needs = NeedsAndDeficit(frameTime);
var used = 0f; var used = 0f;

View File

@@ -153,7 +153,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
} }
if (!target.TryGetComponent(out IBody body) || if (!target.TryGetComponent(out IBody body) ||
!body.TryGetMechanismBehaviors<StomachBehaviorComponent>(out var stomachs)) !body.TryGetMechanismBehaviors<StomachBehavior>(out var stomachs))
{ {
return false; return false;
} }

View File

@@ -2,6 +2,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Content.Server.GameObjects.Components.Body.Behavior;
using Content.Server.GameObjects.Components.Chemistry; using Content.Server.GameObjects.Components.Chemistry;
using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.GUI;
using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Items.Storage;
@@ -133,7 +134,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
var trueTarget = target ?? user; var trueTarget = target ?? user;
if (!trueTarget.TryGetComponent(out IBody? body) || if (!trueTarget.TryGetComponent(out IBody? body) ||
!body.TryGetMechanismBehaviors<SharedStomachBehaviorComponent>(out var stomachs)) !body.TryGetMechanismBehaviors<StomachBehavior>(out var stomachs))
{ {
return false; return false;
} }

View File

@@ -1,28 +0,0 @@
using Content.Shared.GameObjects.Components.Body.Behavior;
using Content.Shared.GameObjects.EntitySystems;
using JetBrains.Annotations;
using Robust.Shared.GameObjects.Systems;
namespace Content.Server.GameObjects.EntitySystems
{
[UsedImplicitly]
public class HeartSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
UpdatesBefore.Add(typeof(MetabolismSystem));
}
public override void Update(float frameTime)
{
base.Update(frameTime);
foreach (var heart in ComponentManager.EntityQuery<SharedHeartBehaviorComponent>())
{
heart.Update(frameTime);
}
}
}
}

View File

@@ -1,28 +0,0 @@
using Content.Shared.GameObjects.Components.Body.Behavior;
using Content.Shared.GameObjects.EntitySystems;
using JetBrains.Annotations;
using Robust.Shared.GameObjects.Systems;
namespace Content.Server.GameObjects.EntitySystems
{
[UsedImplicitly]
public class LungSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
UpdatesBefore.Add(typeof(MetabolismSystem));
}
public override void Update(float frameTime)
{
base.Update(frameTime);
foreach (var lung in ComponentManager.EntityQuery<SharedLungBehaviorComponent>())
{
lung.Update(frameTime);
}
}
}
}

View File

@@ -2,10 +2,11 @@
using Content.Shared.GameObjects.Components.Body.Mechanism; using Content.Shared.GameObjects.Components.Body.Mechanism;
using Content.Shared.GameObjects.Components.Body.Part; using Content.Shared.GameObjects.Components.Body.Part;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Serialization;
namespace Content.Shared.GameObjects.Components.Body.Behavior namespace Content.Shared.GameObjects.Components.Body.Behavior
{ {
public interface IMechanismBehavior : IComponent public interface IMechanismBehavior : IExposeData
{ {
IBody? Body { get; } IBody? Body { get; }
@@ -15,7 +16,20 @@ namespace Content.Shared.GameObjects.Components.Body.Behavior
/// Upward reference to the parent <see cref="IMechanism"/> that this /// Upward reference to the parent <see cref="IMechanism"/> that this
/// behavior is attached to. /// behavior is attached to.
/// </summary> /// </summary>
IMechanism? Mechanism { get; } IMechanism Parent { get; }
/// <summary>
/// The entity that owns <see cref="Parent"/>.
/// For the entity owning the body that this mechanism may be in,
/// see <see cref="IBody.Owner"/>
/// </summary>
IEntity Owner { get; }
void Initialize(IMechanism parent);
void Startup();
void Update(float frameTime);
/// <summary> /// <summary>
/// Called when the containing <see cref="IBodyPart"/> is attached to a /// Called when the containing <see cref="IBodyPart"/> is attached to a

View File

@@ -1,8 +0,0 @@
#nullable enable
namespace Content.Shared.GameObjects.Components.Body.Behavior
{
public abstract class SharedHeartBehaviorComponent : MechanismBehaviorComponent
{
public override string Name => "Heart";
}
}

View File

@@ -1,39 +0,0 @@
#nullable enable
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Shared.GameObjects.Components.Body.Behavior
{
public abstract class SharedLungBehaviorComponent : MechanismBehaviorComponent
{
public override string Name => "Lung";
[ViewVariables] public abstract float Temperature { get; }
[ViewVariables] public abstract float Volume { get; }
[ViewVariables] public LungStatus Status { get; set; }
[ViewVariables] public float CycleDelay { get; set; }
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(this, l => l.CycleDelay, "cycleDelay", 2);
}
public abstract void Inhale(float frameTime);
public abstract void Exhale(float frameTime);
public abstract void Gasp();
}
public enum LungStatus
{
None = 0,
Inhaling,
Exhaling
}
}

View File

@@ -1,4 +1,7 @@
#nullable enable #nullable enable
using System;
using System.Collections.Generic;
using Content.Shared.GameObjects.Components.Body.Behavior;
using Content.Shared.GameObjects.Components.Body.Part; using Content.Shared.GameObjects.Components.Body.Part;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
@@ -10,6 +13,8 @@ namespace Content.Shared.GameObjects.Components.Body.Mechanism
IBodyPart? Part { get; set; } IBodyPart? Part { get; set; }
IReadOnlyDictionary<Type, IMechanismBehavior> Behaviors { get; }
/// <summary> /// <summary>
/// Professional description of the <see cref="IMechanism"/>. /// Professional description of the <see cref="IMechanism"/>.
/// </summary> /// </summary>
@@ -55,6 +60,21 @@ namespace Content.Shared.GameObjects.Components.Body.Mechanism
/// </summary> /// </summary>
BodyPartCompatibility Compatibility { get; set; } BodyPartCompatibility Compatibility { get; set; }
/// <summary>
/// Adds a behavior if it does not exist already.
/// </summary>
/// <typeparam name="T">The behavior type to add.</typeparam>
/// <returns>
/// True if the behavior already existed, false if it had to be created.
/// </returns>
bool EnsureBehavior<T>(out T behavior) where T : IMechanismBehavior, new();
bool HasBehavior<T>() where T : IMechanismBehavior;
bool TryRemoveBehavior<T>() where T : IMechanismBehavior;
void Update(float frameTime);
// TODO BODY Turn these into event listeners so they dont need to be exposed // TODO BODY Turn these into event listeners so they dont need to be exposed
/// <summary> /// <summary>
/// Called when the containing <see cref="IBodyPart"/> is attached to a /// Called when the containing <see cref="IBodyPart"/> is attached to a

View File

@@ -1,10 +1,14 @@
#nullable enable #nullable enable
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Content.Shared.GameObjects.Components.Body.Behavior; using Content.Shared.GameObjects.Components.Body.Behavior;
using Content.Shared.GameObjects.Components.Body.Part; using Content.Shared.GameObjects.Components.Body.Part;
using Content.Shared.Interfaces;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Robust.Shared.Utility; using Robust.Shared.Utility;
@@ -14,11 +18,12 @@ namespace Content.Shared.GameObjects.Components.Body.Mechanism
{ {
public override string Name => "Mechanism"; public override string Name => "Mechanism";
private IBodyPart? _part;
protected readonly Dictionary<int, object> OptionsCache = new Dictionary<int, object>(); protected readonly Dictionary<int, object> OptionsCache = new Dictionary<int, object>();
protected IBody? BodyCache; protected IBody? BodyCache;
protected int IdHash; protected int IdHash;
protected IEntity? PerformerCache; protected IEntity? PerformerCache;
private IBodyPart? _part;
private readonly Dictionary<Type, IMechanismBehavior> _behaviors = new Dictionary<Type, IMechanismBehavior>();
public IBody? Body => Part?.Body; public IBody? Body => Part?.Body;
@@ -61,6 +66,8 @@ namespace Content.Shared.GameObjects.Components.Body.Mechanism
} }
} }
public IReadOnlyDictionary<Type, IMechanismBehavior> Behaviors => _behaviors;
public string Description { get; set; } = string.Empty; public string Description { get; set; } = string.Empty;
public string ExamineMessage { get; set; } = string.Empty; public string ExamineMessage { get; set; } = string.Empty;
@@ -98,6 +105,91 @@ namespace Content.Shared.GameObjects.Components.Body.Mechanism
serializer.DataField(this, m => m.Size, "size", 1); serializer.DataField(this, m => m.Size, "size", 1);
serializer.DataField(this, m => m.Compatibility, "compatibility", BodyPartCompatibility.Universal); serializer.DataField(this, m => m.Compatibility, "compatibility", BodyPartCompatibility.Universal);
var moduleManager = IoCManager.Resolve<IModuleManager>();
if (moduleManager.IsServerModule)
{
serializer.DataReadWriteFunction(
"behaviors",
null!,
behaviors =>
{
if (behaviors == null)
{
return;
}
foreach (var behavior in behaviors)
{
var type = behavior.GetType();
if (!_behaviors.TryAdd(type, behavior))
{
Logger.Warning($"Duplicate behavior in {nameof(SharedMechanismComponent)} for entity {Owner.Name}: {type}.");
continue;
}
IoCManager.InjectDependencies(behavior);
}
},
() => _behaviors.Values.ToList());
}
}
public override void Initialize()
{
base.Initialize();
foreach (var behavior in _behaviors.Values)
{
behavior.Initialize(this);
}
}
protected override void Startup()
{
base.Startup();
foreach (var behavior in _behaviors.Values)
{
behavior.Startup();
}
}
public bool EnsureBehavior<T>(out T behavior) where T : IMechanismBehavior, new()
{
if (_behaviors.TryGetValue(typeof(T), out var rawBehavior))
{
behavior = (T) rawBehavior;
return true;
}
behavior = new T();
IoCManager.InjectDependencies(behavior);
_behaviors.Add(typeof(T), behavior);
behavior.Initialize(this);
behavior.Startup();
return false;
}
public bool HasBehavior<T>() where T : IMechanismBehavior
{
return _behaviors.ContainsKey(typeof(T));
}
public bool TryRemoveBehavior<T>() where T : IMechanismBehavior
{
return _behaviors.Remove(typeof(T));
}
public void Update(float frameTime)
{
foreach (var behavior in _behaviors.Values)
{
behavior.Update(frameTime);
}
} }
public void AddedToBody(IBody body) public void AddedToBody(IBody body)
@@ -105,9 +197,7 @@ namespace Content.Shared.GameObjects.Components.Body.Mechanism
DebugTools.AssertNotNull(Body); DebugTools.AssertNotNull(Body);
DebugTools.AssertNotNull(body); DebugTools.AssertNotNull(body);
OnAddedToBody(body); foreach (var behavior in _behaviors.Values)
foreach (var behavior in Owner.GetAllComponents<IMechanismBehavior>())
{ {
behavior.AddedToBody(body); behavior.AddedToBody(body);
} }
@@ -119,9 +209,8 @@ namespace Content.Shared.GameObjects.Components.Body.Mechanism
DebugTools.AssertNotNull(part); DebugTools.AssertNotNull(part);
Owner.Transform.AttachParent(part.Owner); Owner.Transform.AttachParent(part.Owner);
OnAddedToPart(part);
foreach (var behavior in Owner.GetAllComponents<IMechanismBehavior>().ToArray()) foreach (var behavior in _behaviors.Values)
{ {
behavior.AddedToPart(part); behavior.AddedToPart(part);
} }
@@ -135,9 +224,8 @@ namespace Content.Shared.GameObjects.Components.Body.Mechanism
DebugTools.AssertNotNull(part); DebugTools.AssertNotNull(part);
Owner.Transform.AttachParent(part.Owner); Owner.Transform.AttachParent(part.Owner);
OnAddedToPartInBody(body, part);
foreach (var behavior in Owner.GetAllComponents<IMechanismBehavior>()) foreach (var behavior in _behaviors.Values)
{ {
behavior.AddedToPartInBody(body, part); behavior.AddedToPartInBody(body, part);
} }
@@ -148,9 +236,7 @@ namespace Content.Shared.GameObjects.Components.Body.Mechanism
DebugTools.AssertNull(Body); DebugTools.AssertNull(Body);
DebugTools.AssertNotNull(old); DebugTools.AssertNotNull(old);
OnRemovedFromBody(old); foreach (var behavior in _behaviors.Values)
foreach (var behavior in Owner.GetAllComponents<IMechanismBehavior>())
{ {
behavior.RemovedFromBody(old); behavior.RemovedFromBody(old);
} }
@@ -162,9 +248,8 @@ namespace Content.Shared.GameObjects.Components.Body.Mechanism
DebugTools.AssertNotNull(old); DebugTools.AssertNotNull(old);
Owner.Transform.AttachToGridOrMap(); Owner.Transform.AttachToGridOrMap();
OnRemovedFromPart(old);
foreach (var behavior in Owner.GetAllComponents<IMechanismBehavior>()) foreach (var behavior in _behaviors.Values)
{ {
behavior.RemovedFromPart(old); behavior.RemovedFromPart(old);
} }
@@ -178,24 +263,11 @@ namespace Content.Shared.GameObjects.Components.Body.Mechanism
DebugTools.AssertNotNull(oldPart); DebugTools.AssertNotNull(oldPart);
Owner.Transform.AttachToGridOrMap(); Owner.Transform.AttachToGridOrMap();
OnRemovedFromPartInBody(oldBody, oldPart);
foreach (var behavior in Owner.GetAllComponents<IMechanismBehavior>()) foreach (var behavior in _behaviors.Values)
{ {
behavior.RemovedFromPartInBody(oldBody, oldPart); behavior.RemovedFromPartInBody(oldBody, oldPart);
} }
} }
protected virtual void OnAddedToBody(IBody body) { }
protected virtual void OnAddedToPart(IBodyPart part) { }
protected virtual void OnAddedToPartInBody(IBody body, IBodyPart part) { }
protected virtual void OnRemovedFromBody(IBody old) { }
protected virtual void OnRemovedFromPart(IBodyPart old) { }
protected virtual void OnRemovedFromPartInBody(IBody oldBody, IBodyPart oldPart) { }
} }
} }

View File

@@ -1,12 +0,0 @@
using Robust.Shared.GameObjects;
namespace Content.Shared.GameObjects.Components.Nutrition
{
/// <summary>
/// Shared class for stomach components
/// </summary>
public class SharedStomachComponent : Component
{
public override string Name => "Stomach";
}
}

View File

@@ -0,0 +1,21 @@
using Content.Shared.GameObjects.Components.Body.Behavior;
using Content.Shared.GameObjects.Components.Body.Mechanism;
using JetBrains.Annotations;
using Robust.Shared.GameObjects.Systems;
namespace Content.Shared.GameObjects.EntitySystems
{
[UsedImplicitly]
public class MechanismSystem : EntitySystem
{
public override void Update(float frameTime)
{
base.Update(frameTime);
foreach (var mechanism in ComponentManager.EntityQuery<IMechanism>())
{
mechanism.Update(frameTime);
}
}
}
}

View File

@@ -119,6 +119,7 @@
- attachtogrid - attachtogrid
- attachtograndparent - attachtograndparent
- inrangeunoccluded - inrangeunoccluded
- hungry
CanViewVar: true CanViewVar: true
CanAdminPlace: true CanAdminPlace: true
CanAdminMenu: true CanAdminMenu: true
@@ -231,6 +232,7 @@
- attachtogrid - attachtogrid
- attachtograndparent - attachtograndparent
- inrangeunoccluded - inrangeunoccluded
- hungry
CanViewVar: true CanViewVar: true
CanAdminPlace: true CanAdminPlace: true
CanScript: true CanScript: true

View File

@@ -11,7 +11,8 @@
durability: 10 durability: 10
size: 1 size: 1
compatibility: Biological compatibility: Biological
- type: Brain behaviors:
- !type:BrainBehavior {}
- type: entity - type: entity
id: EyesHuman id: EyesHuman
@@ -40,7 +41,8 @@
durability: 10 durability: 10
size: 1 size: 1
compatibility: Biological compatibility: Biological
- type: Heart behaviors:
- !type:HeartBehavior {}
- type: entity - type: entity
id: LungsHuman id: LungsHuman
@@ -55,7 +57,8 @@
durability: 13 durability: 13
size: 1 size: 1
compatibility: Biological compatibility: Biological
- type: Lung behaviors:
- !type:LungBehavior {}
- type: entity - type: entity
id: StomachHuman id: StomachHuman
@@ -70,7 +73,10 @@
durability: 13 durability: 13
size: 1 size: 1
compatibility: Biological compatibility: Biological
- type: Stomach behaviors:
- !type:StomachBehavior
max_volume: 250
digestionDelay: 20
- type: entity - type: entity
id: LiverHuman id: LiverHuman

View File

@@ -23,9 +23,6 @@
caps: AddTo, RemoveFrom, NoExamine caps: AddTo, RemoveFrom, NoExamine
- type: Bloodstream - type: Bloodstream
max_volume: 100 max_volume: 100
- type: Stomach
max_volume: 250
digestionDelay: 20
# StatusEffects # StatusEffects
- type: Stunnable - type: Stunnable
# Other # Other