SolutionContainer refactors (#2954)

* removes unused method

* Code uncluttering (Also removed the netcode, color code, and visuals, need to rewrite)

* SolutionContainerVisualState

* Removes caching of SolutionContainer Color

* ChemicalsAdded() and ChemicalsRemoved() for updating appearance and handling reaction checks

* SolutionContainerComponentState

* Netcode

* ChemMasterComponent no longer creates a SolutionContainerComponent with new(), uses a Solution instead

* Enable nullable in SolutionContainer implementations

* Some review fixes

* uses IReadOnlyLists in ChemMaster

* Comments

* review fixes 3

* ReagentUnit documentation

* Review fixes

* spelling fix

* spelling 2

* typo

Co-authored-by: py01 <pyronetics01@gmail.com>
This commit is contained in:
py01
2021-01-10 02:41:55 -06:00
committed by GitHub
parent 0b7f286cf3
commit 7bf80fd4b8
14 changed files with 260 additions and 362 deletions

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Linq; using System.Linq;
using Content.Client.UserInterface.Stylesheets; using Content.Client.UserInterface.Stylesheets;
using Content.Shared.Chemistry; using Content.Shared.Chemistry;
@@ -378,7 +378,7 @@ namespace Content.Client.GameObjects.Components.Chemistry.ChemMaster
bufferHBox.AddChild(bufferLabel); bufferHBox.AddChild(bufferLabel);
var bufferVol = new Label var bufferVol = new Label
{ {
Text = $"{state.BufferCurrentVolume}/{state.BufferMaxVolume}", Text = $"{state.BufferCurrentVolume}",
StyleClasses = {StyleNano.StyleClassLabelSecondaryColor} StyleClasses = {StyleNano.StyleClassLabelSecondaryColor}
}; };
bufferHBox.AddChild(bufferVol); bufferHBox.AddChild(bufferVol);

View File

@@ -1,4 +1,4 @@
using Content.Shared.Chemistry; #nullable enable
using Content.Shared.GameObjects.Components.Chemistry; using Content.Shared.GameObjects.Components.Chemistry;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
@@ -8,22 +8,6 @@ namespace Content.Client.GameObjects.Components.Chemistry
[ComponentReference(typeof(SharedSolutionContainerComponent))] [ComponentReference(typeof(SharedSolutionContainerComponent))]
public class SolutionContainerComponent : SharedSolutionContainerComponent public class SolutionContainerComponent : SharedSolutionContainerComponent
{ {
public override bool CanAddSolution(Solution solution)
{
// TODO CLIENT
return false;
}
public override bool TryAddSolution(Solution solution, bool skipReactionCheck = false, bool skipColor = false)
{
// TODO CLIENT
return false;
}
public override bool TryRemoveReagent(string reagentId, ReagentUnit quantity)
{
// TODO CLIENT
return false;
}
} }
} }

View File

@@ -1,4 +1,4 @@
#nullable enable #nullable enable
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Content.Server.GameObjects.Components.Chemistry; using Content.Server.GameObjects.Components.Chemistry;
@@ -147,7 +147,7 @@ namespace Content.Server.GameObjects.Components.Body.Behavior
} }
// Add solution to _stomachContents // Add solution to _stomachContents
solutionComponent.TryAddSolution(solution, false, true); solutionComponent.TryAddSolution(solution);
// Add each reagent to _reagentDeltas. Used to track how long each reagent has been in the stomach // Add each reagent to _reagentDeltas. Used to track how long each reagent has been in the stomach
foreach (var reagent in solution.Contents) foreach (var reagent in solution.Contents)
{ {

View File

@@ -68,7 +68,7 @@ namespace Content.Server.GameObjects.Components.Body.Circulatory
return false; return false;
} }
_internalSolution.TryAddSolution(solution, false, true); _internalSolution.TryAddSolution(solution);
return true; return true;
} }

View File

