Reagents & Solutions (#280)

* Added the ReagentPrototype class.

* Added the new Solution class.

* Added new shared SolutionComponent to the ECS system.

* Added some basic element and chemical reagent prototypes.

* Added a new Beaker item utilizing the SolutionComponent. This is a testing/debug entity, and should be removed or changed soon.

* Added filters for code coverage.

* Nightly work.

* Added the server SolutionComponent class.

* Added a bucket.
Verbs set up for solution interaction.

* Adds water tank entity to the game.

* Added a full water tank entity.
Solutions are properly serialized.
Solution can be poured between two containers.

* Solution class can now be enumerated.
SolutionComponent now calculates the color of the solution.

* Minor Cleanup.
This commit is contained in:
Acruid
2019-07-31 05:10:06 -07:00
committed by Pieter-Jan Briers
parent 41b72d5aa2
commit 2ea8bbf4eb
22 changed files with 1055 additions and 2 deletions

View File

@@ -20,6 +20,7 @@ using Content.Client.Interfaces.GameObjects;
using Content.Client.Interfaces.Parallax;
using Content.Client.Parallax;
using Content.Client.UserInterface;
using Content.Shared.GameObjects.Components.Chemistry;
using Content.Shared.GameObjects.Components.Markers;
using Content.Shared.GameObjects.Components.Materials;
using Content.Shared.GameObjects.Components.Mobs;
@@ -111,6 +112,7 @@ namespace Content.Client
factory.Register<ClothingComponent>();
factory.Register<ItemComponent>();
factory.Register<MaterialComponent>();
factory.Register<SolutionComponent>();
factory.Register<SoundComponent>();
factory.Register<MaterialStorageComponent>();
factory.RegisterReference<MaterialStorageComponent, SharedMaterialStorageComponent>();

View File

@@ -39,6 +39,7 @@ using Content.Server.GameObjects.Components.Weapon.Ranged;
using Content.Server.GameTicking;
using Content.Server.Interfaces;
using Content.Server.Interfaces.GameTicking;
using Content.Shared.GameObjects.Components.Chemistry;
using Content.Shared.GameObjects.Components.Materials;
using Content.Shared.GameObjects.Components.Inventory;
using Content.Shared.GameObjects.Components.Markers;
@@ -59,6 +60,7 @@ using Content.Server.GameObjects.Components.Explosive;
using Content.Server.GameObjects.Components.Items;
using Content.Server.GameObjects.Components.Triggers;
using Content.Shared.GameObjects.Components.Movement;
using SolutionComponent = Content.Server.GameObjects.Components.Chemistry.SolutionComponent;
namespace Content.Server
{
@@ -94,6 +96,8 @@ namespace Content.Server
factory.Register<ServerDoorComponent>();
factory.RegisterReference<ServerDoorComponent, IActivate>();
factory.Register<Content.Server.GameObjects.Components.Chemistry.SolutionComponent>();
//Power Components
factory.Register<PowerTransferComponent>();
factory.Register<PowerProviderComponent>();

View File

@@ -0,0 +1,140 @@
using System;
using Content.Shared.Chemistry;
using Content.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Server.GameObjects.Components.Chemistry
{
/// <summary>
/// Shared ECS component that manages a liquid solution of reagents.
/// </summary>
internal class SolutionComponent : Shared.GameObjects.Components.Chemistry.SolutionComponent
{
/// <summary>
/// Transfers solution from the held container to the target container.
/// </summary>
[Verb]
private sealed class FillTargetVerb : Verb<SolutionComponent>
{
protected override string GetText(IEntity user, SolutionComponent component)
{
if(!user.TryGetComponent<HandsComponent>(out var hands))
return "<I SHOULD BE INVISIBLE>";
if(hands.GetActiveHand == null)
return "<I SHOULD BE INVISIBLE>";
var heldEntityName = hands.GetActiveHand.Owner?.Prototype?.Name ?? "<Item>";
var myName = component.Owner.Prototype?.Name ?? "<Item>";
return $"Transfer liquid from [{heldEntityName}] to [{myName}].";
}
protected override VerbVisibility GetVisibility(IEntity user, SolutionComponent component)
{
if (user.TryGetComponent<HandsComponent>(out var hands))
{
if (hands.GetActiveHand != null)
{
if (hands.GetActiveHand.Owner.TryGetComponent<SolutionComponent>(out var solution))
{
if ((solution.Capabilities & SolutionCaps.PourOut) != 0 && (component.Capabilities & SolutionCaps.PourIn) != 0)
return VerbVisibility.Visible;
}
}
}
return VerbVisibility.Invisible;
}
protected override void Activate(IEntity user, SolutionComponent component)
{
if (!user.TryGetComponent<HandsComponent>(out var hands))
return;
if (hands.GetActiveHand == null)
return;
if (!hands.GetActiveHand.Owner.TryGetComponent<SolutionComponent>(out var handSolutionComp))
return;
if ((handSolutionComp.Capabilities & SolutionCaps.PourOut) == 0 || (component.Capabilities & SolutionCaps.PourIn) == 0)
return;
var transferQuantity = Math.Min(component.MaxVolume - component.CurrentVolume, handSolutionComp.CurrentVolume);
transferQuantity = Math.Min(transferQuantity, 10);
// nothing to transfer
if (transferQuantity <= 0)
return;
var transferSolution = handSolutionComp.SplitSolution(transferQuantity);
component.TryAddSolution(transferSolution);
}
}
/// <summary>
/// Transfers solution from a target container to the held container.
/// </summary>
[Verb]
private sealed class EmptyTargetVerb : Verb<SolutionComponent>
{
protected override string GetText(IEntity user, SolutionComponent component)
{
if (!user.TryGetComponent<HandsComponent>(out var hands))
return "<I SHOULD BE INVISIBLE>";
if (hands.GetActiveHand == null)
return "<I SHOULD BE INVISIBLE>";
var heldEntityName = hands.GetActiveHand.Owner?.Prototype?.Name ?? "<Item>";
var myName = component.Owner.Prototype?.Name ?? "<Item>";
return $"Transfer liquid from [{myName}] to [{heldEntityName}].";
}
protected override VerbVisibility GetVisibility(IEntity user, SolutionComponent component)
{
if (user.TryGetComponent<HandsComponent>(out var hands))
{
if (hands.GetActiveHand != null)
{
if (hands.GetActiveHand.Owner.TryGetComponent<SolutionComponent>(out var solution))
{
if ((solution.Capabilities & SolutionCaps.PourIn) != 0 && (component.Capabilities & SolutionCaps.PourOut) != 0)
return VerbVisibility.Visible;
}
}
}
return VerbVisibility.Invisible;
}
protected override void Activate(IEntity user, SolutionComponent component)
{
if (!user.TryGetComponent<HandsComponent>(out var hands))
return;
if (hands.GetActiveHand == null)
return;
if(!hands.GetActiveHand.Owner.TryGetComponent<SolutionComponent>(out var handSolutionComp))
return;
if ((handSolutionComp.Capabilities & SolutionCaps.PourIn) == 0 || (component.Capabilities & SolutionCaps.PourOut) == 0)
return;
var transferQuantity = Math.Min(handSolutionComp.MaxVolume - handSolutionComp.CurrentVolume, component.CurrentVolume);
transferQuantity = Math.Min(transferQuantity, 10);
// pulling from an empty container, pointless to continue
if (transferQuantity <= 0)
return;
var transferSolution = component.SplitSolution(transferQuantity);
handSolutionComp.TryAddSolution(transferSolution);
}
}
}
}

View File

@@ -0,0 +1,25 @@
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
namespace Content.Shared.Chemistry
{
[Prototype("reagent")]
public class ReagentPrototype : IPrototype, IIndexedPrototype
{
public string ID { get; private set; }
public string Name { get; private set; }
public string Description { get; private set; }
public Color SubstanceColor { get; private set; }
public void LoadFrom(YamlMappingNode mapping)
{
ID = mapping.GetNode("id").AsString();
Name = mapping.GetNode("name").ToString();
Description = mapping.GetNode("desc").ToString();
SubstanceColor = mapping.TryGetNode("color", out var colorNode) ? colorNode.AsHexColor(Color.White) : Color.White;
}
}
}

View File

@@ -0,0 +1,273 @@
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);
/// <summary>
/// The calculated total volume of all reagents in the solution (ex. Total volume of liquid in beaker).
/// </summary>
[ViewVariables]
public int 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>
public Solution(string reagentId, int quantity)
{
AddReagent(reagentId, quantity);
}
/// <inheritdoc />
public void ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref _contents, "reagents", new List<ReagentQuantity>());
if (serializer.Reading)
{
TotalVolume = 0;
foreach (var reagent in _contents)
{
TotalVolume += reagent.Quantity;
}
}
}
/// <summary>
/// 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>
public void AddReagent(string reagentId, int 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>
public int GetReagentQuantity(string reagentId)
{
for (var i = 0; i < _contents.Count; i++)
{
if (_contents[i].ReagentId == reagentId)
return _contents[i].Quantity;
}
return 0;
}
public void RemoveReagent(string reagentId, int 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;
}
}
public void RemoveSolution(int quantity)
{
if(quantity <=0)
return;
var ratio = (float)(TotalVolume - quantity) / TotalVolume;
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 = (int)Math.Floor(oldQuantity * ratio);
_contents[i] = new ReagentQuantity(reagent.ReagentId, newQuantity);
}
TotalVolume = (int)Math.Floor(TotalVolume * ratio);
}
public void RemoveAllSolution()
{
_contents.Clear();
TotalVolume = 0;
}
public Solution SplitSolution(int quantity)
{
if (quantity <= 0)
return new Solution();
Solution newSolution;
if (quantity >= TotalVolume)
{
newSolution = Clone();
RemoveAllSolution();
return newSolution;
}
newSolution = new Solution();
var newTotalVolume = 0;
var ratio = (float)(TotalVolume - quantity) / TotalVolume;
for (var i = 0; i < _contents.Count; i++)
{
var reagent = _contents[i];
var newQuantity = (int)Math.Floor(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;
}
TotalVolume = (int)Math.Floor(TotalVolume * ratio);
newSolution.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 = 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 int Quantity;
public ReagentQuantity(string reagentId, int quantity)
{
ReagentId = reagentId;
Quantity = quantity;
}
[ExcludeFromCodeCoverage]
public override string ToString()
{
return $"{ReagentId}:{Quantity}";
}
}
#region Enumeration
public IEnumerator<ReagentQuantity> GetEnumerator()
{
return _contents.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
}

View File

@@ -0,0 +1,21 @@
using System;
using Robust.Shared.Serialization;
namespace Content.Shared.Chemistry
{
/// <summary>
/// These are the defined capabilities of a container of a solution.
/// </summary>
[Flags]
[Serializable, NetSerializable]
public enum SolutionCaps
{
None = 0,
PourIn = 1,
PourOut = 2,
Injector = 4,
Injectable = 8,
}
}

View File

@@ -0,0 +1,166 @@
using System;
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]
private Solution _containedSolution;
private int _maxVolume;
private SolutionCaps _capabilities;
/// <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 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;
}
/// <inheritdoc />
public override string Name => "Solution";
/// <inheritdoc />
public sealed override uint? NetID => ContentNetIDs.SOLUTION;
/// <inheritdoc />
public sealed override Type StateType => typeof(SolutionComponentState);
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _maxVolume, "maxVol", 0);
serializer.DataField(ref _containedSolution, "contents", new Solution());
serializer.DataField(ref _capabilities, "caps", SolutionCaps.None);
}
public override void Startup()
{
base.Startup();
RecalculateColor();
}
public override void Shutdown()
{
base.Shutdown();
_containedSolution.RemoveAllSolution();
_containedSolution = new Solution();
}
public bool TryAddReagent(string reagentId, int quantity, out int acceptedQuantity)
{
throw new NotImplementedException();
}
public bool TryAddSolution(Solution solution)
{
if (solution.TotalVolume > (_maxVolume - _containedSolution.TotalVolume))
return false;
_containedSolution.AddSolution(solution);
RecalculateColor();
return true;
}
public Solution SplitSolution(int quantity)
{
return _containedSolution.SplitSolution(quantity);
}
private 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) { }
}
}
}

