Files
tbd-station-14/Content.Shared/Chemistry/Components/Solution.cs
blueDev2 c8f2ddc729 Add ReagentWhitelist to Injector component and system (#28262)
* Add reagent whitelist to Injector component and system

* Apply suggested Changes

* Remove LINQ function for performance

* Update Content.Server/Chemistry/EntitySystems/InjectorSystem.cs

Co-authored-by: Ed <96445749+TheShuEd@users.noreply.github.com>

---------

Co-authored-by: Ed <96445749+TheShuEd@users.noreply.github.com>
2024-09-12 13:29:11 +03:00

933 lines
31 KiB
C#

using Content.Shared.Chemistry.Reagent;
using Content.Shared.FixedPoint;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using System.Collections;
using System.Linq;
using Content.Shared.Chemistry.Components.SolutionManager;
namespace Content.Shared.Chemistry.Components
{
/// <summary>
/// A solution of reagents.
/// </summary>
[Serializable, NetSerializable]
[DataDefinition]
public sealed partial class Solution : IEnumerable<ReagentQuantity>, ISerializationHooks
{
// This is a list because it is actually faster to add and remove reagents from
// a list than a dictionary, though contains-reagent checks are slightly slower,
[DataField("reagents")]
public List<ReagentQuantity> Contents;
/// <summary>
/// The calculated total volume of all reagents in the solution (ex. Total volume of liquid in beaker).
/// </summary>
[ViewVariables]
public FixedPoint2 Volume { get; set; }
/// <summary>
/// Maximum volume this solution supports.
/// </summary>
/// <remarks>
/// A value of zero means the maximum will automatically be set equal to the current volume during
/// initialization. Note that most solution methods ignore max volume altogether, but various solution
/// systems use this.
/// </remarks>
[DataField("maxVol")]
[ViewVariables(VVAccess.ReadWrite)]
public FixedPoint2 MaxVolume { get; set; } = FixedPoint2.Zero;
public float FillFraction => MaxVolume == 0 ? 1 : Volume.Float() / MaxVolume.Float();
/// <summary>
/// If reactions will be checked for when adding reagents to the container.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("canReact")]
public bool CanReact { get; set; } = true;
/// <summary>
/// Volume needed to fill this container.
/// </summary>
[ViewVariables]
public FixedPoint2 AvailableVolume => MaxVolume - Volume;
/// <summary>
/// The temperature of the reagents in the solution.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("temperature")]
public float Temperature { get; set; } = 293.15f;
/// <summary>
/// The name of this solution, if it is contained in some <see cref="SolutionContainerManagerComponent"/>
/// </summary>
[DataField]
public string? Name;
/// <summary>
/// Checks if a solution can fit into the container.
/// </summary>
public bool CanAddSolution(Solution solution)
{
return solution.Volume <= AvailableVolume;
}
/// <summary>
/// The total heat capacity of all reagents in the solution.
/// </summary>
[ViewVariables] private float _heatCapacity;
/// <summary>
/// If true, then <see cref="_heatCapacity"/> needs to be recomputed.
/// </summary>
[ViewVariables] private bool _heatCapacityDirty = true;
[ViewVariables(VVAccess.ReadWrite)]
private int _heatCapacityUpdateCounter;
// This value is arbitrary btw.
private const int HeatCapacityUpdateInterval = 15;
public void UpdateHeatCapacity(IPrototypeManager? protoMan)
{
IoCManager.Resolve(ref protoMan);
DebugTools.Assert(_heatCapacityDirty);
_heatCapacityDirty = false;
_heatCapacity = 0;
foreach (var (reagent, quantity) in Contents)
{
_heatCapacity += (float) quantity *
protoMan.Index<ReagentPrototype>(reagent.Prototype).SpecificHeat;
}
_heatCapacityUpdateCounter = 0;
}
public float GetHeatCapacity(IPrototypeManager? protoMan)
{
if (_heatCapacityDirty)
UpdateHeatCapacity(protoMan);
return _heatCapacity;
}
public void CheckRecalculateHeatCapacity()
{
// For performance, we have a few ways for heat capacity to get modified without a full recalculation.
// To avoid these drifting too much due to float error, we mark it as dirty after N such operations,
// so it will be recalculated.
if (++_heatCapacityUpdateCounter >= HeatCapacityUpdateInterval)
_heatCapacityDirty = true;
}
public float GetThermalEnergy(IPrototypeManager? protoMan)
{
return GetHeatCapacity(protoMan) * Temperature;
}
/// <summary>
/// Constructs an empty solution (ex. an empty beaker).
/// </summary>
public Solution() : this(2) // Most objects on the station hold only 1 or 2 reagents.
{
}
/// <summary>
/// Constructs an empty solution (ex. an empty beaker).
/// </summary>
public Solution(int capacity)
{
Contents = new(capacity);
}
/// <summary>
/// Constructs a solution containing 100% of a reagent (ex. A beaker of pure water).
/// </summary>
/// <param name="prototype">The prototype ID of the reagent to add.</param>
/// <param name="quantity">The quantity in milli-units.</param>
public Solution(string prototype, FixedPoint2 quantity, List<ReagentData>? data = null) : this()
{
AddReagent(new ReagentId(prototype, data), quantity);
}
public Solution(IEnumerable<ReagentQuantity> reagents, bool setMaxVol = true)
{
Contents = new(reagents);
Volume = FixedPoint2.Zero;
foreach (var reagent in Contents)
{
Volume += reagent.Quantity;
}
if (setMaxVol)
MaxVolume = Volume;
ValidateSolution();
}
public Solution(Solution solution)
{
Contents = solution.Contents.ShallowClone();
Volume = solution.Volume;
MaxVolume = solution.MaxVolume;
Temperature = solution.Temperature;
_heatCapacity = solution._heatCapacity;
_heatCapacityDirty = solution._heatCapacityDirty;
_heatCapacityUpdateCounter = solution._heatCapacityUpdateCounter;
ValidateSolution();
}
public Solution Clone()
{
return new Solution(this);
}
[AssertionMethod]
public void ValidateSolution()
{
// sandbox forbids: [Conditional("DEBUG")]
#if DEBUG
// Correct volume
DebugTools.Assert(Contents.Select(x => x.Quantity).Sum() == Volume);
// All reagents have at least some reagent present.
DebugTools.Assert(!Contents.Any(x => x.Quantity <= FixedPoint2.Zero));
// No duplicate reagents iDs
DebugTools.Assert(Contents.Select(x => x.Reagent).ToHashSet().Count == Contents.Count);
// If it isn't flagged as dirty, check heat capacity is correct.
if (!_heatCapacityDirty)
{
var cur = _heatCapacity;
_heatCapacityDirty = true;
UpdateHeatCapacity(null);
DebugTools.Assert(MathHelper.CloseTo(_heatCapacity, cur, tolerance: 0.01));
}
#endif
}
void ISerializationHooks.AfterDeserialization()
{
Volume = FixedPoint2.Zero;
foreach (var reagent in Contents)
{
Volume += reagent.Quantity;
}
if (MaxVolume == FixedPoint2.Zero)
MaxVolume = Volume;
}
public bool ContainsPrototype(string prototype)
{
foreach (var (reagent, _) in Contents)
{
if (reagent.Prototype == prototype)
return true;
}
return false;
}
public bool ContainsReagent(ReagentId id)
{
foreach (var (reagent, _) in Contents)
{
if (reagent == id)
return true;
}
return false;
}
public bool ContainsReagent(string reagentId, List<ReagentData>? data)
=> ContainsReagent(new(reagentId, data));
public bool TryGetReagent(ReagentId id, out ReagentQuantity quantity)
{
foreach (var tuple in Contents)
{
if (tuple.Reagent != id)
continue;
DebugTools.Assert(tuple.Quantity > FixedPoint2.Zero);
quantity = tuple;
return true;
}
quantity = new ReagentQuantity(id, FixedPoint2.Zero);
return false;
}
public bool TryGetReagentQuantity(ReagentId id, out FixedPoint2 volume)
{
volume = FixedPoint2.Zero;
if (!TryGetReagent(id, out var quant))
return false;
volume = quant.Quantity;
return true;
}
[Pure]
public ReagentQuantity GetReagent(ReagentId id)
{
TryGetReagent(id, out var quantity);
return quantity;
}
public ReagentQuantity this[ReagentId id]
{
get
{
if (!TryGetReagent(id, out var quantity))
throw new KeyNotFoundException(id.ToString());
return quantity;
}
}
/// <summary>
/// Get the volume/quantity of a single reagent in the solution.
/// </summary>
[Pure]
public FixedPoint2 GetReagentQuantity(ReagentId id)
{
return GetReagent(id).Quantity;
}
/// <summary>
/// Gets the total volume of all reagents in the solution with the given prototype Id.
/// If you only want the volume of a single reagent, use <see cref="GetReagentQuantity"/>
/// </summary>
[Pure]
public FixedPoint2 GetTotalPrototypeQuantity(params string[] prototypes)
{
var total = FixedPoint2.Zero;
foreach (var (reagent, quantity) in Contents)
{
if (prototypes.Contains(reagent.Prototype))
total += quantity;
}
return total;
}
public FixedPoint2 GetTotalPrototypeQuantity(string id)
{
var total = FixedPoint2.Zero;
foreach (var (reagent, quantity) in Contents)
{
if (id == reagent.Prototype)
total += quantity;
}
return total;
}
public ReagentId? GetPrimaryReagentId()
{
if (Contents.Count == 0)
return null;
ReagentQuantity max = default;
foreach (var reagent in Contents)
{
if (reagent.Quantity >= max.Quantity)
{
max = reagent;
}
}
return max.Reagent;
}
/// <summary>
/// Adds a given quantity of a reagent directly into the solution.
/// </summary>
/// <param name="prototype">The prototype ID of the reagent to add.</param>
/// <param name="quantity">The quantity in milli-units.</param>
public void AddReagent(string prototype, FixedPoint2 quantity, bool dirtyHeatCap = true)
=> AddReagent(new ReagentId(prototype, null), quantity, dirtyHeatCap);
/// <summary>
/// Adds a given quantity of a reagent directly into the solution.
/// </summary>
/// <param name="id">The reagent to add.</param>
/// <param name="quantity">The quantity in milli-units.</param>
public void AddReagent(ReagentId id, FixedPoint2 quantity, bool dirtyHeatCap = true)
{
if (quantity <= 0)
{
DebugTools.Assert(quantity == 0, "Attempted to add negative reagent quantity");
return;
}
Volume += quantity;
_heatCapacityDirty |= dirtyHeatCap;
for (var i = 0; i < Contents.Count; i++)
{
var (reagent, existingQuantity) = Contents[i];
if (reagent != id)
continue;
Contents[i] = new ReagentQuantity(id, existingQuantity + quantity);
ValidateSolution();
return;
}
Contents.Add(new ReagentQuantity(id, quantity));
ValidateSolution();
}
/// <summary>
/// Adds a given quantity of a reagent directly into the solution.
/// </summary>
/// <param name="reagentId">The reagent to add.</param>
/// <param name="quantity">The quantity in milli-units.</param>
public void AddReagent(ReagentPrototype proto, ReagentId reagentId, FixedPoint2 quantity)
{
AddReagent(reagentId, quantity, false);
_heatCapacity += quantity.Float() * proto.SpecificHeat;
CheckRecalculateHeatCapacity();
}
public void AddReagent(ReagentQuantity reagentQuantity)
=> AddReagent(reagentQuantity.Reagent, reagentQuantity.Quantity);
/// <summary>
/// Adds a given quantity of a reagent directly into the solution.
/// </summary>
/// <param name="proto">The prototype of the reagent to add.</param>
/// <param name="quantity">The quantity in milli-units.</param>
public void AddReagent(ReagentPrototype proto, FixedPoint2 quantity, float temperature, IPrototypeManager? protoMan, List<ReagentData>? data = null)
{
if (_heatCapacityDirty)
UpdateHeatCapacity(protoMan);
var totalThermalEnergy = Temperature * _heatCapacity + temperature * proto.SpecificHeat;
AddReagent(new ReagentId(proto.ID, data), quantity);
Temperature = _heatCapacity == 0 ? 0 : totalThermalEnergy / _heatCapacity;
}
/// <summary>
/// Scales the amount of solution by some integer quantity.
/// </summary>
/// <param name="scale">The scalar to modify the solution by.</param>
public void ScaleSolution(int scale)
{
if (scale == 1)
return;
if (scale <= 0)
{
RemoveAllSolution();
return;
}
_heatCapacity *= scale;
Volume *= scale;
CheckRecalculateHeatCapacity();
for (int i = 0; i < Contents.Count; i++)
{
var old = Contents[i];
Contents[i] = new ReagentQuantity(old.Reagent, old.Quantity * scale);
}
ValidateSolution();
}
/// <summary>
/// Scales the amount of solution.
/// </summary>
/// <param name="scale">The scalar to modify the solution by.</param>
public void ScaleSolution(float scale)
{
if (scale == 1)
return;
if (scale == 0)
{
RemoveAllSolution();
return;
}
Volume = FixedPoint2.Zero;
for (int i = Contents.Count - 1; i >= 0; i--)
{
var old = Contents[i];
var newQuantity = old.Quantity * scale;
if (newQuantity == FixedPoint2.Zero)
Contents.RemoveSwap(i);
else
{
Contents[i] = new ReagentQuantity(old.Reagent, newQuantity);
Volume += newQuantity;
}
}
_heatCapacityDirty = true;
ValidateSolution();
}
/// <summary>
/// Attempts to remove an amount of reagent from the solution.
/// </summary>
/// <param name="toRemove">The reagent to be removed.</param>
/// <returns>How much reagent was actually removed. Zero if the reagent is not present on the solution.</returns>
public FixedPoint2 RemoveReagent(ReagentQuantity toRemove, bool preserveOrder = false, bool ignoreReagentData = false)
{
if (toRemove.Quantity <= FixedPoint2.Zero)
return FixedPoint2.Zero;
List<int> reagentIndices = new List<int>();
int totalRemoveVolume = 0;
for (var i = 0; i < Contents.Count; i++)
{
var (reagent, quantity) = Contents[i];
if (ignoreReagentData)
{
if (reagent.Prototype != toRemove.Reagent.Prototype)
continue;
}
else
{
if (reagent != toRemove.Reagent)
continue;
}
//We prepend instead of add to handle the Contents list back-to-front later down.
//It makes RemoveSwap safe to use.
totalRemoveVolume += quantity.Value;
reagentIndices.Insert(0, i);
}
if (totalRemoveVolume <= 0)
{
// Reagent is not on the solution...
return FixedPoint2.Zero;
}
FixedPoint2 removedQuantity = 0;
for (var i = 0; i < reagentIndices.Count; i++)
{
var (reagent, curQuantity) = Contents[reagentIndices[i]];
// This is set up such that integer rounding will tend to take more reagents.
var split = ((long)toRemove.Quantity.Value) * curQuantity.Value / totalRemoveVolume;
var splitQuantity = FixedPoint2.FromCents((int)split);
var newQuantity = curQuantity - splitQuantity;
_heatCapacityDirty = true;
if (newQuantity <= 0)
{
if (!preserveOrder)
Contents.RemoveSwap(reagentIndices[i]);
else
Contents.RemoveAt(reagentIndices[i]);
Volume -= curQuantity;
removedQuantity += curQuantity;
continue;
}
Contents[reagentIndices[i]] = new ReagentQuantity(reagent, newQuantity);
Volume -= splitQuantity;
removedQuantity += splitQuantity;
}
ValidateSolution();
return removedQuantity;
}
/// <summary>
/// Attempts to remove an amount of reagent from the solution.
/// </summary>
/// <param name="prototype">The prototype of the reagent to be removed.</param>
/// <param name="quantity">The amount of reagent to remove.</param>
/// <returns>How much reagent was actually removed. Zero if the reagent is not present on the solution.</returns>
public FixedPoint2 RemoveReagent(string prototype, FixedPoint2 quantity, List<ReagentData>? data = null, bool ignoreReagentData = false)
{
return RemoveReagent(new ReagentQuantity(prototype, quantity, data), ignoreReagentData: ignoreReagentData);
}
/// <summary>
/// Attempts to remove an amount of reagent from the solution.
/// </summary>
/// <param name="reagentId">The reagent to be removed.</param>
/// <param name="quantity">The amount of reagent to remove.</param>
/// <returns>How much reagent was actually removed. Zero if the reagent is not present on the solution.</returns>
public FixedPoint2 RemoveReagent(ReagentId reagentId, FixedPoint2 quantity, bool preserveOrder = false, bool ignoreReagentData = false)
{
return RemoveReagent(new ReagentQuantity(reagentId, quantity), preserveOrder, ignoreReagentData);
}
public void RemoveAllSolution()
{
Contents.Clear();
Volume = FixedPoint2.Zero;
_heatCapacityDirty = false;
_heatCapacity = 0;
}
/// <summary>
/// Splits a solution without the specified reagent prototypes.
/// </summary>
public Solution SplitSolutionWithout(FixedPoint2 toTake, params string[] excludedPrototypes)
{
// First remove the blacklisted prototypes
List<ReagentQuantity> excluded = new();
foreach (var id in excludedPrototypes)
{
foreach (var tuple in Contents)
{
if (tuple.Reagent.Prototype != id)
continue;
excluded.Add(tuple);
RemoveReagent(tuple);
break;
}
}
// Then split the solution
var sol = SplitSolution(toTake);
// Then re-add the excluded reagents to the original solution.
foreach (var reagent in excluded)
{
AddReagent(reagent);
}
return sol;
}
/// <summary>
/// Splits a solution with only the specified reagent prototypes.
/// </summary>
public Solution SplitSolutionWithOnly(FixedPoint2 toTake, params string[] includedPrototypes)
{
// First remove the non-included prototypes
List<ReagentQuantity> excluded = new();
for (var i = Contents.Count - 1; i >= 0; i--)
{
if (includedPrototypes.Contains(Contents[i].Reagent.Prototype))
continue;
excluded.Add(Contents[i]);
RemoveReagent(Contents[i]);
}
// Then split the solution
var sol = SplitSolution(toTake);
// Then re-add the excluded reagents to the original solution.
foreach (var reagent in excluded)
{
AddReagent(reagent);
}
return sol;
}
public Solution SplitSolution(FixedPoint2 toTake)
{
if (toTake <= FixedPoint2.Zero)
return new Solution();
Solution newSolution;
if (toTake >= Volume)
{
newSolution = Clone();
RemoveAllSolution();
return newSolution;
}
var origVol = Volume;
var effVol = Volume.Value;
newSolution = new Solution(Contents.Count) { Temperature = Temperature };
var remaining = (long) toTake.Value;
for (var i = Contents.Count - 1; i >= 0; i--) // iterate backwards because of remove swap.
{
var (reagent, quantity) = Contents[i];
// This is set up such that integer rounding will tend to take more reagents.
var split = remaining * quantity.Value / effVol;
if (split <= 0)
{
effVol -= quantity.Value;
DebugTools.Assert(split == 0, "Negative solution quantity while splitting? Long/int overflow?");
continue;
}
var splitQuantity = FixedPoint2.FromCents((int) split);
var newQuantity = quantity - splitQuantity;
DebugTools.Assert(newQuantity >= 0);
if (newQuantity > FixedPoint2.Zero)
Contents[i] = new ReagentQuantity(reagent, newQuantity);
else
Contents.RemoveSwap(i);
newSolution.Contents.Add(new ReagentQuantity(reagent, splitQuantity));
Volume -= splitQuantity;
remaining -= split;
effVol -= quantity.Value;
}
newSolution.Volume = origVol - Volume;
DebugTools.Assert(remaining >= 0);
DebugTools.Assert(remaining == 0 || Volume == FixedPoint2.Zero);
_heatCapacityDirty = true;
newSolution._heatCapacityDirty = true;
ValidateSolution();
newSolution.ValidateSolution();
return newSolution;
}
/// <summary>
/// Variant of <see cref="SplitSolution(FixedPoint2)"/> that doesn't return a new solution containing the removed reagents.
/// </summary>
/// <param name="toTake">The quantity of this solution to remove</param>
public void RemoveSolution(FixedPoint2 toTake)
{
if (toTake <= FixedPoint2.Zero)
return;
if (toTake >= Volume)
{
RemoveAllSolution();
return;
}
var effVol = Volume.Value;
Volume -= toTake;
var remaining = (long) toTake.Value;
for (var i = Contents.Count - 1; i >= 0; i--)// iterate backwards because of remove swap.
{
var (reagent, quantity) = Contents[i];
// This is set up such that integer rounding will tend to take more reagents.
var split = remaining * quantity.Value / effVol;
if (split <= 0)
{
effVol -= quantity.Value;
DebugTools.Assert(split == 0, "Negative solution quantity while splitting? Long/int overflow?");
continue;
}
var splitQuantity = FixedPoint2.FromCents((int) split);
var newQuantity = quantity - splitQuantity;
if (newQuantity > FixedPoint2.Zero)
Contents[i] = new ReagentQuantity(reagent, newQuantity);
else
Contents.RemoveSwap(i);
remaining -= split;
effVol -= quantity.Value;
}
DebugTools.Assert(remaining >= 0);
DebugTools.Assert(remaining == 0 || Volume == FixedPoint2.Zero);
_heatCapacityDirty = true;
ValidateSolution();
}
public void AddSolution(Solution otherSolution, IPrototypeManager? protoMan)
{
if (otherSolution.Volume <= FixedPoint2.Zero)
return;
Volume += otherSolution.Volume;
var closeTemps = MathHelper.CloseTo(otherSolution.Temperature, Temperature);
float totalThermalEnergy = 0;
if (!closeTemps)
{
IoCManager.Resolve(ref protoMan);
if (_heatCapacityDirty)
UpdateHeatCapacity(protoMan);
if (otherSolution._heatCapacityDirty)
otherSolution.UpdateHeatCapacity(protoMan);
totalThermalEnergy = _heatCapacity * Temperature + otherSolution._heatCapacity * otherSolution.Temperature;
}
for (var i = 0; i < otherSolution.Contents.Count; i++)
{
var (otherReagent, otherQuantity) = otherSolution.Contents[i];
var found = false;
for (var j = 0; j < Contents.Count; j++)
{
var (reagent, quantity) = Contents[j];
if (reagent == otherReagent)
{
found = true;
Contents[j] = new ReagentQuantity(reagent, quantity + otherQuantity);
break;
}
}
if (!found)
{
Contents.Add(new ReagentQuantity(otherReagent, otherQuantity));
}
}
_heatCapacity += otherSolution._heatCapacity;
CheckRecalculateHeatCapacity();
if (closeTemps)
_heatCapacityDirty |= otherSolution._heatCapacityDirty;
else
Temperature = _heatCapacity == 0 ? 0 : totalThermalEnergy / _heatCapacity;
ValidateSolution();
}
public Color GetColorWithout(IPrototypeManager? protoMan, params string[] without)
{
if (Volume == FixedPoint2.Zero)
{
return Color.Transparent;
}
IoCManager.Resolve(ref protoMan);
Color mixColor = default;
var runningTotalQuantity = FixedPoint2.New(0);
bool first = true;
foreach (var (reagent, quantity) in Contents)
{
if (without.Contains(reagent.Prototype))
continue;
runningTotalQuantity += quantity;
if (!protoMan.TryIndex(reagent.Prototype, out ReagentPrototype? proto))
{
continue;
}
if (first)
{
first = false;
mixColor = proto.SubstanceColor;
continue;
}
var interpolateValue = quantity.Float() / runningTotalQuantity.Float();
mixColor = Color.InterpolateBetween(mixColor, proto.SubstanceColor, interpolateValue);
}
return mixColor;
}
public Color GetColor(IPrototypeManager? protoMan)
{
return GetColorWithout(protoMan);
}
public Color GetColorWithOnly(IPrototypeManager? protoMan, params string[] included)
{
if (Volume == FixedPoint2.Zero)
{
return Color.Transparent;
}
IoCManager.Resolve(ref protoMan);
Color mixColor = default;
var runningTotalQuantity = FixedPoint2.New(0);
bool first = true;
foreach (var (reagent, quantity) in Contents)
{
if (!included.Contains(reagent.Prototype))
continue;
runningTotalQuantity += quantity;
if (!protoMan.TryIndex(reagent.Prototype, out ReagentPrototype? proto))
{
continue;
}
if (first)
{
first = false;
mixColor = proto.SubstanceColor;
continue;
}
var interpolateValue = quantity.Float() / runningTotalQuantity.Float();
mixColor = Color.InterpolateBetween(mixColor, proto.SubstanceColor, interpolateValue);
}
return mixColor;
}
#region Enumeration
public IEnumerator<ReagentQuantity> GetEnumerator()
{
return Contents.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
public void SetContents(IEnumerable<ReagentQuantity> reagents, bool setMaxVol = false)
{
Volume = 0;
RemoveAllSolution();
_heatCapacityDirty = true;
Contents = new(reagents);
foreach (var reagent in Contents)
{
Volume += reagent.Quantity;
}
if (setMaxVol)
MaxVolume = Volume;
ValidateSolution();
}
public Dictionary<ReagentPrototype, FixedPoint2> GetReagentPrototypes(IPrototypeManager protoMan)
{
var dict = new Dictionary<ReagentPrototype, FixedPoint2>(Contents.Count);
foreach (var (reagent, quantity) in Contents)
{
var proto = protoMan.Index<ReagentPrototype>(reagent.Prototype);
dict[proto] = quantity + dict.GetValueOrDefault(proto);
}
return dict;
}
}
}