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",
"Food",
"FoodContainer",
"Stomach",
"Rotatable",
"MagicMirror",
"FloorTile",
@@ -180,10 +179,8 @@
"BreakableConstruction",
"GasCanister",
"GasCanisterPort",
"Lung",
"Cleanable",
"Configuration",
"Brain",
"PlantHolder",
"SeedExtractor",
"Produce",

View File

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

View File

@@ -1,12 +1,12 @@
#nullable enable
using System.Linq;
using System.Threading.Tasks;
using Content.Server.GameObjects.Components.Body.Behavior;
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.Part;
using NUnit.Framework;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC;
@@ -18,14 +18,11 @@ namespace Content.IntegrationTests.Tests.Body
[TestOf(typeof(SharedBodyComponent))]
[TestOf(typeof(SharedBodyPartComponent))]
[TestOf(typeof(SharedMechanismComponent))]
[TestOf(typeof(MechanismBehaviorComponent))]
[TestOf(typeof(MechanismBehavior))]
public class MechanismBehaviorEventsTest : ContentIntegrationTest
{
[RegisterComponent]
private class TestBehaviorComponent : MechanismBehaviorComponent
private class TestMechanismBehavior : MechanismBehavior
{
public override string Name => nameof(MechanismBehaviorEventsTest) + "TestBehavior";
public bool WasAddedToBody;
public bool WasAddedToPart;
public bool WasAddedToPartInBody;
@@ -33,8 +30,6 @@ namespace Content.IntegrationTests.Tests.Body
public bool WasRemovedFromPart;
public bool WasRemovedFromPartInBody;
public override void Update(float frameTime) { }
public bool NoAdded()
{
return !WasAddedToBody && !WasAddedToPart && !WasAddedToPartInBody;
@@ -111,13 +106,7 @@ namespace Content.IntegrationTests.Tests.Body
[Test]
public async Task EventsTest()
{
var server = StartServerDummyTicker(new ServerContentIntegrationOption
{
ContentBeforeIoC = () =>
{
IoCManager.Resolve<IComponentFactory>().Register<TestBehaviorComponent>();
}
});
var server = StartServerDummyTicker();
await server.WaitAssertion(() =>
{
@@ -141,68 +130,68 @@ namespace Content.IntegrationTests.Tests.Body
var mechanism = centerPart!.Mechanisms.First();
Assert.NotNull(mechanism);
var component = mechanism.Owner.AddComponent<TestBehaviorComponent>();
Assert.False(component.WasAddedToBody);
Assert.False(component.WasAddedToPart);
Assert.That(component.WasAddedToPartInBody);
Assert.That(component.NoRemoved);
mechanism.EnsureBehavior<TestMechanismBehavior>(out var behavior);
Assert.False(behavior.WasAddedToBody);
Assert.False(behavior.WasAddedToPart);
Assert.That(behavior.WasAddedToPartInBody);
Assert.That(behavior.NoRemoved);
component.ResetAll();
behavior.ResetAll();
Assert.That(component.NoAdded);
Assert.That(component.NoRemoved);
Assert.That(behavior.NoAdded);
Assert.That(behavior.NoRemoved);
centerPart.RemoveMechanism(mechanism);
Assert.That(component.NoAdded);
Assert.False(component.WasRemovedFromBody);
Assert.False(component.WasRemovedFromPart);
Assert.That(component.WasRemovedFromPartInBody);
Assert.That(behavior.NoAdded);
Assert.False(behavior.WasRemovedFromBody);
Assert.False(behavior.WasRemovedFromPart);
Assert.That(behavior.WasRemovedFromPartInBody);
component.ResetAll();
behavior.ResetAll();
centerPart.TryAddMechanism(mechanism, true);
Assert.False(component.WasAddedToBody);
Assert.False(component.WasAddedToPart);
Assert.That(component.WasAddedToPartInBody);
Assert.That(component.NoRemoved());
Assert.False(behavior.WasAddedToBody);
Assert.False(behavior.WasAddedToPart);
Assert.That(behavior.WasAddedToPartInBody);
Assert.That(behavior.NoRemoved());
component.ResetAll();
behavior.ResetAll();
body.RemovePart(centerPart);
Assert.That(component.NoAdded);
Assert.That(component.WasRemovedFromBody);
Assert.False(component.WasRemovedFromPart);
Assert.False(component.WasRemovedFromPartInBody);
Assert.That(behavior.NoAdded);
Assert.That(behavior.WasRemovedFromBody);
Assert.False(behavior.WasRemovedFromPart);
Assert.False(behavior.WasRemovedFromPartInBody);
component.ResetAll();
behavior.ResetAll();
centerPart.RemoveMechanism(mechanism);
Assert.That(component.NoAdded);
Assert.False(component.WasRemovedFromBody);
Assert.That(component.WasRemovedFromPart);
Assert.False(component.WasRemovedFromPartInBody);
Assert.That(behavior.NoAdded);
Assert.False(behavior.WasRemovedFromBody);
Assert.That(behavior.WasRemovedFromPart);
Assert.False(behavior.WasRemovedFromPartInBody);
component.ResetAll();
behavior.ResetAll();
centerPart.TryAddMechanism(mechanism, true);
Assert.False(component.WasAddedToBody);
Assert.That(component.WasAddedToPart);
Assert.False(component.WasAddedToPartInBody);
Assert.That(component.NoRemoved);
Assert.False(behavior.WasAddedToBody);
Assert.That(behavior.WasAddedToPart);
Assert.False(behavior.WasAddedToPartInBody);
Assert.That(behavior.NoRemoved);
component.ResetAll();
behavior.ResetAll();
body.TryAddPart(centerSlot!, centerPart, true);
Assert.That(component.WasAddedToBody);
Assert.False(component.WasAddedToPart);
Assert.False(component.WasAddedToPartInBody);
Assert.That(component.NoRemoved);
Assert.That(behavior.WasAddedToBody);
Assert.False(behavior.WasAddedToPart);
Assert.False(behavior.WasAddedToPartInBody);
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
using Content.Server.GameObjects.Components.Mobs;
using Content.Server.Mobs;
using Content.Shared.GameObjects.Components.Body;
using Content.Shared.GameObjects.Components.Body.Behavior;
using Content.Shared.GameObjects.Components.Body.Part;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.ComponentDependencies;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Log;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Body.Behavior
{
[RegisterComponent]
public class BrainBehaviorComponent : MechanismBehaviorComponent
public class BrainBehavior : MechanismBehavior
{
public override string Name => "Brain";
protected override void OnAddedToBody(IBody body)
{
base.OnAddedToBody(body);
@@ -66,9 +58,5 @@ namespace Content.Server.GameObjects.Components.Body.Behavior
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.Networks;
using Robust.Shared.GameObjects;
namespace Content.Server.GameObjects.Components.Body.Behavior
{
[RegisterComponent]
[ComponentReference(typeof(SharedHeartBehaviorComponent))]
public class HeartBehaviorComponent : SharedHeartBehaviorComponent
public class HeartBehavior : MechanismBehavior
{
private float _accumulatedFrameTime;
public override void Update(float frameTime)
{
// TODO BODY do between pre and metabolism
if (Mechanism?.Body == null ||
!Mechanism.Body.Owner.HasComponent<SharedBloodstreamComponent>())
if (Parent.Body == null ||
!Parent.Body.Owner.HasComponent<SharedBloodstreamComponent>())
{
return;
}

View File

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

View File

@@ -1,23 +1,33 @@
#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.Part;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Serialization;
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 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)
{
return;
@@ -33,8 +43,6 @@ namespace Content.Shared.GameObjects.Components.Body.Behavior
}
}
public abstract void Update(float frameTime);
public void AddedToBody(IBody body)
{
DebugTools.AssertNotNull(Body);
@@ -98,5 +106,7 @@ namespace Content.Shared.GameObjects.Components.Body.Behavior
protected virtual void OnRemovedFromPart(IBodyPart old) { }
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.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Shared.GameObjects.Components.Body;
using Content.Shared.GameObjects.Components.Body.Behavior;
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 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>());
}
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>());
}
public static bool HasMechanismBehavior<T>(this IMechanism mechanism)
{
return mechanism.Owner.HasComponent<T>();
return part.Mechanisms.Any(m => m.HasBehavior<T>());
}
public static IEnumerable<IMechanismBehavior> GetMechanismBehaviors(this IBody body)
{
foreach (var part in body.Parts.Values)
foreach (var mechanism in part.Mechanisms)
foreach (var behavior in mechanism.Owner.GetAllComponents<IMechanismBehavior>())
foreach (var behavior in mechanism.Behaviors.Values)
{
yield return behavior;
}
@@ -52,10 +48,11 @@ namespace Content.Shared.GameObjects.Components.Body.Mechanism
{
foreach (var part in body.Parts.Values)
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.Linq;
using Content.Server.GameObjects.Components.Chemistry;
using Content.Shared.Chemistry;
using Content.Shared.GameObjects.Components.Body.Networks;
using Content.Shared.GameObjects.Components.Chemistry;
using Robust.Shared.GameObjects;
using Robust.Shared.Log;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Shared.GameObjects.Components.Body.Behavior
namespace Content.Server.GameObjects.Components.Body.Behavior
{
/// <summary>
/// Where reagents go when ingested. Tracks ingested reagents over time, and
/// eventually transfers them to <see cref="SharedBloodstreamComponent"/> once digested.
/// </summary>
public abstract class SharedStomachBehaviorComponent : MechanismBehaviorComponent
public class StomachBehavior : MechanismBehavior
{
public override string Name => "Stomach";
private float _accumulatedFrameTime;
/// <summary>
@@ -45,7 +45,7 @@ namespace Content.Shared.GameObjects.Components.Body.Behavior
_accumulatedFrameTime -= 1;
if (!Body.Owner.TryGetComponent(out SharedSolutionContainerComponent? solution) ||
if (!Owner.TryGetComponent(out SharedSolutionContainerComponent? solution) ||
!Body.Owner.TryGetComponent(out SharedBloodstreamComponent? bloodstream))
{
return;
@@ -58,7 +58,7 @@ namespace Content.Shared.GameObjects.Components.Body.Behavior
foreach (var delta in _reagentDeltas.ToList())
{
//Increment lifetime of reagents
delta.Increment(frameTime);
delta.Increment(1);
if (delta.Lifetime > _digestionDelay)
{
solution.TryRemoveReagent(delta.ReagentId, delta.Quantity);
@@ -112,6 +112,18 @@ namespace Content.Shared.GameObjects.Components.Body.Behavior
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)
{
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;
if (!trueTarget.TryGetComponent(out IBody body) ||
!body.TryGetMechanismBehaviors<StomachBehaviorComponent>(out var stomachs))
!body.TryGetMechanismBehaviors<StomachBehavior>(out var stomachs))
{
return false;
}

View File

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

View File

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

View File

@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Server.GameObjects.Components.Body.Behavior;
using Content.Server.GameObjects.Components.Chemistry;
using Content.Server.GameObjects.Components.GUI;
using Content.Server.GameObjects.Components.Items.Storage;
@@ -133,7 +134,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
var trueTarget = target ?? user;
if (!trueTarget.TryGetComponent(out IBody? body) ||
!body.TryGetMechanismBehaviors<SharedStomachBehaviorComponent>(out var stomachs))
!body.TryGetMechanismBehaviors<StomachBehavior>(out var stomachs))
{
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.Part;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Serialization;
namespace Content.Shared.GameObjects.Components.Body.Behavior
{
public interface IMechanismBehavior : IComponent
public interface IMechanismBehavior : IExposeData
{
IBody? Body { get; }
@@ -15,7 +16,20 @@ namespace Content.Shared.GameObjects.Components.Body.Behavior
/// Upward reference to the parent <see cref="IMechanism"/> that this
/// behavior is attached to.
/// </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>
/// 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
using System;
using System.Collections.Generic;
using Content.Shared.GameObjects.Components.Body.Behavior;
using Content.Shared.GameObjects.Components.Body.Part;
using Robust.Shared.Interfaces.GameObjects;
@@ -10,6 +13,8 @@ namespace Content.Shared.GameObjects.Components.Body.Mechanism
IBodyPart? Part { get; set; }
IReadOnlyDictionary<Type, IMechanismBehavior> Behaviors { get; }
/// <summary>
/// Professional description of the <see cref="IMechanism"/>.
/// </summary>
@@ -55,6 +60,21 @@ namespace Content.Shared.GameObjects.Components.Body.Mechanism
/// </summary>
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
/// <summary>
/// Called when the containing <see cref="IBodyPart"/> is attached to a

View File

@@ -1,10 +1,14 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Shared.GameObjects.Components.Body.Behavior;
using Content.Shared.GameObjects.Components.Body.Part;
using Content.Shared.Interfaces;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
@@ -14,11 +18,12 @@ namespace Content.Shared.GameObjects.Components.Body.Mechanism
{
public override string Name => "Mechanism";
private IBodyPart? _part;
protected readonly Dictionary<int, object> OptionsCache = new Dictionary<int, object>();
protected IBody? BodyCache;
protected int IdHash;
protected IEntity? PerformerCache;
private IBodyPart? _part;
private readonly Dictionary<Type, IMechanismBehavior> _behaviors = new Dictionary<Type, IMechanismBehavior>();
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 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.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)
@@ -105,9 +197,7 @@ namespace Content.Shared.GameObjects.Components.Body.Mechanism
DebugTools.AssertNotNull(Body);
DebugTools.AssertNotNull(body);
OnAddedToBody(body);
foreach (var behavior in Owner.GetAllComponents<IMechanismBehavior>())
foreach (var behavior in _behaviors.Values)
{
behavior.AddedToBody(body);
}
@@ -119,9 +209,8 @@ namespace Content.Shared.GameObjects.Components.Body.Mechanism
DebugTools.AssertNotNull(part);
Owner.Transform.AttachParent(part.Owner);
OnAddedToPart(part);
foreach (var behavior in Owner.GetAllComponents<IMechanismBehavior>().ToArray())
foreach (var behavior in _behaviors.Values)
{
behavior.AddedToPart(part);
}
@@ -135,9 +224,8 @@ namespace Content.Shared.GameObjects.Components.Body.Mechanism
DebugTools.AssertNotNull(part);
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);
}
@@ -148,9 +236,7 @@ namespace Content.Shared.GameObjects.Components.Body.Mechanism
DebugTools.AssertNull(Body);
DebugTools.AssertNotNull(old);
OnRemovedFromBody(old);
foreach (var behavior in Owner.GetAllComponents<IMechanismBehavior>())
foreach (var behavior in _behaviors.Values)
{
behavior.RemovedFromBody(old);
}
@@ -162,9 +248,8 @@ namespace Content.Shared.GameObjects.Components.Body.Mechanism
DebugTools.AssertNotNull(old);
Owner.Transform.AttachToGridOrMap();
OnRemovedFromPart(old);
foreach (var behavior in Owner.GetAllComponents<IMechanismBehavior>())
foreach (var behavior in _behaviors.Values)
{
behavior.RemovedFromPart(old);
}
@@ -178,24 +263,11 @@ namespace Content.Shared.GameObjects.Components.Body.Mechanism
DebugTools.AssertNotNull(oldPart);
Owner.Transform.AttachToGridOrMap();
OnRemovedFromPartInBody(oldBody, oldPart);
foreach (var behavior in Owner.GetAllComponents<IMechanismBehavior>())
foreach (var behavior in _behaviors.Values)
{
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
- attachtograndparent
- inrangeunoccluded
- hungry
CanViewVar: true
CanAdminPlace: true
CanAdminMenu: true
@@ -231,6 +232,7 @@
- attachtogrid
- attachtograndparent
- inrangeunoccluded
- hungry
CanViewVar: true
CanAdminPlace: true
CanScript: true

View File

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

View File

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