@@ -49,7 +49,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
[ViewVariables] private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered; [ViewVariables] private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered;
[ViewVariables] private readonly SolutionContainerComponent BufferSolution = new(); [ViewVariables] private readonly Solution BufferSolution = new();
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(ChemMasterUiKey.Key); [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(ChemMasterUiKey.Key);
@@ -81,8 +81,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-reagentContainerContainer", Owner); ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-reagentContainerContainer", Owner);
//BufferSolution = Owner.BufferSolution //BufferSolution = Owner.BufferSolution
BufferSolution.Solution = new Solution(); BufferSolution.RemoveAllSolution();
BufferSolution.MaxVolume = ReagentUnit.New(1000);
UpdateUserInterface(); UpdateUserInterface();
} }
@@ -182,12 +181,12 @@ namespace Content.Server.GameObjects.Components.Chemistry
if (beaker == null) if (beaker == null)
{ {
return new ChemMasterBoundUserInterfaceState(Powered, false, ReagentUnit.New(0), ReagentUnit.New(0), return new ChemMasterBoundUserInterfaceState(Powered, false, ReagentUnit.New(0), ReagentUnit.New(0),
"", Owner.Name, new List<Solution.ReagentQuantity>(), BufferSolution.ReagentList.ToList(), _bufferModeTransfer, BufferSolution.CurrentVolume, BufferSolution.MaxVolume); "", Owner.Name, new List<Solution.ReagentQuantity>(), BufferSolution.Contents, _bufferModeTransfer, BufferSolution.TotalVolume);
} }
var solution = beaker.GetComponent<SolutionContainerComponent>(); var solution = beaker.GetComponent<SolutionContainerComponent>();
return new ChemMasterBoundUserInterfaceState(Powered, true, solution.CurrentVolume, solution.MaxVolume, return new ChemMasterBoundUserInterfaceState(Powered, true, solution.CurrentVolume, solution.MaxVolume,
beaker.Name, Owner.Name, solution.ReagentList.ToList(), BufferSolution.ReagentList.ToList(), _bufferModeTransfer, BufferSolution.CurrentVolume, BufferSolution.MaxVolume); beaker.Name, Owner.Name, solution.ReagentList, BufferSolution.Contents, _bufferModeTransfer, BufferSolution.TotalVolume);
} }
private void UpdateUserInterface() private void UpdateUserInterface()
@@ -222,12 +221,12 @@ namespace Content.Server.GameObjects.Components.Chemistry
var beakerSolution = beaker.GetComponent<SolutionContainerComponent>(); var beakerSolution = beaker.GetComponent<SolutionContainerComponent>();
if (isBuffer) if (isBuffer)
{ {
foreach (var reagent in BufferSolution.Solution.Contents) foreach (var reagent in BufferSolution.Contents)
{ {
if (reagent.ReagentId == id) if (reagent.ReagentId == id)
{ {
ReagentUnit actualAmount; ReagentUnit actualAmount;
if (amount == ReagentUnit.New(-1)) if (amount == ReagentUnit.New(-1)) //amount is ReagentUnit.New(-1) when the client sends a message requesting to remove all solution from the container
{ {
actualAmount = ReagentUnit.Min(reagent.Quantity, beakerSolution.EmptyVolume); actualAmount = ReagentUnit.Min(reagent.Quantity, beakerSolution.EmptyVolume);
} }
@@ -237,7 +236,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
} }
BufferSolution.Solution.RemoveReagent(id, actualAmount); BufferSolution.RemoveReagent(id, actualAmount);
if (_bufferModeTransfer) if (_bufferModeTransfer)
{ {
beakerSolution.TryAddReagent(id, actualAmount, out var _); beakerSolution.TryAddReagent(id, actualAmount, out var _);
@@ -257,14 +256,14 @@ namespace Content.Server.GameObjects.Components.Chemistry
ReagentUnit actualAmount; ReagentUnit actualAmount;
if (amount == ReagentUnit.New(-1)) if (amount == ReagentUnit.New(-1))
{ {
actualAmount = ReagentUnit.Min(reagent.Quantity, BufferSolution.EmptyVolume); actualAmount = reagent.Quantity;
} }
else else
{ {
actualAmount = ReagentUnit.Min(reagent.Quantity, amount, BufferSolution.EmptyVolume); actualAmount = ReagentUnit.Min(reagent.Quantity, amount);
} }
beakerSolution.TryRemoveReagent(id, actualAmount); beakerSolution.TryRemoveReagent(id, actualAmount);
BufferSolution.Solution.AddReagent(id, actualAmount); BufferSolution.AddReagent(id, actualAmount);
break; break;
} }
} }
@@ -275,12 +274,12 @@ namespace Content.Server.GameObjects.Components.Chemistry
private void TryCreatePackage(IEntity user, UiAction action, int pillAmount, int bottleAmount) private void TryCreatePackage(IEntity user, UiAction action, int pillAmount, int bottleAmount)
{ {
if (BufferSolution.CurrentVolume == 0) if (BufferSolution.TotalVolume == 0)
return; return;
if (action == UiAction.CreateBottles) if (action == UiAction.CreateBottles)
{ {
var individualVolume = BufferSolution.CurrentVolume / ReagentUnit.New(bottleAmount); var individualVolume = BufferSolution.TotalVolume / ReagentUnit.New(bottleAmount);
if (individualVolume < ReagentUnit.New(1)) if (individualVolume < ReagentUnit.New(1))
return; return;
@@ -289,7 +288,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
{ {
var bottle = Owner.EntityManager.SpawnEntity("bottle", Owner.Transform.Coordinates); var bottle = Owner.EntityManager.SpawnEntity("bottle", Owner.Transform.Coordinates);
var bufferSolution = BufferSolution.Solution.SplitSolution(actualVolume); var bufferSolution = BufferSolution.SplitSolution(actualVolume);
bottle.TryGetComponent<SolutionContainerComponent>(out var bottleSolution); bottle.TryGetComponent<SolutionContainerComponent>(out var bottleSolution);
bottleSolution?.TryAddSolution(bufferSolution); bottleSolution?.TryAddSolution(bufferSolution);
@@ -314,7 +313,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
} }
else //Pills else //Pills
{ {
var individualVolume = BufferSolution.CurrentVolume / ReagentUnit.New(pillAmount); var individualVolume = BufferSolution.TotalVolume / ReagentUnit.New(pillAmount);
if (individualVolume < ReagentUnit.New(1)) if (individualVolume < ReagentUnit.New(1))
return; return;
@@ -323,7 +322,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
{ {
var pill = Owner.EntityManager.SpawnEntity("pill", Owner.Transform.Coordinates); var pill = Owner.EntityManager.SpawnEntity("pill", Owner.Transform.Coordinates);
var bufferSolution = BufferSolution.Solution.SplitSolution(actualVolume); var bufferSolution = BufferSolution.SplitSolution(actualVolume);
pill.TryGetComponent<SolutionContainerComponent>(out var pillSolution); pill.TryGetComponent<SolutionContainerComponent>(out var pillSolution);
pillSolution?.TryAddSolution(bufferSolution); pillSolution?.TryAddSolution(bufferSolution);

View File

@@ -1,144 +1,19 @@
using System.Collections.Generic; #nullable enable
using System.Linq;
using Content.Server.Chemistry;
using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.GUI;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.Chemistry; using Content.Shared.Chemistry;
using Content.Shared.GameObjects.Components.Chemistry; using Content.Shared.GameObjects.Components.Chemistry;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.GameObjects.EntitySystems.ActionBlocker; using Content.Shared.GameObjects.EntitySystems.ActionBlocker;
using Content.Shared.GameObjects.Verbs; using Content.Shared.GameObjects.Verbs;
using Content.Shared.Utility;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Chemistry namespace Content.Server.GameObjects.Components.Chemistry
{ {
/// <summary>
/// ECS component that manages a liquid solution of reagents.
/// </summary>
[RegisterComponent] [RegisterComponent]
[ComponentReference(typeof(SharedSolutionContainerComponent))] [ComponentReference(typeof(SharedSolutionContainerComponent))]
public class SolutionContainerComponent : SharedSolutionContainerComponent, IExamine public class SolutionContainerComponent : SharedSolutionContainerComponent
{ {
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
private IEnumerable<ReactionPrototype> _reactions;
private ChemicalReactionSystem _reactionSystem;
private string _fillInitState;
private int _fillInitSteps;
private string _fillPathString = "Objects/Specific/Chemistry/fillings.rsi";
private ResourcePath _fillPath;
private SpriteSpecifier _fillSprite;
private AudioSystem _audioSystem;
private ChemistrySystem _chemistrySystem;
private SpriteComponent _spriteComponent;
/// <summary>
/// The volume without reagents remaining in the container.
/// </summary>
[ViewVariables]
public ReagentUnit EmptyVolume => MaxVolume - CurrentVolume;
public IReadOnlyList<Solution.ReagentQuantity> ReagentList => Solution.Contents;
public bool CanExamineContents => Capabilities.HasCap(SolutionContainerCaps.CanExamine);
public bool CanUseWithChemDispenser => Capabilities.HasCap(SolutionContainerCaps.FitsInDispenser);
public bool CanAddSolutions => Capabilities.HasCap(SolutionContainerCaps.AddTo);
public bool CanRemoveSolutions => Capabilities.HasCap(SolutionContainerCaps.RemoveFrom);
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(this, x => x.MaxVolume, "maxVol", ReagentUnit.New(0));
serializer.DataField(this, x => x.Solution, "contents", new Solution());
serializer.DataField(this, x => x.Capabilities, "caps", SolutionContainerCaps.AddTo | SolutionContainerCaps.RemoveFrom | SolutionContainerCaps.CanExamine);
serializer.DataField(ref _fillInitState, "fillingState", string.Empty);
serializer.DataField(ref _fillInitSteps, "fillingSteps", 7);
}
public override void Initialize()
{
base.Initialize();
_audioSystem = EntitySystem.Get<AudioSystem>();
_chemistrySystem = _entitySystemManager.GetEntitySystem<ChemistrySystem>();
_reactions = _prototypeManager.EnumeratePrototypes<ReactionPrototype>();
_reactionSystem = _entitySystemManager.GetEntitySystem<ChemicalReactionSystem>();
}
protected override void Startup()
{
base.Startup();
RecalculateColor();
if (!string.IsNullOrEmpty(_fillInitState))
{
_spriteComponent = Owner.GetComponent<SpriteComponent>();
_fillPath = new ResourcePath(_fillPathString);
_fillSprite = new SpriteSpecifier.Rsi(_fillPath, _fillInitState + (_fillInitSteps - 1));
_spriteComponent.AddLayerWithSprite(_fillSprite);
UpdateFillIcon();
}
}
public void RemoveAllSolution()
{
Solution.RemoveAllSolution();
OnSolutionChanged(false);
}
public override bool TryRemoveReagent(string reagentId, ReagentUnit quantity)
{
if (!Solution.ContainsReagent(reagentId, out var currentQuantity))
{
return false;
}
Solution.RemoveReagent(reagentId, quantity);
OnSolutionChanged(false);
return true;
}
/// <summary>
/// Attempt to remove the specified quantity from this solution
/// </summary>
/// <param name="quantity">Quantity of this solution to remove</param>
/// <returns>Whether or not the solution was successfully removed</returns>
public bool TryRemoveSolution(ReagentUnit quantity)
{
if (CurrentVolume == 0)
{
return false;
}
Solution.RemoveSolution(quantity);
OnSolutionChanged(false);
return true;
}
public Solution SplitSolution(ReagentUnit quantity)
{
var solutionSplit = Solution.SplitSolution(quantity);
OnSolutionChanged(false);
return solutionSplit;
}
protected void RecalculateColor()
{
SubstanceColor = Solution.Color;
}
/// <summary> /// <summary>
/// Transfers solution from the held container to the target container. /// Transfers solution from the held container to the target container.
/// </summary> /// </summary>
@@ -195,43 +70,6 @@ namespace Content.Server.GameObjects.Components.Chemistry
} }
} }
void IExamine.Examine(FormattedMessage message, bool inDetailsRange)
{
if (!CanExamineContents)
{
return;
}
if (ReagentList.Count == 0)
{
message.AddText(Loc.GetString("It's empty."));
}
else if (ReagentList.Count == 1)
{
var reagent = ReagentList[0];
if (_prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype proto))
{
message.AddMarkup(
Loc.GetString("It contains a [color={0}]{1}[/color] substance.",
proto.GetSubstanceTextColor().ToHexNoAlpha(),
Loc.GetString(proto.PhysicalDescription)));
}
}
else
{
var reagent = ReagentList.Max();
if (_prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype proto))
{
message.AddMarkup(
Loc.GetString("It contains a [color={0}]{1}[/color] mixture of substances.",
SubstanceColor.ToHexNoAlpha(),
Loc.GetString(proto.PhysicalDescription)));
}
}
}
/// <summary> /// <summary>
/// Transfers solution from a target container to the held container. /// Transfers solution from a target container to the held container.
/// </summary> /// </summary>
@@ -288,86 +126,5 @@ namespace Content.Server.GameObjects.Components.Chemistry
handSolutionComp.TryAddSolution(transferSolution); handSolutionComp.TryAddSolution(transferSolution);
} }
} }
private void CheckForReaction()
{
_reactionSystem.FullyReactSolution(Solution, Owner, MaxVolume);
}
public bool TryAddReagent(string reagentId, ReagentUnit quantity, out ReagentUnit acceptedQuantity, bool skipReactionCheck = false, bool skipColor = false)
{
var toAcceptQuantity = MaxVolume - Solution.TotalVolume;
if (quantity > toAcceptQuantity)
{
acceptedQuantity = toAcceptQuantity;
if (acceptedQuantity == 0) return false;
}
else
{
acceptedQuantity = quantity;
}
Solution.AddReagent(reagentId, acceptedQuantity);
if (!skipColor) {
RecalculateColor();
}
if(!skipReactionCheck)
CheckForReaction();
OnSolutionChanged(skipColor);
return true;
}
public override bool CanAddSolution(Solution solution)
{
return solution.TotalVolume <= (MaxVolume - Solution.TotalVolume);
}
public override bool TryAddSolution(Solution solution, bool skipReactionCheck = false, bool skipColor = false)
{
if (!CanAddSolution(solution))
return false;
Solution.AddSolution(solution);
if (!skipColor) {
RecalculateColor();
}
if(!skipReactionCheck)
CheckForReaction();
OnSolutionChanged(skipColor);
return true;
}
protected void UpdateFillIcon()
{
if (string.IsNullOrEmpty(_fillInitState))
{
return;
}
var percentage = (CurrentVolume / MaxVolume).Double();
var level = ContentHelpers.RoundToLevels(percentage * 100, 100, _fillInitSteps);
//Transformed glass uses special fancy sprites so we don't bother
if (level == 0 || (Owner.TryGetComponent<TransformableContainerComponent>(out var transformComp) && transformComp.Transformed))
{
_spriteComponent.LayerSetColor(1, Color.Transparent);
return;
}
_fillSprite = new SpriteSpecifier.Rsi(_fillPath, _fillInitState + level);
_spriteComponent.LayerSetSprite(1, _fillSprite);
_spriteComponent.LayerSetColor(1, SubstanceColor);
}
protected virtual void OnSolutionChanged(bool skipColor)
{
if (!skipColor)
{
RecalculateColor();
}
UpdateFillIcon();
_chemistrySystem.HandleSolutionChange(Owner);
}
} }
} }