View File

@@ -7,6 +7,7 @@
public const uint DESTRUCTIBLE = 1001;
public const uint TEMPERATURE = 1002;
public const uint HANDS = 1003;
public const uint SOLUTION = 1004;
public const uint STORAGE = 1005;
public const uint INVENTORY = 1006;
public const uint POWER_DEBUG_TOOL = 1007;

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using JetBrains.Annotations;
using Robust.Shared.Interfaces.GameObjects;
@@ -116,7 +117,7 @@ namespace Content.Shared.GameObjects
foreach (var component in entity.GetComponentInstances())
{
var type = component.GetType();
foreach (var nestedType in type.GetNestedTypes())
foreach (var nestedType in type.GetNestedTypes(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static))
{
if (!typeof(Verb).IsAssignableFrom(nestedType) || nestedType.IsAbstract)
{

View File

@@ -0,0 +1,42 @@
using System.IO;
using Content.Shared.Chemistry;
using NUnit.Framework;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
namespace Content.Tests.Shared.Chemistry
{
[TestFixture, Parallelizable, TestOf(typeof(ReagentPrototype))]
public class ReagentPrototype_Tests
{
[Test]
public void DeserializeReagentPrototype()
{
using (TextReader stream = new StringReader(YamlReagentPrototype))
{
var yamlStream = new YamlStream();
yamlStream.Load(stream);
var document = yamlStream.Documents[0];
var rootNode = (YamlSequenceNode)document.RootNode;
var proto = (YamlMappingNode)rootNode[0];
var defType = proto.GetNode("type").AsString();
var newReagent = new ReagentPrototype();
newReagent.LoadFrom(proto);
Assert.That(defType, Is.EqualTo("reagent"));
Assert.That(newReagent.ID, Is.EqualTo("chem.H2"));
Assert.That(newReagent.Name, Is.EqualTo("Hydrogen"));
Assert.That(newReagent.Description, Is.EqualTo("A light, flammable gas."));
Assert.That(newReagent.SubstanceColor, Is.EqualTo(Color.Teal));
}
}
private const string YamlReagentPrototype = @"- type: reagent
id: chem.H2
name: Hydrogen
desc: A light, flammable gas.
color: " + "\"#008080\"";
}
}

View File

@@ -0,0 +1,251 @@
using Content.Shared.Chemistry;
using NUnit.Framework;
namespace Content.Tests.Shared.Chemistry
{
[TestFixture, Parallelizable, TestOf(typeof(Solution))]
public class Solution_Tests
{
[Test]
public void AddReagentAndGetSolution()
{
var solution = new Solution();
solution.AddReagent("water", 1000);
var quantity = solution.GetReagentQuantity("water");
Assert.That(quantity, Is.EqualTo(1000));
}
[Test]
public void ConstructorAddReagent()
{
var solution = new Solution("water", 1000);
var quantity = solution.GetReagentQuantity("water");
Assert.That(quantity, Is.EqualTo(1000));
}
[Test]
public void NonExistingReagentReturnsZero()
{
var solution = new Solution();
var quantity = solution.GetReagentQuantity("water");
Assert.That(quantity, Is.EqualTo(0));
}
[Test]
public void AddLessThanZeroReagentReturnsZero()
{
var solution = new Solution("water", -1000);
var quantity = solution.GetReagentQuantity("water");
Assert.That(quantity, Is.EqualTo(0));
}
[Test]
public void AddingReagentsSumsProperly()
{
var solution = new Solution();
solution.AddReagent("water", 1000);
solution.AddReagent("water", 2000);
var quantity = solution.GetReagentQuantity("water");
Assert.That(quantity, Is.EqualTo(3000));
}
[Test]
public void ReagentQuantitiesStayUnique()
{
var solution = new Solution();
solution.AddReagent("water", 1000);
solution.AddReagent("fire", 2000);
Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(1000));
Assert.That(solution.GetReagentQuantity("fire"), Is.EqualTo(2000));
}
[Test]
public void TotalVolumeIsCorrect()
{
var solution = new Solution();
solution.AddReagent("water", 1000);
solution.AddReagent("fire", 2000);
Assert.That(solution.TotalVolume, Is.EqualTo(3000));
}
[Test]
public void CloningSolutionIsCorrect()
{
var solution = new Solution();
solution.AddReagent("water", 1000);
solution.AddReagent("fire", 2000);
var newSolution = solution.Clone();
Assert.That(newSolution.GetReagentQuantity("water"), Is.EqualTo(1000));
Assert.That(newSolution.GetReagentQuantity("fire"), Is.EqualTo(2000));
Assert.That(newSolution.TotalVolume, Is.EqualTo(3000));
}
[Test]
public void RemoveSolutionRecalculatesProperly()
{
var solution = new Solution();
solution.AddReagent("water", 1000);
solution.AddReagent("fire", 2000);
solution.RemoveReagent("water", 500);
Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(500));
Assert.That(solution.GetReagentQuantity("fire"), Is.EqualTo(2000));
Assert.That(solution.TotalVolume, Is.EqualTo(2500));
}
[Test]
public void RemoveLessThanOneQuantityDoesNothing()
{
var solution = new Solution("water", 100);
solution.RemoveReagent("water", -100);
Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(100));
Assert.That(solution.TotalVolume, Is.EqualTo(100));
}
[Test]
public void RemoveMoreThanTotalRemovesAllReagent()
{
var solution = new Solution("water", 100);
solution.RemoveReagent("water", 1000);
Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(0));
Assert.That(solution.TotalVolume, Is.EqualTo(0));
}
[Test]
public void RemoveNonExistReagentDoesNothing()
{
var solution = new Solution("water", 100);
solution.RemoveReagent("fire", 1000);
Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(100));
Assert.That(solution.TotalVolume, Is.EqualTo(100));
}
[Test]
public void RemoveSolution()
{
var solution = new Solution("water", 700);
solution.RemoveSolution(500);
Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(200));
Assert.That(solution.TotalVolume, Is.EqualTo(200));
}
[Test]
public void RemoveSolutionMoreThanTotalRemovesAll()
{
var solution = new Solution("water", 800);
solution.RemoveSolution(1000);
Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(0));
Assert.That(solution.TotalVolume, Is.EqualTo(0));
}
[Test]
public void RemoveSolutionRatioPreserved()
{
var solution = new Solution();
solution.AddReagent("water", 1000);
solution.AddReagent("fire", 2000);
solution.RemoveSolution(1500);
Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(500));
Assert.That(solution.GetReagentQuantity("fire"), Is.EqualTo(1000));
Assert.That(solution.TotalVolume, Is.EqualTo(1500));
}
[Test]
public void RemoveSolutionLessThanOneDoesNothing()
{
var solution = new Solution("water", 800);
solution.RemoveSolution(-200);
Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(800));
Assert.That(solution.TotalVolume, Is.EqualTo(800));
}
[Test]
public void SplitSolution()
{
var solution = new Solution();
solution.AddReagent("water", 1000);
solution.AddReagent("fire", 2000);
var splitSolution = solution.SplitSolution(750);
Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(750));
Assert.That(solution.GetReagentQuantity("fire"), Is.EqualTo(1500));
Assert.That(solution.TotalVolume, Is.EqualTo(2250));
Assert.That(splitSolution.GetReagentQuantity("water"), Is.EqualTo(250));
Assert.That(splitSolution.GetReagentQuantity("fire"), Is.EqualTo(500));
Assert.That(splitSolution.TotalVolume, Is.EqualTo(750));
}
[Test]
public void SplitSolutionMoreThanTotalRemovesAll()
{
var solution = new Solution("water", 800);
var splitSolution = solution.SplitSolution(1000);
Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(0));
Assert.That(solution.TotalVolume, Is.EqualTo(0));
Assert.That(splitSolution.GetReagentQuantity("water"), Is.EqualTo(800));
Assert.That(splitSolution.TotalVolume, Is.EqualTo(800));
}
[Test]
public void SplitSolutionLessThanOneDoesNothing()
{
var solution = new Solution("water", 800);
var splitSolution = solution.SplitSolution(-200);
Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(800));
Assert.That(solution.TotalVolume, Is.EqualTo(800));
Assert.That(splitSolution.GetReagentQuantity("water"), Is.EqualTo(0));
Assert.That(splitSolution.TotalVolume, Is.EqualTo(0));
}
[Test]
public void AddSolution()
{
var solutionOne = new Solution();
solutionOne.AddReagent("water", 1000);
solutionOne.AddReagent("fire", 2000);
var solutionTwo = new Solution();
solutionTwo.AddReagent("water", 500);
solutionTwo.AddReagent("earth", 1000);
solutionOne.AddSolution(solutionTwo);
Assert.That(solutionOne.GetReagentQuantity("water"), Is.EqualTo(1500));
Assert.That(solutionOne.GetReagentQuantity("fire"), Is.EqualTo(2000));
Assert.That(solutionOne.GetReagentQuantity("earth"), Is.EqualTo(1000));
Assert.That(solutionOne.TotalVolume, Is.EqualTo(4500));
}
}
}

