Solution rejig (#12428)
This commit is contained in:
@@ -1,12 +1,13 @@
|
||||
using System.Collections;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.FixedPoint;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Collections;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Shared.Chemistry.Components
|
||||
{
|
||||
@@ -17,7 +18,8 @@ namespace Content.Shared.Chemistry.Components
|
||||
[DataDefinition]
|
||||
public sealed partial class Solution : IEnumerable<Solution.ReagentQuantity>, ISerializationHooks
|
||||
{
|
||||
// Most objects on the station hold only 1 or 2 reagents
|
||||
// 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 = new(2);
|
||||
|
||||
@@ -25,7 +27,41 @@ namespace Content.Shared.Chemistry.Components
|
||||
/// The calculated total volume of all reagents in the solution (ex. Total volume of liquid in beaker).
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public FixedPoint2 TotalVolume { get; set; }
|
||||
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>
|
||||
/// If reactions can occur via mixing.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("canMix")]
|
||||
public bool CanMix { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Volume needed to fill this container.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public FixedPoint2 AvailableVolume => MaxVolume - Volume;
|
||||
|
||||
/// <summary>
|
||||
/// The temperature of the reagents in the solution.
|
||||
@@ -34,40 +70,155 @@ namespace Content.Shared.Chemistry.Components
|
||||
[DataField("temperature")]
|
||||
public float Temperature { get; set; } = 293.15f;
|
||||
|
||||
public Color Color => GetColor();
|
||||
|
||||
/// <summary>
|
||||
/// The name of this solution, if it is contained in some <see cref="SolutionContainerManagerComponent"/>
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
public void UpdateHeatCapacity(IPrototypeManager? protoMan)
|
||||
{
|
||||
IoCManager.Resolve(ref protoMan);
|
||||
DebugTools.Assert(_heatCapacityDirty);
|
||||
_heatCapacityDirty = false;
|
||||
_heatCapacity = 0;
|
||||
foreach (var reagent in Contents)
|
||||
{
|
||||
_heatCapacity += (float) reagent.Quantity * protoMan.Index<ReagentPrototype>(reagent.ReagentId).SpecificHeat;
|
||||
}
|
||||
}
|
||||
|
||||
public float GetHeatCapacity(IPrototypeManager? protoMan)
|
||||
{
|
||||
if (_heatCapacityDirty)
|
||||
UpdateHeatCapacity(protoMan);
|
||||
return _heatCapacity;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructs an empty solution (ex. an empty beaker).
|
||||
/// </summary>
|
||||
public Solution() { }
|
||||
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="reagentId">The prototype ID of the reagent to add.</param>
|
||||
/// <param name="quantity">The quantity in milli-units.</param>
|
||||
public Solution(string reagentId, FixedPoint2 quantity)
|
||||
public Solution(string reagentId, FixedPoint2 quantity) : this()
|
||||
{
|
||||
AddReagent(reagentId, 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)
|
||||
{
|
||||
Volume = solution.Volume;
|
||||
_heatCapacity = solution._heatCapacity;
|
||||
_heatCapacityDirty = solution._heatCapacityDirty;
|
||||
Contents = solution.Contents.ShallowClone();
|
||||
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 reagent iDs
|
||||
DebugTools.Assert(Contents.Select(x => x.ReagentId).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));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
TotalVolume = FixedPoint2.Zero;
|
||||
Contents.ForEach(reagent => TotalVolume += reagent.Quantity);
|
||||
Volume = FixedPoint2.Zero;
|
||||
foreach (var reagent in Contents)
|
||||
{
|
||||
Volume += reagent.Quantity;
|
||||
}
|
||||
|
||||
if (MaxVolume == FixedPoint2.Zero)
|
||||
MaxVolume = Volume;
|
||||
}
|
||||
|
||||
public bool ContainsReagent(string reagentId)
|
||||
{
|
||||
return ContainsReagent(reagentId, out _);
|
||||
foreach (var reagent in Contents)
|
||||
{
|
||||
if (reagent.ReagentId == reagentId)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool ContainsReagent(string reagentId, out FixedPoint2 quantity)
|
||||
public bool TryGetReagent(string reagentId, out FixedPoint2 quantity)
|
||||
{
|
||||
foreach (var reagent in Contents)
|
||||
{
|
||||
@@ -82,15 +233,22 @@ namespace Content.Shared.Chemistry.Components
|
||||
return false;
|
||||
}
|
||||
|
||||
public string GetPrimaryReagentId()
|
||||
public string? GetPrimaryReagentId()
|
||||
{
|
||||
if (Contents.Count == 0)
|
||||
return null;
|
||||
|
||||
ReagentQuantity max = default;
|
||||
|
||||
foreach (var reagent in Contents)
|
||||
{
|
||||
return "";
|
||||
if (reagent.Quantity >= max.Quantity)
|
||||
{
|
||||
max = reagent;
|
||||
}
|
||||
}
|
||||
|
||||
var majorReagent = Contents.MaxBy(reagent => reagent.Quantity);
|
||||
return majorReagent.ReagentId;
|
||||
return max.ReagentId!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -98,16 +256,16 @@ namespace Content.Shared.Chemistry.Components
|
||||
/// </summary>
|
||||
/// <param name="reagentId">The prototype ID of the reagent to add.</param>
|
||||
/// <param name="quantity">The quantity in milli-units.</param>
|
||||
public void AddReagent(string reagentId, FixedPoint2 quantity, float? temperature = null)
|
||||
public void AddReagent(string reagentId, FixedPoint2 quantity, bool dirtyHeatCap = true)
|
||||
{
|
||||
if (quantity <= 0)
|
||||
{
|
||||
DebugTools.Assert(quantity == 0, "Attempted to add negative reagent quantity");
|
||||
return;
|
||||
if (!IoCManager.Resolve<IPrototypeManager>().TryIndex(reagentId, out ReagentPrototype? proto))
|
||||
proto = new ReagentPrototype();
|
||||
}
|
||||
|
||||
var actualTemp = temperature ?? Temperature;
|
||||
var oldThermalEnergy = Temperature * GetHeatCapacity();
|
||||
var addedThermalEnergy = (float) quantity * proto.SpecificHeat * actualTemp;
|
||||
Volume += quantity;
|
||||
_heatCapacityDirty |= dirtyHeatCap;
|
||||
for (var i = 0; i < Contents.Count; i++)
|
||||
{
|
||||
var reagent = Contents[i];
|
||||
@@ -115,16 +273,65 @@ namespace Content.Shared.Chemistry.Components
|
||||
continue;
|
||||
|
||||
Contents[i] = new ReagentQuantity(reagentId, reagent.Quantity + quantity);
|
||||
|
||||
TotalVolume += quantity;
|
||||
ThermalEnergy = oldThermalEnergy + addedThermalEnergy;
|
||||
ValidateSolution();
|
||||
return;
|
||||
}
|
||||
|
||||
Contents.Add(new ReagentQuantity(reagentId, quantity));
|
||||
ValidateSolution();
|
||||
}
|
||||
|
||||
TotalVolume += quantity;
|
||||
ThermalEnergy = oldThermalEnergy + addedThermalEnergy;
|
||||
/// <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)
|
||||
{
|
||||
AddReagent(proto.ID, quantity, false);
|
||||
_heatCapacity += quantity.Float() * proto.SpecificHeat;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
if (_heatCapacityDirty)
|
||||
UpdateHeatCapacity(protoMan);
|
||||
|
||||
var totalThermalEnergy = Temperature * _heatCapacity + temperature * proto.SpecificHeat;
|
||||
AddReagent(proto, 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;
|
||||
|
||||
for (int i = 0; i <= Contents.Count; i++)
|
||||
{
|
||||
var old = Contents[i];
|
||||
Contents[i] = new ReagentQuantity(old.ReagentId, old.Quantity * scale);
|
||||
}
|
||||
ValidateSolution();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -133,21 +340,31 @@ namespace Content.Shared.Chemistry.Components
|
||||
/// <param name="scale">The scalar to modify the solution by.</param>
|
||||
public void ScaleSolution(float scale)
|
||||
{
|
||||
if (scale.Equals(1f))
|
||||
if (scale == 1)
|
||||
return;
|
||||
|
||||
var tempContents = new List<ReagentQuantity>(Contents);
|
||||
foreach(var current in tempContents)
|
||||
if (scale == 0)
|
||||
{
|
||||
if(scale > 1)
|
||||
{
|
||||
AddReagent(current.ReagentId, current.Quantity * scale - current.Quantity);
|
||||
}
|
||||
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
|
||||
{
|
||||
RemoveReagent(current.ReagentId, current.Quantity - current.Quantity * scale);
|
||||
Contents[i] = new ReagentQuantity(old.ReagentId, newQuantity);
|
||||
Volume += newQuantity;
|
||||
}
|
||||
}
|
||||
|
||||
_heatCapacityDirty = true;
|
||||
ValidateSolution();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -159,11 +376,12 @@ namespace Content.Shared.Chemistry.Components
|
||||
{
|
||||
for (var i = 0; i < Contents.Count; i++)
|
||||
{
|
||||
if (Contents[i].ReagentId == reagentId)
|
||||
return Contents[i].Quantity;
|
||||
var reagent = Contents[i];
|
||||
if (reagent.ReagentId == reagentId)
|
||||
return reagent.Quantity;
|
||||
}
|
||||
|
||||
return FixedPoint2.New(0);
|
||||
return FixedPoint2.Zero;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -174,7 +392,7 @@ namespace Content.Shared.Chemistry.Components
|
||||
/// <returns>How much reagent was actually removed. Zero if the reagent is not present on the solution.</returns>
|
||||
public FixedPoint2 RemoveReagent(string reagentId, FixedPoint2 quantity)
|
||||
{
|
||||
if(quantity <= 0)
|
||||
if (quantity <= FixedPoint2.Zero)
|
||||
return FixedPoint2.Zero;
|
||||
|
||||
for (var i = 0; i < Contents.Count; i++)
|
||||
@@ -186,16 +404,19 @@ namespace Content.Shared.Chemistry.Components
|
||||
|
||||
var curQuantity = reagent.Quantity;
|
||||
var newQuantity = curQuantity - quantity;
|
||||
_heatCapacityDirty = true;
|
||||
|
||||
if (newQuantity <= 0)
|
||||
{
|
||||
Contents.RemoveSwap(i);
|
||||
TotalVolume -= curQuantity;
|
||||
Volume -= curQuantity;
|
||||
ValidateSolution();
|
||||
return curQuantity;
|
||||
}
|
||||
|
||||
Contents[i] = new ReagentQuantity(reagentId, newQuantity);
|
||||
TotalVolume -= quantity;
|
||||
Volume -= quantity;
|
||||
ValidateSolution();
|
||||
return quantity;
|
||||
}
|
||||
|
||||
@@ -203,104 +424,150 @@ namespace Content.Shared.Chemistry.Components
|
||||
return FixedPoint2.Zero;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the specified quantity from this solution.
|
||||
/// </summary>
|
||||
/// <param name="quantity">The quantity of this solution to remove</param>
|
||||
public void RemoveSolution(FixedPoint2 quantity)
|
||||
{
|
||||
if(quantity <= 0)
|
||||
return;
|
||||
|
||||
var ratio = (TotalVolume - quantity).Double() / TotalVolume.Double();
|
||||
|
||||
if (ratio <= 0)
|
||||
{
|
||||
RemoveAllSolution();
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < Contents.Count; i++)
|
||||
{
|
||||
var reagent = Contents[i];
|
||||
var oldQuantity = reagent.Quantity;
|
||||
|
||||
// quantity taken is always a little greedy, so fractional quantities get rounded up to the nearest
|
||||
// whole unit. This should prevent little bits of chemical remaining because of float rounding errors.
|
||||
var newQuantity = oldQuantity * ratio;
|
||||
|
||||
Contents[i] = new ReagentQuantity(reagent.ReagentId, newQuantity);
|
||||
}
|
||||
|
||||
TotalVolume *= ratio;
|
||||
}
|
||||
|
||||
public void RemoveAllSolution()
|
||||
{
|
||||
Contents.Clear();
|
||||
TotalVolume = FixedPoint2.New(0);
|
||||
Volume = FixedPoint2.Zero;
|
||||
_heatCapacityDirty = false;
|
||||
_heatCapacity = 0;
|
||||
}
|
||||
|
||||
public Solution SplitSolution(FixedPoint2 quantity)
|
||||
public Solution SplitSolution(FixedPoint2 toTake)
|
||||
{
|
||||
if (quantity <= 0)
|
||||
if (toTake <= FixedPoint2.Zero)
|
||||
return new Solution();
|
||||
|
||||
Solution newSolution;
|
||||
|
||||
if (quantity >= TotalVolume)
|
||||
if (toTake >= Volume)
|
||||
{
|
||||
newSolution = Clone();
|
||||
RemoveAllSolution();
|
||||
return newSolution;
|
||||
}
|
||||
|
||||
newSolution = new Solution();
|
||||
var newTotalVolume = FixedPoint2.New(0);
|
||||
var newHeatCapacity = 0.0d;
|
||||
var remainingVolume = TotalVolume;
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
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--)
|
||||
for (var i = Contents.Count - 1; i >= 0; i--) // iterate backwards because of remove swap.
|
||||
{
|
||||
if (remainingVolume == FixedPoint2.Zero)
|
||||
// shouldn't happen, but it can if someone, somehow has a reagent with 0-quantity in a solution.
|
||||
break;
|
||||
|
||||
var reagent = Contents[i];
|
||||
var ratio = (remainingVolume - quantity).Double() / remainingVolume.Double();
|
||||
if(!prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype? proto))
|
||||
proto = new ReagentPrototype();
|
||||
|
||||
remainingVolume -= reagent.Quantity;
|
||||
// This is set up such that integer rounding will tend to take more reagents.
|
||||
var split = remaining * reagent.Quantity.Value / effVol;
|
||||
|
||||
var newQuantity = reagent.Quantity * ratio;
|
||||
var splitQuantity = reagent.Quantity - newQuantity;
|
||||
if (split <= 0)
|
||||
{
|
||||
effVol -= reagent.Quantity.Value;
|
||||
DebugTools.Assert(split == 0, "Negative solution quantity while splitting? Long/int overflow?");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (newQuantity > 0)
|
||||
var splitQuantity = FixedPoint2.FromCents((int) split);
|
||||
var newQuantity = reagent.Quantity - splitQuantity;
|
||||
|
||||
DebugTools.Assert(newQuantity >= 0);
|
||||
|
||||
if (newQuantity > FixedPoint2.Zero)
|
||||
Contents[i] = new ReagentQuantity(reagent.ReagentId, newQuantity);
|
||||
else
|
||||
Contents.RemoveAt(i);
|
||||
Contents.RemoveSwap(i);
|
||||
|
||||
if (splitQuantity > 0)
|
||||
newSolution.Contents.Add(new ReagentQuantity(reagent.ReagentId, splitQuantity));
|
||||
|
||||
newTotalVolume += splitQuantity;
|
||||
newHeatCapacity += (float) splitQuantity * proto.SpecificHeat;
|
||||
quantity -= splitQuantity;
|
||||
newSolution.Contents.Add(new ReagentQuantity(reagent.ReagentId, splitQuantity));
|
||||
Volume -= splitQuantity;
|
||||
remaining -= split;
|
||||
effVol -= reagent.Quantity.Value;
|
||||
}
|
||||
|
||||
newSolution.TotalVolume = newTotalVolume;
|
||||
newSolution.Temperature = Temperature;
|
||||
TotalVolume -= newTotalVolume;
|
||||
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;
|
||||
}
|
||||
|
||||
public void AddSolution(Solution otherSolution)
|
||||
/// <summary>
|
||||
/// Variant of <see cref="SplitSolution(FixedPoint2)"/> that doesn't return a new solution containing the removed reagents.
|
||||
/// </summary>
|
||||
/// <param name="quantity">The quantity of this solution to remove</param>
|
||||
public void RemoveSolution(FixedPoint2 toTake)
|
||||
{
|
||||
var oldThermalEnergy = Temperature * GetHeatCapacity();
|
||||
var addedThermalEnergy = otherSolution.Temperature * otherSolution.GetHeatCapacity();
|
||||
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 = Contents[i];
|
||||
|
||||
// This is set up such that integer rounding will tend to take more reagents.
|
||||
var split = remaining * reagent.Quantity.Value / effVol;
|
||||
|
||||
if (split <= 0)
|
||||
{
|
||||
effVol -= reagent.Quantity.Value;
|
||||
DebugTools.Assert(split == 0, "Negative solution quantity while splitting? Long/int overflow?");
|
||||
continue;
|
||||
}
|
||||
|
||||
var splitQuantity = FixedPoint2.FromCents((int) split);
|
||||
var newQuantity = reagent.Quantity - splitQuantity;
|
||||
|
||||
if (newQuantity > FixedPoint2.Zero)
|
||||
Contents[i] = new ReagentQuantity(reagent.ReagentId, newQuantity);
|
||||
else
|
||||
Contents.RemoveSwap(i);
|
||||
|
||||
remaining -= split;
|
||||
effVol -= reagent.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 = otherSolution.Contents[i];
|
||||
@@ -323,65 +590,50 @@ namespace Content.Shared.Chemistry.Components
|
||||
}
|
||||
}
|
||||
|
||||
TotalVolume += otherSolution.TotalVolume;
|
||||
ThermalEnergy = oldThermalEnergy + addedThermalEnergy;
|
||||
_heatCapacity += otherSolution._heatCapacity;
|
||||
if (closeTemps)
|
||||
_heatCapacityDirty |= otherSolution._heatCapacityDirty;
|
||||
else
|
||||
Temperature = _heatCapacity == 0 ? 0 : totalThermalEnergy / _heatCapacity;
|
||||
|
||||
ValidateSolution();
|
||||
}
|
||||
|
||||
private Color GetColor()
|
||||
public Color GetColor(IPrototypeManager? protoMan)
|
||||
{
|
||||
if (TotalVolume == 0)
|
||||
if (Volume == FixedPoint2.Zero)
|
||||
{
|
||||
return Color.Transparent;
|
||||
}
|
||||
|
||||
IoCManager.Resolve(ref protoMan);
|
||||
|
||||
Color mixColor = default;
|
||||
var runningTotalQuantity = FixedPoint2.New(0);
|
||||
var protoManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
bool first = true;
|
||||
|
||||
foreach (var reagent in Contents)
|
||||
{
|
||||
runningTotalQuantity += reagent.Quantity;
|
||||
|
||||
if (!protoManager.TryIndex(reagent.ReagentId, out ReagentPrototype? proto))
|
||||
if (!protoMan.TryIndex(reagent.ReagentId, out ReagentPrototype? proto))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mixColor == default)
|
||||
if (first)
|
||||
{
|
||||
first = false;
|
||||
mixColor = proto.SubstanceColor;
|
||||
continue;
|
||||
}
|
||||
|
||||
var interpolateValue = (1 / runningTotalQuantity.Float()) * reagent.Quantity.Float();
|
||||
var interpolateValue = reagent.Quantity.Float() / runningTotalQuantity.Float();
|
||||
mixColor = Color.InterpolateBetween(mixColor, proto.SubstanceColor, interpolateValue);
|
||||
}
|
||||
return mixColor;
|
||||
}
|
||||
|
||||
public Solution Clone()
|
||||
{
|
||||
var volume = FixedPoint2.New(0);
|
||||
var heatCapacity = 0.0d;
|
||||
var newSolution = new Solution();
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
|
||||
for (var i = 0; i < Contents.Count; i++)
|
||||
{
|
||||
var reagent = Contents[i];
|
||||
if (!prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype? proto))
|
||||
proto = new ReagentPrototype();
|
||||
|
||||
newSolution.Contents.Add(reagent);
|
||||
volume += reagent.Quantity;
|
||||
heatCapacity += (float) reagent.Quantity * proto.SpecificHeat;
|
||||
}
|
||||
|
||||
newSolution.TotalVolume = volume;
|
||||
newSolution.Temperature = Temperature;
|
||||
return newSolution;
|
||||
}
|
||||
|
||||
[Obsolete("Use ReactiveSystem.DoEntityReaction")]
|
||||
public void DoEntityReaction(EntityUid uid, ReactionMethod method)
|
||||
{
|
||||
@@ -429,7 +681,23 @@ namespace Content.Shared.Chemistry.Components
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void SetContents(IEnumerable<ReagentQuantity> reagents, bool setMaxVol = false)
|
||||
{
|
||||
RemoveAllSolution();
|
||||
_heatCapacityDirty = true;
|
||||
Contents = new(reagents);
|
||||
foreach (var reagent in Contents)
|
||||
{
|
||||
Volume += reagent.Quantity;
|
||||
}
|
||||
|
||||
if (setMaxVol)
|
||||
MaxVolume = Volume;
|
||||
|
||||
ValidateSolution();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user