Add injectors + injected reagent metabolism via BloodstreamCompo… (#730)

* Add BloodstreamComponent and BloodstreamSystem

New component for metabolizing reagents that other organs like the stomach pass their input reagents to.

* Change StomachComponent to put ingested reagents in bloodstream after delay

Now StomachComponent does not metabolize any reagents. Instead, it tracks how long each reagent has been inside it, and once they pass "digestionDelay" they'll be put inside the bloodstream, where the bloodstream will handle metabolism of the reagent.

* Add reagent injectors

Injects reagents straight into the bloodstream when used on mobs with bloodstreams. Also allows draw/inject from beakers. Does not support drawing blood/reagents from the bloodstream yet.

* Address code review

Make use of `Loc` static class instead of using `ILocalizationManager`. Localize InjectorToggleMode enum properly.
This commit is contained in:
moneyl
2020-02-23 19:47:33 -05:00
committed by GitHub
parent d8291ed9c4
commit 1b7860aeda
16 changed files with 648 additions and 41 deletions

View File

@@ -1,40 +1,74 @@
using System.Collections.Generic;
using System.Linq;
using Content.Server.GameObjects.Components.Chemistry;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.GameObjects.Components.Metabolism;
using Content.Shared.Chemistry;
using Content.Shared.GameObjects.Components.Nutrition;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Nutrition
{
/// <summary>
/// Where reagents go when ingested. Tracks ingested reagents over time, and
/// eventually transfers them to <see cref="BloodstreamComponent"/> once digested.
/// </summary>
[RegisterComponent]
public class StomachComponent : SharedStomachComponent
{
#pragma warning disable 649
[Dependency] private readonly IPrototypeManager _prototypeManager;
[Dependency] private readonly ILocalizationManager _localizationManager;
#pragma warning restore 649
[ViewVariables(VVAccess.ReadOnly)]
private SolutionComponent _stomachContents;
/// <summary>
/// Max volume of internal solution storage
/// </summary>
public int MaxVolume
{
get => _stomachContents.MaxVolume;
set => _stomachContents.MaxVolume = value;
}
/// <summary>
/// Internal solution storage
/// </summary>
[ViewVariables]
private SolutionComponent _stomachContents;
/// <summary>
/// Initial internal solution storage volume
/// </summary>
[ViewVariables]
private int _initialMaxVolume;
//Used to track changes to reagent amounts during metabolism
private readonly Dictionary<string, int> _reagentDeltas = new Dictionary<string, int>();
/// <summary>
/// Time in seconds between reagents being ingested and them being transferred to <see cref="BloodstreamComponent"/>
/// </summary>
[ViewVariables]
private float _digestionDelay;
/// <summary>
/// Used to track how long each reagent has been in the stomach
/// </summary>
private readonly List<ReagentDelta> _reagentDeltas = new List<ReagentDelta>();
/// <summary>
/// Reference to bloodstream where digested reagents are transferred to
/// </summary>
private BloodstreamComponent _bloodstream;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _initialMaxVolume, "max_volume", 20);
serializer.DataField(ref _initialMaxVolume, "maxVolume", 100);
serializer.DataField(ref _digestionDelay, "digestionDelay", 20);
}
public override void Initialize()
{
base.Initialize();
@@ -43,6 +77,14 @@ namespace Content.Server.GameObjects.Components.Nutrition
_stomachContents.InitializeFromPrototype();
_stomachContents.MaxVolume = _initialMaxVolume;
_stomachContents.Owner = Owner; //Manually set owner to avoid crash when VV'ing this
//Ensure bloodstream in present
if (!Owner.TryGetComponent<BloodstreamComponent>(out _bloodstream))
{
Logger.Warning(_localizationManager.GetString(
"StomachComponent entity does not have a BloodstreamComponent, which is required for it to function. Owner entity name: {0}",
Owner.Name));
}
}
public bool TryTransferSolution(Solution solution)
@@ -52,47 +94,64 @@ namespace Content.Server.GameObjects.Components.Nutrition
{
return false;
}
//Add solution to _stomachContents
_stomachContents.TryAddSolution(solution, false, true);
//Add each reagent to _reagentDeltas. Used to track how long each reagent has been in the stomach
foreach (var reagent in solution.Contents)
{
_reagentDeltas.Add(new ReagentDelta(reagent.ReagentId, reagent.Quantity));
}
return true;
}
/// <summary>
/// Loops through each reagent in _stomachContents, and calls the IMetabolizable for each of them./>
/// Updates digestion status of ingested reagents. Once reagents surpass _digestionDelay
/// they are moved to the bloodstream, where they are then metabolized.
/// </summary>
/// <param name="tickTime">The time since the last metabolism tick in seconds.</param>
public void Metabolize(float tickTime)
/// <param name="tickTime">The time since the last update in seconds.</param>
public void OnUpdate(float tickTime)
{
if (_stomachContents.CurrentVolume == 0)
return;
//Run metabolism for each reagent, track quantity changes
_reagentDeltas.Clear();
foreach (var reagent in _stomachContents.ReagentList)
if (_bloodstream == null)
{
if(!_prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype proto))
continue;
return;
}
foreach (var metabolizable in proto.Metabolism)
//Add reagents ready for transfer to bloodstream to transferSolution
var transferSolution = new Solution();
foreach (var delta in _reagentDeltas.ToList()) //Use ToList here to remove entries while iterating
{
//Increment lifetime of reagents
delta.Increment(tickTime);
if (delta.Lifetime > _digestionDelay)
{
_reagentDeltas[reagent.ReagentId] = metabolizable.Metabolize(Owner, reagent.ReagentId, tickTime);
_stomachContents.TryRemoveReagent(delta.ReagentId, delta.Quantity);
transferSolution.AddReagent(delta.ReagentId, delta.Quantity);
_reagentDeltas.Remove(delta);
}
}
//Apply changes to quantity afterwards. Can't change the reagent quantities while the iterating the
//list of reagents, because that would invalidate the iterator and throw an exception.
foreach (var reagentDelta in _reagentDeltas)
{
_stomachContents.TryRemoveReagent(reagentDelta.Key, reagentDelta.Value);
}
//Transfer digested reagents to bloodstream
_bloodstream.TryTransferSolution(transferSolution);
}
/// <summary>
/// Triggers metabolism of the reagents inside _stomachContents. Called by <see cref="StomachSystem"/>
/// Used to track quantity changes when ingesting & digesting reagents
/// </summary>
/// <param name="tickTime">The time since the last metabolism tick in seconds.</param>
public void OnUpdate(float tickTime)
private class ReagentDelta
{
Metabolize(tickTime);
public readonly string ReagentId;
public readonly int Quantity;
public float Lifetime { get; private set; }
public ReagentDelta(string reagentId, int quantity)
{
ReagentId = reagentId;
Quantity = quantity;
Lifetime = 0.0f;
}
public void Increment(float delta) => Lifetime += delta;
}
}
}