View File

@@ -0,0 +1,8 @@
- type: entity
parent: BaseItem
id: ReagentItem
name: "Reagent Item"
abstract: true
components:
- type: Solution
maxVol: 5

View File

@@ -0,0 +1,46 @@
- type: entity
parent: ReagentItem
name: "Extra-Grip™ Mop"
id: MopItem
description: A mop that cant be stopped, viscera cleanup detail awaits.
components:
- type: Sprite
texture: Objects/mop.png
- type: Icon
texture: Objects/mop.png
- type: Item
Size: 10
- type: Solution
maxVol: 10
caps: 1
- type: entity
parent: ReagentItem
name: Mop Bucket
id: MopBucket
description: Holds water and the tears of the janitor.
components:
- type: Sprite
texture: Objects/mopbucket.png
- type: Icon
texture: Objects/mopbucket.png
- type: Clickable
- type: BoundingBox
- type: Solution
maxVol: 500
caps: 3
- type: entity
parent: ReagentItem
name: Bucket
id: Bucket
description: "It's a bucket."
components:
- type: Sprite
texture: Objects/bucket.png
- type: Icon
texture: Objects/bucket.png
- type: Solution
maxVol: 500
caps: 3

View File

@@ -0,0 +1,46 @@
- type: entity
parent: ReagentItem
id: watertank
name: Water Tank
description: "A water tank. It is used to store high amounts of water."
components:
- type: Sprite
texture: Buildings/watertank.png
- type: Icon
texture: Buildings/watertank.png
- type: Clickable
- type: BoundingBox
aabb: "-0.5,-0.25,0.5,0.25"
- type: Collidable
mask: 3
layer: 1
IsScrapingFloor: true
- type: Physics
mass: 15
Anchored: false
- type: Damageable
- type: Destructible
thresholdvalue: 10
- type: Solution
maxVol: 1500
caps: 3
placement:
snap:
- Wall
- type: entity
parent: watertank
id: watertank_full
components:
- type: Solution
contents:
reagents:
- ReagentId: chem.H2O
Quantity: 1500

