Files
OldThink/Content.Shared/Chemistry/Solution.cs

282 lines
8.9 KiB
C#
Raw Normal View History

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Content.Shared.Chemistry
{
/// <summary>
/// A solution of reagents.
/// </summary>
public class Solution : IExposeData, IEnumerable<Solution.ReagentQuantity>
{
// Most objects on the station hold only 1 or 2 reagents
[ViewVariables]
private List<ReagentQuantity> _contents = new List<ReagentQuantity>(2);
Reagent dispensers (#360) * Expose more private values of Solution and SolutionComponent Expose SolutionComponent.ContainedSolution and Solution.Contents. Both needed by ReagentDispenserComponent. * Implement IExamine for SolutionComponent Allows players to see the contents of a solution by examining the entity which contains it. * Implement ReagentDispenserComponent Adds ReagentDispenserComponent. A component which can add or remove reagents from a solution container. It's written in a general way so that it can be used for things such as the Chemical dispensers in chemistry, but also the booze and soda dispensers in the bar. The chemicals it may dispense are defined in yaml, similar to the way that vending machines define which entities they can dispense, by defining a reagent pack. * Add chemical dispenser and equipment Adds the chemical dispenser, beaker, large beaker, dropper, and a few more chemicals. * Add booze and soda dispensers. Adds the booze and soda dispensers, and a few chemicals for them to dispense. There's no drink mixing or drunkenness yet. * Update engine submodule. * Remove unneeded and commented out code Had a few WIP notes and debug code bits I forgot to remove beforehand. * Make SolutionComponent._containedSolution and it's values private again - Remove `SolutionComponent.ContainedSolution` property, replace with specific access functions to maintain safety. - Make Solution.Contents return a `ReadOnlyCollection` instead of `_contents` to prevent uncontrolled access to the Solution values. - Add `SolutionComponent.RemoveAllSolution()` * Update Content.Shared/Chemistry/Solution.cs Commits a suggestion from RemieRichards to match the coding style of the rest of the codebase. Using `IReadOnlyList` instead of `IReadOnlyCollection`. Co-Authored-By: Remie Richards <remierichards@gmail.com> * Update Content.Shared/GameObjects/Components/Chemistry/SolutionComponent.cs Commits a suggestion from RemieRichards to match the coding style of the rest of the codebase. Using `IReadOnlyList` instead of `IReadOnlyCollection`. Co-Authored-By: Remie Richards <remierichards@gmail.com> * Add import for IReadOnlyList to Shared/SolutionComponent.cs * Add documentation * Improve localization Improve use of ILocalizationManager. * Resolve ReagentDispenserWindow._localizationManager before using it Forgot to do this in the last commit, resulting in a crash. Oops. * Add SolutionCaps.FitsInDispenser. Use in ReagentDispenserComponent. Used to limit large containers like buckets or mop buckets from being placed in a dispenser. Both have large capacities (500) and weren't designed to hold certain chemicals like a beaker is, so for now they can be blocked from being put in a dispenser by giving them that flag. * Add colors to new reagents * Update engine submodule
2019-10-05 09:10:05 -04:00
public IReadOnlyList<ReagentQuantity> Contents => _contents;
/// <summary>
/// The calculated total volume of all reagents in the solution (ex. Total volume of liquid in beaker).
/// </summary>
[ViewVariables]
2020-04-05 11:36:12 +02:00
public ReagentUnit TotalVolume { get; private set; }
/// <summary>
/// Constructs an empty solution (ex. an empty beaker).
/// </summary>
public Solution() { }
/// <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>
2020-04-05 11:36:12 +02:00
public Solution(string reagentId, ReagentUnit quantity)
{
AddReagent(reagentId, quantity);
}
/// <inheritdoc />
public void ExposeData(ObjectSerializer serializer)
{
serializer.DataReadWriteFunction(
"reagents",
new List<ReagentQuantity>(),
quantities =>
{
_contents = quantities;
TotalVolume = ReagentUnit.New(0);
quantities.ForEach(reagent => TotalVolume += reagent.Quantity);
},
() => _contents);
}
/// <summary>
2019-07-31 15:02:36 +02:00
/// Adds a given quantity of a reagent directly into the solution.
/// </summary>
/// <param name="reagentId">The prototype ID of the reagent to add.</param>
/// <param name="quantity">The quantity in milli-units.</param>
2020-04-05 11:36:12 +02:00
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;
}
/// <summary>
/// Returns the amount of a single reagent inside the solution.
/// </summary>
/// <param name="reagentId">The prototype ID of the reagent to add.</param>
/// <returns>The quantity in milli-units.</returns>
2020-04-05 11:36:12 +02:00
public ReagentUnit GetReagentQuantity(string reagentId)
{
for (var i = 0; i < _contents.Count; i++)
{
if (_contents[i].ReagentId == reagentId)
return _contents[i].Quantity;
}
2020-04-05 11:36:12 +02:00
return ReagentUnit.New(0);
}
2020-04-05 11:36:12 +02:00
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;
2020-04-05 11:36:12 +02:00
var newQuantity = curQuantity - quantity;
if (newQuantity <= 0)
{
_contents.RemoveSwap(i);
TotalVolume -= curQuantity;
}
else
{
_contents[i] = new ReagentQuantity(reagentId, newQuantity);
TotalVolume -= quantity;
}
return;
}
}
Add solution pouring / click-transfer (#574) * Add click-based solution transfer For example, clicking on a beaker with a soda can to transfer the soda to the beaker. Works on plain solution containers like beakers and also on open drink containers like soda cans as long as they have the `PourIn` and `PourOut` solution capabilities. If no `SolutionComponent` is added to a drink entity, the `DrinkComponent` will give the entity one. This PR extends that behavior slightly by also giving these default `SolutionComponent`'s the proper capabilities for pouring in/out. * Improve fix for poured drinks not immediately disappearing Instead of making `DrinkComponent.Use` public this splits out the code important to both users and made that function public, leaving `Use` private. * Shorten solution transfer popup * Make code review changes - Move pouring code from SolutionComponent to new PourableComponent. Added PourableComponent to client ignore list and added to existing container prototypes. - Added EmptyVolume property to shared SolutionComponent for convenience. - Removed DrinkComponent fix from pouring AttackBy code. Instead DrinkComponent subscribes to the SolutionChanged action and updates its self when necessary. - Fixed pouring being able to add more than a containers max volume and sometimes deleting reagents. - Added message for when a container is full. * More code review changes - Remove IAttackBy ComponentReference attribute in PourableComponent - Remove _transferAmount from shared SolutionComponent. Left over var from previous commit not being used anymore.
2020-01-28 20:07:02 -05:00
/// <summary>
/// Remove the specified quantity from this solution.
/// </summary>
/// <param name="quantity">The quantity of this solution to remove</param>
2020-04-05 11:36:12 +02:00
public void RemoveSolution(ReagentUnit quantity)
{
Add solution pouring / click-transfer (#574) * Add click-based solution transfer For example, clicking on a beaker with a soda can to transfer the soda to the beaker. Works on plain solution containers like beakers and also on open drink containers like soda cans as long as they have the `PourIn` and `PourOut` solution capabilities. If no `SolutionComponent` is added to a drink entity, the `DrinkComponent` will give the entity one. This PR extends that behavior slightly by also giving these default `SolutionComponent`'s the proper capabilities for pouring in/out. * Improve fix for poured drinks not immediately disappearing Instead of making `DrinkComponent.Use` public this splits out the code important to both users and made that function public, leaving `Use` private. * Shorten solution transfer popup * Make code review changes - Move pouring code from SolutionComponent to new PourableComponent. Added PourableComponent to client ignore list and added to existing container prototypes. - Added EmptyVolume property to shared SolutionComponent for convenience. - Removed DrinkComponent fix from pouring AttackBy code. Instead DrinkComponent subscribes to the SolutionChanged action and updates its self when necessary. - Fixed pouring being able to add more than a containers max volume and sometimes deleting reagents. - Added message for when a container is full. * More code review changes - Remove IAttackBy ComponentReference attribute in PourableComponent - Remove _transferAmount from shared SolutionComponent. Left over var from previous commit not being used anymore.
2020-01-28 20:07:02 -05:00
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.
2020-04-05 11:36:12 +02:00
var newQuantity = oldQuantity * ratio;
_contents[i] = new ReagentQuantity(reagent.ReagentId, newQuantity);
}
2020-04-05 11:36:12 +02:00
TotalVolume = TotalVolume * ratio;
}
public void RemoveAllSolution()
{
_contents.Clear();
2020-04-05 11:36:12 +02:00
TotalVolume = ReagentUnit.New(0);
}
2020-04-05 11:36:12 +02:00
public Solution SplitSolution(ReagentUnit quantity)
{
if (quantity <= 0)
2020-04-05 11:36:12 +02:00
return new Solution();
Solution newSolution;
if (quantity >= TotalVolume)
{
newSolution = Clone();
RemoveAllSolution();
return newSolution;
}
2020-04-05 11:36:12 +02:00
newSolution = new Solution();
var newTotalVolume = ReagentUnit.New(0);
2020-04-14 15:41:23 +02:00
var remainingVolume = TotalVolume;
for (var i = 0; i < _contents.Count; i++)
{
var reagent = _contents[i];
var ratio = (remainingVolume - quantity).Double() / remainingVolume.Double();
2020-04-14 15:41:23 +02:00
remainingVolume -= reagent.Quantity;
2020-04-05 11:36:12 +02:00
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;
2020-04-14 15:41:23 +02:00
quantity -= splitQuantity;
}
newSolution.TotalVolume = newTotalVolume;
2020-04-14 15:41:23 +02:00
TotalVolume -= newTotalVolume;
return newSolution;
}
2019-07-31 15:02:36 +02:00
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()
{
2020-04-05 11:36:12 +02:00
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;
}
2019-07-31 15:02:36 +02:00
newSolution.TotalVolume = volume;
return newSolution;
}
[Serializable, NetSerializable]
public readonly struct ReagentQuantity
{
public readonly string ReagentId;
2020-04-05 11:36:12 +02:00
public readonly ReagentUnit Quantity;
2020-04-05 11:36:12 +02:00
public ReagentQuantity(string reagentId, ReagentUnit quantity)
{
ReagentId = reagentId;
Quantity = quantity;
}
[ExcludeFromCodeCoverage]
public override string ToString()
{
return $"{ReagentId}:{Quantity}";
}
}
#region Enumeration
2019-07-31 15:02:36 +02:00
public IEnumerator<ReagentQuantity> GetEnumerator()
{
return _contents.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
}