* 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.
239 lines
7.8 KiB
C#
239 lines
7.8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using Content.Shared.Chemistry;
|
|
using Robust.Shared.GameObjects;
|
|
using Robust.Shared.IoC;
|
|
using Robust.Shared.Maths;
|
|
using Robust.Shared.Prototypes;
|
|
using Robust.Shared.Serialization;
|
|
using Robust.Shared.ViewVariables;
|
|
|
|
namespace Content.Shared.GameObjects.Components.Chemistry
|
|
{
|
|
public class SolutionComponent : Component
|
|
{
|
|
#pragma warning disable 649
|
|
[Dependency] private readonly IPrototypeManager _prototypeManager;
|
|
#pragma warning restore 649
|
|
|
|
[ViewVariables]
|
|
protected Solution _containedSolution = new Solution();
|
|
protected int _maxVolume;
|
|
private SolutionCaps _capabilities;
|
|
|
|
/// <summary>
|
|
/// Triggered when the solution contents change.
|
|
/// </summary>
|
|
public event Action SolutionChanged;
|
|
|
|
/// <summary>
|
|
/// The maximum volume of the container.
|
|
/// </summary>
|
|
[ViewVariables(VVAccess.ReadWrite)]
|
|
public int MaxVolume
|
|
{
|
|
get => _maxVolume;
|
|
set => _maxVolume = value; // Note that the contents won't spill out if the capacity is reduced.
|
|
}
|
|
|
|
/// <summary>
|
|
/// The total volume of all the of the reagents in the container.
|
|
/// </summary>
|
|
[ViewVariables]
|
|
public int CurrentVolume => _containedSolution.TotalVolume;
|
|
|
|
/// <summary>
|
|
/// The volume without reagents remaining in the container.
|
|
/// </summary>
|
|
[ViewVariables]
|
|
public int EmptyVolume => MaxVolume - CurrentVolume;
|
|
|
|
/// <summary>
|
|
/// The current blended color of all the reagents in the container.
|
|
/// </summary>
|
|
[ViewVariables(VVAccess.ReadWrite)]
|
|
public Color SubstanceColor { get; private set; }
|
|
|
|
/// <summary>
|
|
/// The current capabilities of this container (is the top open to pour? can I inject it into another object?).
|
|
/// </summary>
|
|
[ViewVariables(VVAccess.ReadWrite)]
|
|
public SolutionCaps Capabilities
|
|
{
|
|
get => _capabilities;
|
|
set => _capabilities = value;
|
|
}
|
|
|
|
public IReadOnlyList<Solution.ReagentQuantity> ReagentList => _containedSolution.Contents;
|
|
|
|
/// <summary>
|
|
/// Shortcut for Capabilities PourIn flag to avoid binary operators.
|
|
/// </summary>
|
|
public bool CanPourIn => (Capabilities & SolutionCaps.PourIn) != 0;
|
|
/// <summary>
|
|
/// 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";
|
|
|
|
/// <inheritdoc />
|
|
public sealed override uint? NetID => ContentNetIDs.SOLUTION;
|
|
|
|
/// <inheritdoc />
|
|
public override void ExposeData(ObjectSerializer serializer)
|
|
{
|
|
base.ExposeData(serializer);
|
|
|
|
serializer.DataField(ref _maxVolume, "maxVol", 0);
|
|
serializer.DataField(ref _containedSolution, "contents", _containedSolution);
|
|
serializer.DataField(ref _capabilities, "caps", SolutionCaps.None);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override void Startup()
|
|
{
|
|
base.Startup();
|
|
|
|
RecalculateColor();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override void Shutdown()
|
|
{
|
|
base.Shutdown();
|
|
|
|
_containedSolution.RemoveAllSolution();
|
|
_containedSolution = new Solution();
|
|
}
|
|
|
|
public void RemoveAllSolution()
|
|
{
|
|
_containedSolution.RemoveAllSolution();
|
|
OnSolutionChanged();
|
|
}
|
|
|
|
public bool TryRemoveReagent(string reagentId, int quantity)
|
|
{
|
|
if (!ContainsReagent(reagentId, out var currentQuantity)) return false;
|
|
|
|
_containedSolution.RemoveReagent(reagentId, quantity);
|
|
OnSolutionChanged();
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempt to remove the specified quantity from this solution
|
|
/// </summary>
|
|
/// <param name="quantity">Quantity of this solution to remove</param>
|
|
/// <returns>Whether or not the solution was successfully removed</returns>
|
|
public bool TryRemoveSolution(int quantity)
|
|
{
|
|
if (CurrentVolume == 0)
|
|
return false;
|
|
|
|
_containedSolution.RemoveSolution(quantity);
|
|
OnSolutionChanged();
|
|
return true;
|
|
}
|
|
|
|
public Solution SplitSolution(int quantity)
|
|
{
|
|
var solutionSplit = _containedSolution.SplitSolution(quantity);
|
|
OnSolutionChanged();
|
|
return solutionSplit;
|
|
}
|
|
|
|
protected void RecalculateColor()
|
|
{
|
|
if(_containedSolution.TotalVolume == 0)
|
|
SubstanceColor = Color.White;
|
|
|
|
Color mixColor = default;
|
|
float runningTotalQuantity = 0;
|
|
|
|
foreach (var reagent in _containedSolution)
|
|
{
|
|
runningTotalQuantity += reagent.Quantity;
|
|
|
|
if(!_prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype proto))
|
|
continue;
|
|
|
|
if (mixColor == default)
|
|
mixColor = proto.SubstanceColor;
|
|
|
|
mixColor = BlendRGB(mixColor, proto.SubstanceColor, reagent.Quantity / runningTotalQuantity);
|
|
}
|
|
}
|
|
|
|
private Color BlendRGB(Color rgb1, Color rgb2, float amount)
|
|
{
|
|
var r = (float)Math.Round(rgb1.R + (rgb2.R - rgb1.R) * amount, 1);
|
|
var g = (float)Math.Round(rgb1.G + (rgb2.G - rgb1.G) * amount, 1);
|
|
var b = (float)Math.Round(rgb1.B + (rgb2.B - rgb1.B) * amount, 1);
|
|
var alpha = (float)Math.Round(rgb1.A + (rgb2.A - rgb1.A) * amount, 1);
|
|
|
|
return new Color(r, g, b, alpha);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override ComponentState GetComponentState()
|
|
{
|
|
return new SolutionComponentState();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
|
|
{
|
|
base.HandleComponentState(curState, nextState);
|
|
|
|
if(curState == null)
|
|
return;
|
|
|
|
var compState = (SolutionComponentState)curState;
|
|
|
|
//TODO: Make me work!
|
|
}
|
|
|
|
[Serializable, NetSerializable]
|
|
public class SolutionComponentState : ComponentState
|
|
{
|
|
public SolutionComponentState() : base(ContentNetIDs.SOLUTION) { }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if the solution contains the specified reagent.
|
|
/// </summary>
|
|
/// <param name="reagentId">The reagent to check for.</param>
|
|
/// <param name="quantity">Output the quantity of the reagent if it is contained, 0 if it isn't.</param>
|
|
/// <returns>Return true if the solution contains the reagent.</returns>
|
|
public bool ContainsReagent(string reagentId, out int quantity)
|
|
{
|
|
foreach (var reagent in _containedSolution.Contents)
|
|
{
|
|
if (reagent.ReagentId == reagentId)
|
|
{
|
|
quantity = reagent.Quantity;
|
|
return true;
|
|
}
|
|
}
|
|
quantity = 0;
|
|
return false;
|
|
}
|
|
|
|
protected virtual void OnSolutionChanged()
|
|
{
|
|
SolutionChanged?.Invoke();
|
|
}
|
|
}
|
|
}
|