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:
@@ -131,7 +131,8 @@ namespace Content.Client
|
||||
"UseDelay",
|
||||
"Pourable",
|
||||
"Paper",
|
||||
"Write"
|
||||
"Write",
|
||||
"Bloodstream"
|
||||
};
|
||||
|
||||
foreach (var ignoreName in registerIgnore)
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
using Content.Client.UserInterface;
|
||||
using Content.Client.Utility;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Shared.GameObjects.Components.Chemistry;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.Chemistry
|
||||
{
|
||||
/// <summary>
|
||||
/// Client behavior for injectors & syringes. Used for item status on injectors
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class InjectorComponent : SharedInjectorComponent, IItemStatus
|
||||
{
|
||||
[ViewVariables] private int CurrentVolume { get; set; }
|
||||
[ViewVariables] private int TotalVolume { get; set; }
|
||||
[ViewVariables] private InjectorToggleMode CurrentMode { get; set; }
|
||||
[ViewVariables(VVAccess.ReadWrite)] private bool _uiUpdateNeeded;
|
||||
|
||||
//Add/remove item status code
|
||||
Control IItemStatus.MakeControl() => new StatusControl(this);
|
||||
void IItemStatus.DestroyControl(Control control) { }
|
||||
|
||||
//Handle net updates
|
||||
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
|
||||
{
|
||||
var cast = (InjectorComponentState)curState;
|
||||
if (cast != null)
|
||||
{
|
||||
CurrentVolume = cast.CurrentVolume;
|
||||
TotalVolume = cast.TotalVolume;
|
||||
CurrentMode = cast.CurrentMode;
|
||||
_uiUpdateNeeded = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Item status control for injectors
|
||||
/// </summary>
|
||||
private sealed class StatusControl : Control
|
||||
{
|
||||
private readonly InjectorComponent _parent;
|
||||
private readonly RichTextLabel _label;
|
||||
|
||||
public StatusControl(InjectorComponent parent)
|
||||
{
|
||||
_parent = parent;
|
||||
_label = new RichTextLabel { StyleClasses = { NanoStyle.StyleClassItemStatus } };
|
||||
AddChild(_label);
|
||||
|
||||
parent._uiUpdateNeeded = true;
|
||||
}
|
||||
|
||||
protected override void Update(FrameEventArgs args)
|
||||
{
|
||||
base.Update(args);
|
||||
if (!_parent._uiUpdateNeeded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_parent._uiUpdateNeeded = false;
|
||||
|
||||
//Update current volume and injector state
|
||||
var modeStringLocalized = _parent.CurrentMode switch
|
||||
{
|
||||
InjectorToggleMode.Draw => Loc.GetString("Draw"),
|
||||
InjectorToggleMode.Inject => Loc.GetString("Inject"),
|
||||
_ => Loc.GetString("Invalid")
|
||||
};
|
||||
_label.SetMarkup(Loc.GetString("Volume: [color=white]{0}/{1}[/color] | [color=white]{2}[/color]",
|
||||
_parent.CurrentVolume, _parent.TotalVolume, modeStringLocalized));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
using System;
|
||||
using Content.Server.GameObjects.Components.Metabolism;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Server.Interfaces;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.GameObjects.Components.Chemistry;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Chemistry
|
||||
{
|
||||
/// <summary>
|
||||
/// Server behavior for reagent injectors and syringes. Can optionally support both
|
||||
/// injection and drawing or just injection. Can inject/draw reagents from solution
|
||||
/// containers, and can directly inject into a mobs bloodstream.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class InjectorComponent : SharedInjectorComponent, IAfterAttack, IUse
|
||||
{
|
||||
#pragma warning disable 649
|
||||
[Dependency] private readonly IServerNotifyManager _notifyManager;
|
||||
#pragma warning restore 649
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the injector is able to draw from containers or if it's a single use
|
||||
/// device that can only inject.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
private bool _injectOnly;
|
||||
|
||||
/// <summary>
|
||||
/// Amount to inject or draw on each usage. If the injector is inject only, it will
|
||||
/// attempt to inject it's entire contents upon use.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
private int _transferAmount;
|
||||
|
||||
/// <summary>
|
||||
/// Initial storage volume of the injector
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
private int _initialMaxVolume;
|
||||
|
||||
/// <summary>
|
||||
/// The state of the injector. Determines it's attack behavior. Containers must have the
|
||||
/// right SolutionCaps to support injection/drawing. For InjectOnly injectors this should
|
||||
/// only ever be set to Inject
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
private InjectorToggleMode _toggleState;
|
||||
/// <summary>
|
||||
/// Internal solution container
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
private SolutionComponent _internalContents;
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
serializer.DataField(ref _injectOnly, "injectOnly", false);
|
||||
serializer.DataField(ref _initialMaxVolume, "initialMaxVolume", 15);
|
||||
serializer.DataField(ref _transferAmount, "transferAmount", 5);
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
//Create and setup internal storage
|
||||
_internalContents = new SolutionComponent();
|
||||
_internalContents.InitializeFromPrototype();
|
||||
_internalContents.Init();
|
||||
_internalContents.MaxVolume = _initialMaxVolume;
|
||||
_internalContents.Owner = Owner; //Manually set owner to avoid crash when VV'ing this
|
||||
_internalContents.Capabilities |= SolutionCaps.Injector;
|
||||
|
||||
//Set _toggleState based on prototype
|
||||
_toggleState = _injectOnly ? InjectorToggleMode.Inject : InjectorToggleMode.Draw;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggle between draw/inject state if applicable
|
||||
/// </summary>
|
||||
private void Toggle()
|
||||
{
|
||||
if (_injectOnly)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_toggleState = _toggleState switch
|
||||
{
|
||||
InjectorToggleMode.Inject => InjectorToggleMode.Draw,
|
||||
InjectorToggleMode.Draw => InjectorToggleMode.Inject,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
Dirty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when clicking on entities while holding in active hand
|
||||
/// </summary>
|
||||
/// <param name="eventArgs"></param>
|
||||
void IAfterAttack.AfterAttack(AfterAttackEventArgs eventArgs)
|
||||
{
|
||||
//Make sure we have the attacking entity
|
||||
if (eventArgs.Attacked == null || !_internalContents.Injector)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var targetEntity = eventArgs.Attacked;
|
||||
//Handle injecting/drawing for solutions
|
||||
if (targetEntity.TryGetComponent<SolutionComponent>(out var targetSolution) && targetSolution.Injectable)
|
||||
{
|
||||
if (_toggleState == InjectorToggleMode.Inject)
|
||||
{
|
||||
TryInject(targetSolution, eventArgs.User);
|
||||
}
|
||||
else if (_toggleState == InjectorToggleMode.Draw)
|
||||
{
|
||||
TryDraw(targetSolution, eventArgs.User);
|
||||
}
|
||||
}
|
||||
else //Handle injecting into bloodstream
|
||||
{
|
||||
if (targetEntity.TryGetComponent<BloodstreamComponent>(out var bloodstream) && _toggleState == InjectorToggleMode.Inject)
|
||||
{
|
||||
TryInjectIntoBloodstream(bloodstream, eventArgs.User);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when use key is pressed when held in active hand
|
||||
/// </summary>
|
||||
/// <param name="eventArgs"></param>
|
||||
/// <returns></returns>
|
||||
bool IUse.UseEntity(UseEntityEventArgs eventArgs)
|
||||
{
|
||||
Toggle();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void TryInjectIntoBloodstream(BloodstreamComponent targetBloodstream, IEntity user)
|
||||
{
|
||||
if (_internalContents.CurrentVolume == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//Get transfer amount. May be smaller than _transferAmount if not enough room
|
||||
int realTransferAmount = Math.Min(_transferAmount, targetBloodstream.EmptyVolume);
|
||||
if (realTransferAmount <= 0)
|
||||
{
|
||||
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user,
|
||||
Loc.GetString("Container full."));
|
||||
return;
|
||||
}
|
||||
|
||||
//Move units from attackSolution to targetSolution
|
||||
var removedSolution = _internalContents.SplitSolution(realTransferAmount);
|
||||
if (!targetBloodstream.TryTransferSolution(removedSolution))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user,
|
||||
Loc.GetString("Injected {0}u", removedSolution.TotalVolume));
|
||||
Dirty();
|
||||
}
|
||||
|
||||
private void TryInject(SolutionComponent targetSolution, IEntity user)
|
||||
{
|
||||
if (_internalContents.CurrentVolume == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//Get transfer amount. May be smaller than _transferAmount if not enough room
|
||||
int realTransferAmount = Math.Min(_transferAmount, targetSolution.EmptyVolume);
|
||||
if (realTransferAmount <= 0)
|
||||
{
|
||||
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user,
|
||||
Loc.GetString("Container full."));
|
||||
return;
|
||||
}
|
||||
|
||||
//Move units from attackSolution to targetSolution
|
||||
var removedSolution = _internalContents.SplitSolution(realTransferAmount);
|
||||
if (!targetSolution.TryAddSolution(removedSolution))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user,
|
||||
Loc.GetString("Injected {0}u", removedSolution.TotalVolume));
|
||||
Dirty();
|
||||
}
|
||||
|
||||
private void TryDraw(SolutionComponent targetSolution, IEntity user)
|
||||
{
|
||||
if (_internalContents.EmptyVolume == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//Get transfer amount. May be smaller than _transferAmount if not enough room
|
||||
int realTransferAmount = Math.Min(_transferAmount, targetSolution.CurrentVolume);
|
||||
if (realTransferAmount <= 0)
|
||||
{
|
||||
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user,
|
||||
Loc.GetString("Container empty"));
|
||||
return;
|
||||
}
|
||||
|
||||
//Move units from attackSolution to targetSolution
|
||||
var removedSolution = targetSolution.SplitSolution(realTransferAmount);
|
||||
if (!_internalContents.TryAddSolution(removedSolution))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user,
|
||||
Loc.GetString("Drew {0}u", removedSolution.TotalVolume));
|
||||
Dirty();
|
||||
}
|
||||
|
||||
public override ComponentState GetComponentState()
|
||||
{
|
||||
return new InjectorComponentState(_internalContents.CurrentVolume, _internalContents.MaxVolume, _toggleState);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,11 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
protected override void Startup()
|
||||
{
|
||||
base.Startup();
|
||||
Init();
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
_reactions = _prototypeManager.EnumeratePrototypes<ReactionPrototype>();
|
||||
_audioSystem = _entitySystemManager.GetEntitySystem<AudioSystem>();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
using System.Linq;
|
||||
using Content.Server.GameObjects.Components.Chemistry;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.Chemistry;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Metabolism
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles all metabolism for mobs. All delivery methods eventually bring reagents
|
||||
/// to the bloodstream. For example, injectors put reagents directly into the bloodstream,
|
||||
/// and the stomach does with some delay.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class BloodstreamComponent : Component
|
||||
{
|
||||
#pragma warning disable 649
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager;
|
||||
#pragma warning restore 649
|
||||
|
||||
public override string Name => "Bloodstream";
|
||||
|
||||
/// <summary>
|
||||
/// Internal solution for reagent storage
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
private SolutionComponent _internalSolution;
|
||||
|
||||
/// <summary>
|
||||
/// Max volume of internal solution storage
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
private int _initialMaxVolume;
|
||||
|
||||
/// <summary>
|
||||
/// Empty volume of internal solution
|
||||
/// </summary>
|
||||
public int EmptyVolume => _internalSolution.EmptyVolume;
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
serializer.DataField(ref _initialMaxVolume, "maxVolume", 250);
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
//Create and setup internal solution storage
|
||||
_internalSolution = new SolutionComponent();
|
||||
_internalSolution.InitializeFromPrototype();
|
||||
_internalSolution.Init();
|
||||
_internalSolution.MaxVolume = _initialMaxVolume;
|
||||
_internalSolution.Owner = Owner; //Manually set owner to avoid crash when VV'ing this
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to transfer provided solution to internal solution. Only supports complete transfers
|
||||
/// </summary>
|
||||
/// <param name="solution">Solution to be transferred</param>
|
||||
/// <returns>Whether or not transfer was a success</returns>
|
||||
public bool TryTransferSolution(Solution solution)
|
||||
{
|
||||
//For now doesn't support partial transfers
|
||||
if (solution.TotalVolume + _internalSolution.CurrentVolume > _internalSolution.MaxVolume)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_internalSolution.TryAddSolution(solution, false, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loops through each reagent in _internalSolution, and calls the IMetabolizable for each of them./>
|
||||
/// </summary>
|
||||
/// <param name="tickTime">The time since the last metabolism tick in seconds.</param>
|
||||
private void Metabolize(float tickTime)
|
||||
{
|
||||
if (_internalSolution.CurrentVolume == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//Run metabolism for each reagent, remove metabolized reagents
|
||||
foreach (var reagent in _internalSolution.ReagentList.ToList()) //Using ToList here lets us edit reagents while iterating
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype proto))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//Run metabolism code for each reagent
|
||||
foreach (var metabolizable in proto.Metabolism)
|
||||
{
|
||||
int reagentDelta = metabolizable.Metabolize(Owner, reagent.ReagentId, tickTime);
|
||||
_internalSolution.TryRemoveReagent(reagent.ReagentId, reagentDelta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triggers metabolism of the reagents inside _internalSolution. Called by <see cref="BloodstreamSystem"/>
|
||||
/// </summary>
|
||||
/// <param name="tickTime">The time since the last metabolism tick in seconds.</param>
|
||||
public void OnUpdate(float tickTime)
|
||||
{
|
||||
Metabolize(tickTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 (_bloodstream == null)
|
||||
{
|
||||
if (_stomachContents.CurrentVolume == 0)
|
||||
return;
|
||||
|
||||
//Run metabolism for each reagent, track quantity changes
|
||||
_reagentDeltas.Clear();
|
||||
foreach (var reagent in _stomachContents.ReagentList)
|
||||
{
|
||||
if(!_prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype proto))
|
||||
continue;
|
||||
|
||||
foreach (var metabolizable in proto.Metabolism)
|
||||
{
|
||||
_reagentDeltas[reagent.ReagentId] = metabolizable.Metabolize(Owner, reagent.ReagentId, tickTime);
|
||||
}
|
||||
}
|
||||
|
||||
//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)
|
||||
//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
|
||||
{
|
||||
_stomachContents.TryRemoveReagent(reagentDelta.Key, reagentDelta.Value);
|
||||
//Increment lifetime of reagents
|
||||
delta.Increment(tickTime);
|
||||
if (delta.Lifetime > _digestionDelay)
|
||||
{
|
||||
_stomachContents.TryRemoveReagent(delta.ReagentId, delta.Quantity);
|
||||
transferSolution.AddReagent(delta.ReagentId, delta.Quantity);
|
||||
_reagentDeltas.Remove(delta);
|
||||
}
|
||||
}
|
||||
//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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
using Content.Server.GameObjects.Components.Metabolism;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
|
||||
namespace Content.Server.GameObjects.EntitySystems
|
||||
{
|
||||
/// <summary>
|
||||
/// Triggers metabolism updates for <see cref="BloodstreamComponent"/>
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public class BloodstreamSystem : EntitySystem
|
||||
{
|
||||
private float _accumulatedFrameTime;
|
||||
public override void Initialize()
|
||||
{
|
||||
EntityQuery = new TypeEntityQuery(typeof(BloodstreamComponent));
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
//Trigger metabolism updates at most once per second
|
||||
_accumulatedFrameTime += frameTime;
|
||||
if (_accumulatedFrameTime > 1.0f)
|
||||
{
|
||||
foreach (var entity in RelevantEntities)
|
||||
{
|
||||
var comp = entity.GetComponent<BloodstreamComponent>();
|
||||
comp.OnUpdate(_accumulatedFrameTime);
|
||||
}
|
||||
_accumulatedFrameTime = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
using Content.Server.GameObjects.Components.Nutrition;
|
||||
using Content.Server.GameObjects.Components.Nutrition;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
|
||||
namespace Content.Server.GameObjects.EntitySystems
|
||||
{
|
||||
/// <summary>
|
||||
/// Triggers digestion updates on <see cref="StomachComponent"/>
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public class StomachSystem : EntitySystem
|
||||
{
|
||||
@@ -16,8 +19,8 @@ namespace Content.Server.GameObjects.EntitySystems
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
//Update at most once per second
|
||||
_accumulatedFrameTime += frameTime;
|
||||
// TODO: Potential performance improvement (e.g. going through say 1/5th the entities every tick)
|
||||
if (_accumulatedFrameTime > 1.0f)
|
||||
{
|
||||
foreach (var entity in RelevantEntities)
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.GameObjects.Components.Chemistry
|
||||
{
|
||||
/// <summary>
|
||||
/// Shared class for injectors & syringes
|
||||
/// </summary>
|
||||
public class SharedInjectorComponent : Component
|
||||
{
|
||||
public override string Name => "Injector";
|
||||
public sealed override uint? NetID => ContentNetIDs.REAGENT_INJECTOR;
|
||||
|
||||
/// <summary>
|
||||
/// Component data used for net updates. Used by client for item status ui
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
protected sealed class InjectorComponentState : ComponentState
|
||||
{
|
||||
public int CurrentVolume { get; }
|
||||
public int TotalVolume { get; }
|
||||
public InjectorToggleMode CurrentMode { get; }
|
||||
|
||||
public InjectorComponentState(int currentVolume, int totalVolume, InjectorToggleMode currentMode) : base(ContentNetIDs.REAGENT_INJECTOR)
|
||||
{
|
||||
CurrentVolume = currentVolume;
|
||||
TotalVolume = totalVolume;
|
||||
CurrentMode = currentMode;
|
||||
}
|
||||
}
|
||||
|
||||
protected enum InjectorToggleMode
|
||||
{
|
||||
Inject,
|
||||
Draw
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,6 +74,14 @@ namespace Content.Shared.GameObjects.Components.Chemistry
|
||||
/// Shortcut for Capabilities PourOut flag to avoid binary operators.
|
||||
/// </summary>
|
||||
public bool CanPourOut => (Capabilities & SolutionCaps.PourOut) != 0;
|
||||
/// <summary>
|
||||
/// Shortcut for Capabilities Injectable flag
|
||||
/// </summary>
|
||||
public bool Injectable => (Capabilities & SolutionCaps.Injectable) != 0;
|
||||
/// <summary>
|
||||
/// Shortcut for Capabilities Injector flag
|
||||
/// </summary>
|
||||
public bool Injector => (Capabilities & SolutionCaps.Injector) != 0;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string Name => "Solution";
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
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";
|
||||
|
||||
@@ -40,5 +40,6 @@
|
||||
public const uint STACK = 1035;
|
||||
public const uint HANDHELD_LIGHT = 1036;
|
||||
public const uint PAPER = 1037;
|
||||
public const uint REAGENT_INJECTOR = 1038;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
texture: Objects/Chemistry/chemicals.rsi/beaker.png
|
||||
- type: Solution
|
||||
maxVol: 50
|
||||
caps: 19
|
||||
caps: 27
|
||||
- type: Pourable
|
||||
transferAmount: 5
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
texture: Objects/Chemistry/chemicals.rsi/beakerlarge.png
|
||||
- type: Solution
|
||||
maxVol: 100
|
||||
caps: 19
|
||||
caps: 27
|
||||
- type: Pourable
|
||||
transferAmount: 5
|
||||
|
||||
@@ -45,3 +45,19 @@
|
||||
caps: 19
|
||||
- type: Pourable
|
||||
transferAmount: 5
|
||||
|
||||
- type: entity
|
||||
name: Syringe
|
||||
parent: BaseItem
|
||||
description: Used to draw blood samples from mobs, or to inject them with reagents
|
||||
id: Syringe
|
||||
components:
|
||||
- type: Sprite
|
||||
texture: Objects/Chemistry/chemicals.rsi/syringeproj.png
|
||||
- type: Icon
|
||||
texture: Objects/Chemistry/chemicals.rsi/syringeproj.png
|
||||
- type: Solution
|
||||
maxVol: 15
|
||||
caps: 19
|
||||
- type: Injector
|
||||
injectOnly: false
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
- type: Thirst
|
||||
# Organs
|
||||
- type: Stomach
|
||||
maxVolume: 100
|
||||
digestionDelay: 20
|
||||
- type: Bloodstream
|
||||
maxVolume: 250
|
||||
|
||||
- type: Inventory
|
||||
- type: Constructor
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version": 1, "size": {"x": 32, "y": 32}, "license": "CC-BY-SA-3.0", "copyright": "Taken from https://github.com/discordia-space/CEV-Eris/blob/2b969adc2dfd3e9621bf3597c5cbffeb3ac8c9f0/icons/obj/chemical.dmi", "states": [{"name": "beaker", "directions": 1, "delays": [[1.0]]}, {"name": "beakerbluespace", "directions": 1, "delays": [[0.1, 0.1]]}, {"name": "beakerlarge", "directions": 1, "delays": [[1.0]]}, {"name": "beakernoreact", "directions": 1, "delays": [[0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05]]}, {"name": "bottle", "directions": 1, "delays": [[1.0]]}, {"name": "bottle-1", "directions": 1, "delays": [[1.0]]}, {"name": "bottle-2", "directions": 1, "delays": [[1.0]]}, {"name": "bottle-3", "directions": 1, "delays": [[1.0]]}, {"name": "bottle-4", "directions": 1, "delays": [[1.0]]}, {"name": "bottle1", "directions": 1, "delays": [[1.0]]}, {"name": "bottle10", "directions": 1, "delays": [[1.0]]}, {"name": "bottle11", "directions": 1, "delays": [[1.0]]}, {"name": "bottle12", "directions": 1, "delays": [[1.0]]}, {"name": "bottle13", "directions": 1, "delays": [[1.0]]}, {"name": "bottle14", "directions": 1, "delays": [[1.0]]}, {"name": "bottle15", "directions": 1, "delays": [[1.0]]}, {"name": "bottle16", "directions": 1, "delays": [[1.0]]}, {"name": "bottle17", "directions": 1, "delays": [[1.0]]}, {"name": "bottle18", "directions": 1, "delays": [[1.0]]}, {"name": "bottle19", "directions": 1, "delays": [[1.0]]}, {"name": "bottle2", "directions": 1, "delays": [[1.0]]}, {"name": "bottle20", "directions": 1, "delays": [[1.0]]}, {"name": "bottle3", "directions": 1, "delays": [[1.0]]}, {"name": "bottle4", "directions": 1, "delays": [[1.0]]}, {"name": "bottle5", "directions": 1, "delays": [[1.0]]}, {"name": "bottle6", "directions": 1, "delays": [[1.0]]}, {"name": "bottle7", "directions": 1, "delays": [[1.0]]}, {"name": "bottle8", "directions": 1, "delays": [[1.0]]}, {"name": "bottle9", "directions": 1, "delays": [[1.0]]}, {"name": "chemg", "directions": 1, "delays": [[1.0]]}, {"name": "chemg_armed", "directions": 1, "delays": [[0.1, 0.2]]}, {"name": "chemg_ass", "directions": 1, "delays": [[1.0]]}, {"name": "chemg_locked", "directions": 1, "delays": [[1.0]]}, {"name": "chempuff", "directions": 4, "delays": [[0.1, 0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1, 0.1]]}, {"name": "dropper", "directions": 1, "delays": [[1.0]]}, {"name": "large_grenade", "directions": 1, "delays": [[1.0]]}, {"name": "large_grenade_armed", "directions": 1, "delays": [[0.1, 0.1]]}, {"name": "large_grenade_ass", "directions": 1, "delays": [[1.0]]}, {"name": "large_grenade_locked", "directions": 1, "delays": [[1.0]]}, {"name": "lid_beaker", "directions": 1, "delays": [[1.0]]}, {"name": "lid_beakerlarge", "directions": 1, "delays": [[1.0]]}, {"name": "lid_beakernoreact", "directions": 1, "delays": [[1.0]]}, {"name": "lid_bottle", "directions": 1, "delays": [[1.0]]}, {"name": "lid_vial", "directions": 1, "delays": [[1.0]]}, {"name": "molten", "directions": 1, "delays": [[1.0]]}, {"name": "pill", "directions": 1, "delays": [[1.0]]}, {"name": "pill1", "directions": 1, "delays": [[1.0]]}, {"name": "pill10", "directions": 1, "delays": [[1.0]]}, {"name": "pill11", "directions": 1, "delays": [[1.0]]}, {"name": "pill12", "directions": 1, "delays": [[1.0]]}, {"name": "pill13", "directions": 1, "delays": [[1.0]]}, {"name": "pill14", "directions": 1, "delays": [[1.0]]}, {"name": "pill15", "directions": 1, "delays": [[1.0]]}, {"name": "pill16", "directions": 1, "delays": [[1.0]]}, {"name": "pill17", "directions": 1, "delays": [[1.0]]}, {"name": "pill18", "directions": 1, "delays": [[1.0]]}, {"name": "pill19", "directions": 1, "delays": [[1.0]]}, {"name": "pill2", "directions": 1, "delays": [[1.0]]}, {"name": "pill20", "directions": 1, "delays": [[1.0]]}, {"name": "pill3", "directions": 1, "delays": [[1.0]]}, {"name": "pill4", "directions": 1, "delays": [[1.0]]}, {"name": "pill5", "directions": 1, "delays": [[1.0]]}, {"name": "pill6", "directions": 1, "delays": [[1.0]]}, {"name": "pill7", "directions": 1, "delays": [[1.0]]}, {"name": "pill8", "directions": 1, "delays": [[1.0]]}, {"name": "pill9", "directions": 1, "delays": [[1.0]]}, {"name": "pill_canister", "directions": 1, "delays": [[1.0]]}, {"name": "syringeproj", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "vial", "directions": 1, "delays": [[1.0]]}, {"name": "weedpuff", "directions": 4, "delays": [[0.1, 0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1, 0.1]]}]}
|
||||
{"version":1,"size":{"x":32,"y":32},"license":"CC-BY-SA-3.0","copyright":"Taken from https://github.com/discordia-space/CEV-Eris/blob/2b969adc2dfd3e9621bf3597c5cbffeb3ac8c9f0/icons/obj/chemical.dmi","states":[{"name":"beaker","directions":1,"delays":[[1]]},{"name":"beakerbluespace","directions":1,"delays":[[0.1,0.1]]},{"name":"beakerlarge","directions":1,"delays":[[1]]},{"name":"beakernoreact","directions":1,"delays":[[0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05]]},{"name":"bottle","directions":1,"delays":[[1]]},{"name":"bottle-1","directions":1,"delays":[[1]]},{"name":"bottle-2","directions":1,"delays":[[1]]},{"name":"bottle-3","directions":1,"delays":[[1]]},{"name":"bottle-4","directions":1,"delays":[[1]]},{"name":"bottle1","directions":1,"delays":[[1]]},{"name":"bottle10","directions":1,"delays":[[1]]},{"name":"bottle11","directions":1,"delays":[[1]]},{"name":"bottle12","directions":1,"delays":[[1]]},{"name":"bottle13","directions":1,"delays":[[1]]},{"name":"bottle14","directions":1,"delays":[[1]]},{"name":"bottle15","directions":1,"delays":[[1]]},{"name":"bottle16","directions":1,"delays":[[1]]},{"name":"bottle17","directions":1,"delays":[[1]]},{"name":"bottle18","directions":1,"delays":[[1]]},{"name":"bottle19","directions":1,"delays":[[1]]},{"name":"bottle2","directions":1,"delays":[[1]]},{"name":"bottle20","directions":1,"delays":[[1]]},{"name":"bottle3","directions":1,"delays":[[1]]},{"name":"bottle4","directions":1,"delays":[[1]]},{"name":"bottle5","directions":1,"delays":[[1]]},{"name":"bottle6","directions":1,"delays":[[1]]},{"name":"bottle7","directions":1,"delays":[[1]]},{"name":"bottle8","directions":1,"delays":[[1]]},{"name":"bottle9","directions":1,"delays":[[1]]},{"name":"chemg","directions":1,"delays":[[1]]},{"name":"chemg_armed","directions":1,"delays":[[0.1,0.2]]},{"name":"chemg_ass","directions":1,"delays":[[1]]},{"name":"chemg_locked","directions":1,"delays":[[1]]},{"name":"chempuff","directions":4,"delays":[[0.1,0.1,0.1,0.1,0.1],[0.1,0.1,0.1,0.1,0.1],[0.1,0.1,0.1,0.1,0.1],[0.1,0.1,0.1,0.1,0.1]]},{"name":"dropper","directions":1,"delays":[[1]]},{"name":"large_grenade","directions":1,"delays":[[1]]},{"name":"large_grenade_armed","directions":1,"delays":[[0.1,0.1]]},{"name":"large_grenade_ass","directions":1,"delays":[[1]]},{"name":"large_grenade_locked","directions":1,"delays":[[1]]},{"name":"lid_beaker","directions":1,"delays":[[1]]},{"name":"lid_beakerlarge","directions":1,"delays":[[1]]},{"name":"lid_beakernoreact","directions":1,"delays":[[1]]},{"name":"lid_bottle","directions":1,"delays":[[1]]},{"name":"lid_vial","directions":1,"delays":[[1]]},{"name":"molten","directions":1,"delays":[[1]]},{"name":"pill","directions":1,"delays":[[1]]},{"name":"pill1","directions":1,"delays":[[1]]},{"name":"pill10","directions":1,"delays":[[1]]},{"name":"pill11","directions":1,"delays":[[1]]},{"name":"pill12","directions":1,"delays":[[1]]},{"name":"pill13","directions":1,"delays":[[1]]},{"name":"pill14","directions":1,"delays":[[1]]},{"name":"pill15","directions":1,"delays":[[1]]},{"name":"pill16","directions":1,"delays":[[1]]},{"name":"pill17","directions":1,"delays":[[1]]},{"name":"pill18","directions":1,"delays":[[1]]},{"name":"pill19","directions":1,"delays":[[1]]},{"name":"pill2","directions":1,"delays":[[1]]},{"name":"pill20","directions":1,"delays":[[1]]},{"name":"pill3","directions":1,"delays":[[1]]},{"name":"pill4","directions":1,"delays":[[1]]},{"name":"pill5","directions":1,"delays":[[1]]},{"name":"pill6","directions":1,"delays":[[1]]},{"name":"pill7","directions":1,"delays":[[1]]},{"name":"pill8","directions":1,"delays":[[1]]},{"name":"pill9","directions":1,"delays":[[1]]},{"name":"pill_canister","directions":1,"delays":[[1]]},{"name":"syringeproj","directions":1,"delays":[[1]]},{"name":"vial","directions":1,"delays":[[1]]},{"name":"weedpuff","directions":4,"delays":[[0.1,0.1,0.1,0.1,0.1],[0.1,0.1,0.1,0.1,0.1],[0.1,0.1,0.1,0.1,0.1],[0.1,0.1,0.1,0.1,0.1]]}]}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 489 B After Width: | Height: | Size: 257 B |
Reference in New Issue
Block a user