View File

@@ -286,7 +286,7 @@ namespace Content.Server.GameObjects.Components.Fluids
Color newColor; Color newColor;
if (_recolor) if (_recolor)
{ {
newColor = _contents.SubstanceColor.WithAlpha(cappedScale); newColor = _contents.Color.WithAlpha(cappedScale);
} }
else else
{ {

View File

@@ -161,7 +161,7 @@ namespace Content.Server.GameObjects.Components.Fluids
if (vapor.TryGetComponent(out AppearanceComponent appearance)) // Vapor sprite should face down. if (vapor.TryGetComponent(out AppearanceComponent appearance)) // Vapor sprite should face down.
{ {
appearance.SetData(VaporVisuals.Rotation, -Angle.South + rotation); appearance.SetData(VaporVisuals.Rotation, -Angle.South + rotation);
appearance.SetData(VaporVisuals.Color, contents.SubstanceColor.WithAlpha(1f)); appearance.SetData(VaporVisuals.Color, contents.Color.WithAlpha(1f));
appearance.SetData(VaporVisuals.State, true); appearance.SetData(VaporVisuals.State, true);
} }

View File

@@ -1,4 +1,4 @@
#nullable enable #nullable enable
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Server.Atmos; using Content.Server.Atmos;

View File

@@ -1,10 +1,14 @@
using System; using System;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using Robust.Shared.Interfaces.Serialization; using Robust.Shared.Interfaces.Serialization;
namespace Content.Shared.Chemistry namespace Content.Shared.Chemistry
{ {
/// <summary>
/// Represents a quantity of reagent, to a precision of 0.01.
/// To enforce this level of precision, floats are shifted by 2 decimal points, rounded, and converted to an int.
/// </summary>
[Serializable] [Serializable]
public struct ReagentUnit : ISelfSerialize, IComparable<ReagentUnit>, IEquatable<ReagentUnit> public struct ReagentUnit : ISelfSerialize, IComparable<ReagentUnit>, IEquatable<ReagentUnit>
{ {

View File

@@ -63,6 +63,11 @@ namespace Content.Shared.Chemistry
() => _contents); () => _contents);
} }
public bool ContainsReagent(string reagentId)
{
return ContainsReagent(reagentId, out _);
}
public bool ContainsReagent(string reagentId, out ReagentUnit quantity) public bool ContainsReagent(string reagentId, out ReagentUnit quantity)
{ {
foreach (var reagent in Contents) foreach (var reagent in Contents)

View File

@@ -1,4 +1,4 @@
#nullable enable #nullable enable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Content.Shared.Chemistry; using Content.Shared.Chemistry;
@@ -30,20 +30,19 @@ namespace Content.Shared.GameObjects.Components.Chemistry.ChemMaster
/// <summary> /// <summary>
/// A list of the reagents and their amounts within the beaker/reagent container, if applicable. /// A list of the reagents and their amounts within the beaker/reagent container, if applicable.
/// </summary> /// </summary>
public readonly List<Solution.ReagentQuantity> ContainerReagents; public readonly IReadOnlyList<Solution.ReagentQuantity> ContainerReagents;
/// <summary> /// <summary>
/// A list of the reagents and their amounts within the buffer, if applicable. /// A list of the reagents and their amounts within the buffer, if applicable.
/// </summary> /// </summary>
public readonly List<Solution.ReagentQuantity> BufferReagents; public readonly IReadOnlyList<Solution.ReagentQuantity> BufferReagents;
public readonly string DispenserName; public readonly string DispenserName;
public readonly bool BufferModeTransfer; public readonly bool BufferModeTransfer;
public readonly ReagentUnit BufferCurrentVolume; public readonly ReagentUnit BufferCurrentVolume;
public readonly ReagentUnit BufferMaxVolume;
public ChemMasterBoundUserInterfaceState(bool hasPower, bool hasBeaker, ReagentUnit beakerCurrentVolume, ReagentUnit beakerMaxVolume, string containerName, public ChemMasterBoundUserInterfaceState(bool hasPower, bool hasBeaker, ReagentUnit beakerCurrentVolume, ReagentUnit beakerMaxVolume, string containerName,
string dispenserName, List<Solution.ReagentQuantity> containerReagents, List<Solution.ReagentQuantity> bufferReagents, bool bufferModeTransfer, ReagentUnit bufferCurrentVolume, ReagentUnit bufferMaxVolume) string dispenserName, IReadOnlyList<Solution.ReagentQuantity> containerReagents, IReadOnlyList<Solution.ReagentQuantity> bufferReagents, bool bufferModeTransfer, ReagentUnit bufferCurrentVolume)
{ {
HasPower = hasPower; HasPower = hasPower;
HasBeaker = hasBeaker; HasBeaker = hasBeaker;
@@ -55,7 +54,6 @@ namespace Content.Shared.GameObjects.Components.Chemistry.ChemMaster
BufferReagents = bufferReagents; BufferReagents = bufferReagents;
BufferModeTransfer = bufferModeTransfer; BufferModeTransfer = bufferModeTransfer;
BufferCurrentVolume = bufferCurrentVolume; BufferCurrentVolume = bufferCurrentVolume;
BufferMaxVolume = bufferMaxVolume;
} }
} }

View File

@@ -1,100 +1,230 @@
#nullable enable #nullable enable
using System;
using Content.Shared.Chemistry; using Content.Shared.Chemistry;
using Content.Shared.GameObjects.EntitySystems;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components.Appearance;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Maths; using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
using System;
using System.Collections.Generic;
namespace Content.Shared.GameObjects.Components.Chemistry namespace Content.Shared.GameObjects.Components.Chemistry
{ {
public abstract class SharedSolutionContainerComponent : Component /// <summary>
/// Holds a <see cref="Solution"/> with a limited volume.
/// </summary>
public abstract class SharedSolutionContainerComponent : Component, IExamine
{ {
public override string Name => "SolutionContainer"; public override string Name => "SolutionContainer";
/// <inheritdoc /> /// <inheritdoc />
public sealed override uint? NetID => ContentNetIDs.SOLUTION; public sealed override uint? NetID => ContentNetIDs.SOLUTION;
private Solution _solution = new();
private ReagentUnit _maxVolume;
private Color _substanceColor;
/// <summary>
/// The contained solution.
/// </summary>
[ViewVariables] [ViewVariables]
public Solution Solution public Solution Solution { get; private set; } = new();
{
get => _solution;
set
{
if (_solution == value)
{
return;
}
_solution = value; public IReadOnlyList<Solution.ReagentQuantity> ReagentList => Solution.Contents;
Dirty();
} [ViewVariables(VVAccess.ReadWrite)]
} public ReagentUnit MaxVolume { get; set; }
/// <summary>
/// The total volume of all the of the reagents in the container.
/// </summary>
[ViewVariables] [ViewVariables]
public ReagentUnit CurrentVolume => Solution.TotalVolume; public ReagentUnit CurrentVolume => Solution.TotalVolume;
/// <summary> /// <summary>
/// The maximum volume of the container. /// Volume needed to fill this container.
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables]
public ReagentUnit MaxVolume public ReagentUnit EmptyVolume => MaxVolume - CurrentVolume;
{
get => _maxVolume;
set
{
if (_maxVolume == value)
{
return;
}
_maxVolume = value; [ViewVariables]
Dirty(); public virtual Color Color => Solution.Color;
}
}
/// <summary> /// <summary>
/// The current blended color of all the reagents in the container. /// If reactions will be checked for when adding reagents to the container.
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
public virtual Color SubstanceColor public bool CanReact { get; set; }
{
get => _substanceColor;
set
{
if (_substanceColor == value)
{
return;
}
_substanceColor = value;
Dirty();
}
}
/// <summary>
/// The current capabilities of this container (is the top open to pour? can I inject it into another object?).
/// </summary>
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
public SolutionContainerCaps Capabilities { get; set; } public SolutionContainerCaps Capabilities { get; set; }
public abstract bool CanAddSolution(Solution solution); public bool CanExamineContents => Capabilities.HasCap(SolutionContainerCaps.CanExamine);
public abstract bool TryAddSolution(Solution solution, bool skipReactionCheck = false, bool skipColor = false); public bool CanUseWithChemDispenser => Capabilities.HasCap(SolutionContainerCaps.FitsInDispenser);
public abstract bool TryRemoveReagent(string reagentId, ReagentUnit quantity); public bool CanAddSolutions => Capabilities.HasCap(SolutionContainerCaps.AddTo);
public bool CanRemoveSolutions => Capabilities.HasCap(SolutionContainerCaps.RemoveFrom);
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(this, x => x.CanReact, "canReact", true);
serializer.DataField(this, x => x.MaxVolume, "maxVol", ReagentUnit.New(0));
serializer.DataField(this, x => x.Solution, "contents", new Solution());
serializer.DataField(this, x => x.Capabilities, "caps", SolutionContainerCaps.AddTo | SolutionContainerCaps.RemoveFrom | SolutionContainerCaps.CanExamine);
}
public void RemoveAllSolution()
{
if (CurrentVolume == 0)
return;
Solution.RemoveAllSolution();
ChemicalsRemoved();
}
/// <summary>
/// Adds reagent of an Id to the container.
/// </summary>
/// <param name="reagentId">The Id of the reagent to add.</param>
/// <param name="quantity">The amount of reagent to add.</param>
/// <param name="acceptedQuantity">The amount of reagent sucesfully added.</param>
/// <returns>If all the reagent could be added.</returns>
public bool TryAddReagent(string reagentId, ReagentUnit quantity, out ReagentUnit acceptedQuantity)
{
acceptedQuantity = EmptyVolume > quantity ? quantity : EmptyVolume;
Solution.AddReagent(reagentId, acceptedQuantity);
if (acceptedQuantity > 0)
ChemicalsAdded();
return acceptedQuantity == quantity;
}
/// <summary>
/// Removes reagent of an Id to the container.
/// </summary>
/// <param name="reagentId">The Id of the reagent to remove.</param>
/// <param name="quantity">The amount of reagent to remove.</param>
/// <returns>If the reagent to remove was found in the container.</returns>
public bool TryRemoveReagent(string reagentId, ReagentUnit quantity)
{
if (!Solution.ContainsReagent(reagentId))
return false;
Solution.RemoveReagent(reagentId, quantity);
ChemicalsRemoved();
return true;
}
/// <summary>
/// Removes part of the solution in the container.
/// </summary>
/// <param name="quantity">the volume of solution to remove.</param>
/// <returns>The solution that was removed.</returns>
public Solution SplitSolution(ReagentUnit quantity)
{
var splitSol = Solution.SplitSolution(quantity);
ChemicalsRemoved();
return splitSol;
}
/// <summary>
/// Checks if a solution can fit into the container.
/// </summary>
/// <param name="solution">The solution that is trying to be added.</param>
/// <returns>If the solution can be fully added.</returns>
public bool CanAddSolution(Solution solution)
{
return solution.TotalVolume <= EmptyVolume;
}
/// <summary>
/// Adds a solution to the container, if it can fully fit.
/// </summary>
/// <param name="solution">The solution to try to add.</param>
/// <returns>If the solution could be added.</returns>
public bool TryAddSolution(Solution solution)
{
if (!CanAddSolution(solution))
return false;
Solution.AddSolution(solution);
ChemicalsAdded();
return true;
}
private void ChemicalsAdded()
{
ProcessReactions();
SolutionChanged();
UpdateAppearance();
Dirty();
}
private void ChemicalsRemoved()
{
SolutionChanged();
UpdateAppearance();
Dirty();
}
private void SolutionChanged()
{
EntitySystem.Get<ChemistrySystem>()
.HandleSolutionChange(Owner);
}
private void ProcessReactions()
{
if (!CanReact)
return;
EntitySystem.Get<ChemicalReactionSystem>()
.FullyReactSolution(Solution, Owner, MaxVolume);
}
void IExamine.Examine(FormattedMessage message, bool inDetailsRange)
{
if (!CanExamineContents)
return;
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
if (ReagentList.Count == 0)
{
message.AddText(Loc.GetString("Contains no chemicals."));
return;
}
var primaryReagent = Solution.GetPrimaryReagentId();
if (!prototypeManager.TryIndex(primaryReagent, out ReagentPrototype proto))
{
Logger.Error($"{nameof(SharedSolutionContainerComponent)} could not find the prototype associated with {primaryReagent}.");
return;
}
var colorHex = Color.ToHexNoAlpha(); //TODO: If the chem has a dark color, the examine text becomes black on a black background, which is unreadable.
var messageString = "It contains a [color={0}]{1}[/color] " + (ReagentList.Count == 1 ? "chemical." : "mixture of chemicals.");
message.AddMarkup(Loc.GetString(messageString, colorHex, Loc.GetString(proto.PhysicalDescription)));
}
private void UpdateAppearance()
{
if (!Owner.TryGetComponent<SharedAppearanceComponent>(out var appearance))
return;
appearance.SetData(SolutionContainerVisuals.VisualState, GetVisualState());
}
private SolutionContainerVisualState GetVisualState()
{
var filledVolumeFraction = CurrentVolume.Float() / MaxVolume.Float();
return new SolutionContainerVisualState(Color, filledVolumeFraction);
}
/// <inheritdoc />
public override ComponentState GetComponentState() public override ComponentState GetComponentState()
{ {
return new SolutionContainerComponentState(Solution); return new SolutionContainerComponentState(Solution);
@@ -102,14 +232,34 @@ namespace Content.Shared.GameObjects.Components.Chemistry
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
{ {
base.HandleComponentState(curState, nextState); if (curState is not SolutionContainerComponentState containerState)
if (curState is not SolutionContainerComponentState state)
{
return; return;
}
_solution = state.Solution; Solution = containerState.Solution;
}
}
[Serializable, NetSerializable]
public enum SolutionContainerVisuals : byte
{
VisualState
}
[Serializable, NetSerializable]
public class SolutionContainerVisualState
{
public readonly Color Color;
/// <summary>
/// Represents how full the container is, as a fraction equivalent to <see cref="FilledVolumeFraction"/>/<see cref="byte.MaxValue"/>.
/// </summary>
public readonly byte FilledVolumeFraction;
/// <param name="filledVolumeFraction">The fraction of the container's volume that is filled.</param>
public SolutionContainerVisualState(Color color, float filledVolumeFraction)
{
Color = color;
FilledVolumeFraction = (byte) (byte.MaxValue * filledVolumeFraction);
} }
} }

View File

@@ -8,6 +8,7 @@ using System.Collections.Generic;
namespace Content.Shared.GameObjects.EntitySystems namespace Content.Shared.GameObjects.EntitySystems
{ {
//TODO: Reimplement sounds for reactions
public class ChemicalReactionSystem : EntitySystem public class ChemicalReactionSystem : EntitySystem
{ {
private IEnumerable<ReactionPrototype> _reactions; private IEnumerable<ReactionPrototype> _reactions;