View File

@@ -0,0 +1,9 @@
- type: reagent
id: chem.H2SO4
name: Sulfuric Acid
desc: A highly corrosive, oily, colorless liquid.
- type: reagent
id: chem.H2O
name: Water
desc: A tasty colorless liquid.

View File

@@ -0,0 +1,16 @@
- type: reagent
id: chem.H2
name: Hydrogen
desc: A light, flammable gas.
- type: reagent
id: chem.O2
name: Oxygen
desc: An oxidizing, colorless gas.
- type: reagent
id: chem.S8
name: Sulfur
desc: A yellow, crystalline solid.
color: "#FFFACD"

Binary file not shown.

After

Width:  |  Height:  |  Size: 447 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 525 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 B

View File

@@ -13,6 +13,8 @@
<s:String x:Key="/Default/FilterSettingsManager/AttributeFilterXml/@EntryValue">&lt;data /&gt;</s:String>
<s:String x:Key="/Default/FilterSettingsManager/CoverageFilterXml/@EntryValue">&lt;data&gt;&lt;IncludeFilters /&gt;&lt;ExcludeFilters&gt;&lt;Filter ModuleMask="*.UnitTesting" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /&gt;&lt;/ExcludeFilters&gt;&lt;/data&gt;</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=BYOND/@EntryIndexedValue">True</s:Boolean>
<s:String x:Key="/Default/FilterSettingsManager/AttributeFilterXml/@EntryValue">&lt;data /&gt;</s:String>
<s:String x:Key="/Default/FilterSettingsManager/CoverageFilterXml/@EntryValue">&lt;data&gt;&lt;IncludeFilters /&gt;&lt;ExcludeFilters&gt;&lt;Filter ModuleMask="Lidgren.Network" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /&gt;&lt;/ExcludeFilters&gt;&lt;/data&gt;</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Lerp/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Noto/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=preemptively/@EntryIndexedValue">True</s:Boolean>