using Content.Shared.Interfaces.Chemistry; using Robust.Shared.Interfaces.Serialization; using Robust.Shared.IoC; using Robust.Shared.Serialization; using Robust.Shared.Utility; using Robust.Shared.ViewVariables; using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace Content.Shared.Chemistry { /// /// A solution of reagents. /// public class Solution : IExposeData, IEnumerable { // Most objects on the station hold only 1 or 2 reagents [ViewVariables] private List _contents = new List(2); public IReadOnlyList Contents => _contents; /// /// The calculated total volume of all reagents in the solution (ex. Total volume of liquid in beaker). /// [ViewVariables] public ReagentUnit TotalVolume { get; private set; } /// /// Constructs an empty solution (ex. an empty beaker). /// public Solution() { } /// /// Constructs a solution containing 100% of a reagent (ex. A beaker of pure water). /// /// The prototype ID of the reagent to add. /// The quantity in milli-units. public Solution(string reagentId, ReagentUnit quantity) { AddReagent(reagentId, quantity); } /// public void ExposeData(ObjectSerializer serializer) { serializer.DataField(ref _contents, "reagents", new List()); if (serializer.Reading) { TotalVolume = ReagentUnit.New(0); foreach (var reagent in _contents) { TotalVolume += reagent.Quantity; } } } /// /// Adds a given quantity of a reagent directly into the solution. /// /// The prototype ID of the reagent to add. /// The quantity in milli-units. public void AddReagent(string reagentId, ReagentUnit quantity) { if (quantity <= 0) return; for (var i = 0; i < _contents.Count; i++) { var reagent = _contents[i]; if (reagent.ReagentId != reagentId) continue; _contents[i] = new ReagentQuantity(reagentId, reagent.Quantity + quantity); TotalVolume += quantity; return; } _contents.Add(new ReagentQuantity(reagentId, quantity)); TotalVolume += quantity; } /// /// Returns the amount of a single reagent inside the solution. /// /// The prototype ID of the reagent to add. /// The quantity in milli-units. public ReagentUnit GetReagentQuantity(string reagentId) { for (var i = 0; i < _contents.Count; i++) { if (_contents[i].ReagentId == reagentId) return _contents[i].Quantity; } return ReagentUnit.New(0); } public void RemoveReagent(string reagentId, ReagentUnit quantity) { if(quantity <= 0) return; for (var i = 0; i < _contents.Count; i++) { var reagent = _contents[i]; if(reagent.ReagentId != reagentId) continue; var curQuantity = reagent.Quantity; var newQuantity = curQuantity - quantity; if (newQuantity <= 0) { _contents.RemoveSwap(i); TotalVolume -= curQuantity; } else { _contents[i] = new ReagentQuantity(reagentId, newQuantity); TotalVolume -= quantity; } return; } } /// /// Remove the specified quantity from this solution. /// /// The quantity of this solution to remove public void RemoveSolution(ReagentUnit 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 = TotalVolume * ratio; } public void RemoveAllSolution() { _contents.Clear(); TotalVolume = ReagentUnit.New(0); } public Solution SplitSolution(ReagentUnit quantity) { if (quantity <= 0) return new Solution(); Solution newSolution; if (quantity >= TotalVolume) { newSolution = Clone(); RemoveAllSolution(); return newSolution; } newSolution = new Solution(); var newTotalVolume = ReagentUnit.New(0); var remainingVolume = TotalVolume; for (var i = 0; i < _contents.Count; i++) { var reagent = _contents[i]; var ratio = (remainingVolume - quantity).Double() / remainingVolume.Double(); remainingVolume -= reagent.Quantity; var newQuantity = reagent.Quantity * ratio; var splitQuantity = reagent.Quantity - newQuantity; _contents[i] = new ReagentQuantity(reagent.ReagentId, newQuantity); newSolution._contents.Add(new ReagentQuantity(reagent.ReagentId, splitQuantity)); newTotalVolume += splitQuantity; quantity -= splitQuantity; } newSolution.TotalVolume = newTotalVolume; TotalVolume -= newTotalVolume; return newSolution; } public void AddSolution(Solution otherSolution) { for (var i = 0; i < otherSolution._contents.Count; i++) { var otherReagent = otherSolution._contents[i]; var found = false; for (var j = 0; j < _contents.Count; j++) { var reagent = _contents[j]; if (reagent.ReagentId == otherReagent.ReagentId) { found = true; _contents[j] = new ReagentQuantity(reagent.ReagentId, reagent.Quantity + otherReagent.Quantity); break; } } if (!found) { _contents.Add(new ReagentQuantity(otherReagent.ReagentId, otherReagent.Quantity)); } } TotalVolume += otherSolution.TotalVolume; } public Solution Clone() { var volume = ReagentUnit.New(0); var newSolution = new Solution(); for (var i = 0; i < _contents.Count; i++) { var reagent = _contents[i]; newSolution._contents.Add(reagent); volume += reagent.Quantity; } newSolution.TotalVolume = volume; return newSolution; } [Serializable, NetSerializable] public readonly struct ReagentQuantity { public readonly string ReagentId; public readonly ReagentUnit Quantity; public ReagentQuantity(string reagentId, ReagentUnit quantity) { ReagentId = reagentId; Quantity = quantity; } [ExcludeFromCodeCoverage] public override string ToString() { return $"{ReagentId}:{Quantity}"; } } #region Enumeration public IEnumerator GetEnumerator() { return _contents.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion } }