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:
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Content.Client.UserInterface.Stylesheets;
|
||||
using Content.Shared.Chemistry;
|
||||
@@ -378,7 +378,7 @@ namespace Content.Client.GameObjects.Components.Chemistry.ChemMaster
|
||||
bufferHBox.AddChild(bufferLabel);
|
||||
var bufferVol = new Label
|
||||
{
|
||||
Text = $"{state.BufferCurrentVolume}/{state.BufferMaxVolume}",
|
||||
Text = $"{state.BufferCurrentVolume}",
|
||||
StyleClasses = {StyleNano.StyleClassLabelSecondaryColor}
|
||||
};
|
||||
bufferHBox.AddChild(bufferVol);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Shared.Chemistry;
|
||||
#nullable enable
|
||||
using Content.Shared.GameObjects.Components.Chemistry;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
@@ -8,22 +8,6 @@ namespace Content.Client.GameObjects.Components.Chemistry
|
||||
[ComponentReference(typeof(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#nullable enable
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Server.GameObjects.Components.Chemistry;
|
||||
@@ -147,7 +147,7 @@ namespace Content.Server.GameObjects.Components.Body.Behavior
|
||||
}
|
||||
|
||||
// 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
|
||||
foreach (var reagent in solution.Contents)
|
||||
{
|
||||
|
||||
@@ -68,7 +68,7 @@ namespace Content.Server.GameObjects.Components.Body.Circulatory
|
||||
return false;
|
||||
}
|
||||
|
||||
_internalSolution.TryAddSolution(solution, false, true);
|
||||
_internalSolution.TryAddSolution(solution);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
|
||||
[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);
|
||||
|
||||
@@ -81,8 +81,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-reagentContainerContainer", Owner);
|
||||
|
||||
//BufferSolution = Owner.BufferSolution
|
||||
BufferSolution.Solution = new Solution();
|
||||
BufferSolution.MaxVolume = ReagentUnit.New(1000);
|
||||
BufferSolution.RemoveAllSolution();
|
||||
|
||||
UpdateUserInterface();
|
||||
}
|
||||
@@ -182,12 +181,12 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
if (beaker == null)
|
||||
{
|
||||
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>();
|
||||
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()
|
||||
@@ -222,12 +221,12 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
var beakerSolution = beaker.GetComponent<SolutionContainerComponent>();
|
||||
if (isBuffer)
|
||||
{
|
||||
foreach (var reagent in BufferSolution.Solution.Contents)
|
||||
foreach (var reagent in BufferSolution.Contents)
|
||||
{
|
||||
if (reagent.ReagentId == id)
|
||||
{
|
||||
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);
|
||||
}
|
||||
@@ -237,7 +236,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
}
|
||||
|
||||
|
||||
BufferSolution.Solution.RemoveReagent(id, actualAmount);
|
||||
BufferSolution.RemoveReagent(id, actualAmount);
|
||||
if (_bufferModeTransfer)
|
||||
{
|
||||
beakerSolution.TryAddReagent(id, actualAmount, out var _);
|
||||
@@ -257,14 +256,14 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
ReagentUnit actualAmount;
|
||||
if (amount == ReagentUnit.New(-1))
|
||||
{
|
||||
actualAmount = ReagentUnit.Min(reagent.Quantity, BufferSolution.EmptyVolume);
|
||||
actualAmount = reagent.Quantity;
|
||||
}
|
||||
else
|
||||
{
|
||||
actualAmount = ReagentUnit.Min(reagent.Quantity, amount, BufferSolution.EmptyVolume);
|
||||
actualAmount = ReagentUnit.Min(reagent.Quantity, amount);
|
||||
}
|
||||
beakerSolution.TryRemoveReagent(id, actualAmount);
|
||||
BufferSolution.Solution.AddReagent(id, actualAmount);
|
||||
BufferSolution.AddReagent(id, actualAmount);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -275,12 +274,12 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
|
||||
private void TryCreatePackage(IEntity user, UiAction action, int pillAmount, int bottleAmount)
|
||||
{
|
||||
if (BufferSolution.CurrentVolume == 0)
|
||||
if (BufferSolution.TotalVolume == 0)
|
||||
return;
|
||||
|
||||
if (action == UiAction.CreateBottles)
|
||||
{
|
||||
var individualVolume = BufferSolution.CurrentVolume / ReagentUnit.New(bottleAmount);
|
||||
var individualVolume = BufferSolution.TotalVolume / ReagentUnit.New(bottleAmount);
|
||||
if (individualVolume < ReagentUnit.New(1))
|
||||
return;
|
||||
|
||||
@@ -289,7 +288,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
{
|
||||
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);
|
||||
bottleSolution?.TryAddSolution(bufferSolution);
|
||||
@@ -314,7 +313,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
}
|
||||
else //Pills
|
||||
{
|
||||
var individualVolume = BufferSolution.CurrentVolume / ReagentUnit.New(pillAmount);
|
||||
var individualVolume = BufferSolution.TotalVolume / ReagentUnit.New(pillAmount);
|
||||
if (individualVolume < ReagentUnit.New(1))
|
||||
return;
|
||||
|
||||
@@ -323,7 +322,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
{
|
||||
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);
|
||||
pillSolution?.TryAddSolution(bufferSolution);
|
||||
|
||||
@@ -1,144 +1,19 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Server.Chemistry;
|
||||
#nullable enable
|
||||
using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.GameObjects.Components.Chemistry;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.EntitySystems.ActionBlocker;
|
||||
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.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// ECS component that manages a liquid solution of reagents.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[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>
|
||||
/// Transfers solution from the held container to the target container.
|
||||
/// </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>
|
||||
/// Transfers solution from a target container to the held container.
|
||||
/// </summary>
|
||||
@@ -288,86 +126,5 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,7 +286,7 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
Color newColor;
|
||||
if (_recolor)
|
||||
{
|
||||
newColor = _contents.SubstanceColor.WithAlpha(cappedScale);
|
||||
newColor = _contents.Color.WithAlpha(cappedScale);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -161,7 +161,7 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
if (vapor.TryGetComponent(out AppearanceComponent appearance)) // Vapor sprite should face down.
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#nullable enable
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Atmos;
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
|
||||
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]
|
||||
public struct ReagentUnit : ISelfSerialize, IComparable<ReagentUnit>, IEquatable<ReagentUnit>
|
||||
{
|
||||
|
||||
@@ -63,6 +63,11 @@ namespace Content.Shared.Chemistry
|
||||
() => _contents);
|
||||
}
|
||||
|
||||
public bool ContainsReagent(string reagentId)
|
||||
{
|
||||
return ContainsReagent(reagentId, out _);
|
||||
}
|
||||
|
||||
public bool ContainsReagent(string reagentId, out ReagentUnit quantity)
|
||||
{
|
||||
foreach (var reagent in Contents)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#nullable enable
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.Chemistry;
|
||||
@@ -30,20 +30,19 @@ namespace Content.Shared.GameObjects.Components.Chemistry.ChemMaster
|
||||
/// <summary>
|
||||
/// A list of the reagents and their amounts within the beaker/reagent container, if applicable.
|
||||
/// </summary>
|
||||
public readonly List<Solution.ReagentQuantity> ContainerReagents;
|
||||
public readonly IReadOnlyList<Solution.ReagentQuantity> ContainerReagents;
|
||||
/// <summary>
|
||||
/// A list of the reagents and their amounts within the buffer, if applicable.
|
||||
/// </summary>
|
||||
public readonly List<Solution.ReagentQuantity> BufferReagents;
|
||||
public readonly IReadOnlyList<Solution.ReagentQuantity> BufferReagents;
|
||||
public readonly string DispenserName;
|
||||
|
||||
public readonly bool BufferModeTransfer;
|
||||
|
||||
public readonly ReagentUnit BufferCurrentVolume;
|
||||
public readonly ReagentUnit BufferMaxVolume;
|
||||
|
||||
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;
|
||||
HasBeaker = hasBeaker;
|
||||
@@ -55,7 +54,6 @@ namespace Content.Shared.GameObjects.Components.Chemistry.ChemMaster
|
||||
BufferReagents = bufferReagents;
|
||||
BufferModeTransfer = bufferModeTransfer;
|
||||
BufferCurrentVolume = bufferCurrentVolume;
|
||||
BufferMaxVolume = bufferMaxVolume;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,100 +1,230 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
#nullable enable
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
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.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
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";
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override uint? NetID => ContentNetIDs.SOLUTION;
|
||||
|
||||
private Solution _solution = new();
|
||||
private ReagentUnit _maxVolume;
|
||||
private Color _substanceColor;
|
||||
|
||||
/// <summary>
|
||||
/// The contained solution.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Solution Solution
|
||||
{
|
||||
get => _solution;
|
||||
set
|
||||
{
|
||||
if (_solution == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
public Solution Solution { get; private set; } = new();
|
||||
|
||||
_solution = value;
|
||||
Dirty();
|
||||
}
|
||||
}
|
||||
public IReadOnlyList<Solution.ReagentQuantity> ReagentList => Solution.Contents;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public ReagentUnit MaxVolume { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The total volume of all the of the reagents in the container.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public ReagentUnit CurrentVolume => Solution.TotalVolume;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum volume of the container.
|
||||
/// Volume needed to fill this container.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public ReagentUnit MaxVolume
|
||||
{
|
||||
get => _maxVolume;
|
||||
set
|
||||
{
|
||||
if (_maxVolume == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
[ViewVariables]
|
||||
public ReagentUnit EmptyVolume => MaxVolume - CurrentVolume;
|
||||
|
||||
_maxVolume = value;
|
||||
Dirty();
|
||||
}
|
||||
}
|
||||
[ViewVariables]
|
||||
public virtual Color Color => Solution.Color;
|
||||
|
||||
/// <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>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public virtual Color SubstanceColor
|
||||
{
|
||||
get => _substanceColor;
|
||||
set
|
||||
{
|
||||
if (_substanceColor == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
public bool CanReact { get; set; }
|
||||
|
||||
_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)]
|
||||
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()
|
||||
{
|
||||
return new SolutionContainerComponentState(Solution);
|
||||
@@ -102,14 +232,34 @@ namespace Content.Shared.GameObjects.Components.Chemistry
|
||||
|
||||
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
|
||||
{
|
||||
base.HandleComponentState(curState, nextState);
|
||||
|
||||
if (curState is not SolutionContainerComponentState state)
|
||||
{
|
||||
if (curState is not SolutionContainerComponentState containerState)
|
||||
return;
|
||||
|
||||
Solution = containerState.Solution;
|
||||
}
|
||||
}
|
||||
|
||||
_solution = state.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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Collections.Generic;
|
||||
|
||||
namespace Content.Shared.GameObjects.EntitySystems
|
||||
{
|
||||
//TODO: Reimplement sounds for reactions
|
||||
public class ChemicalReactionSystem : EntitySystem
|
||||
{
|
||||
private IEnumerable<ReactionPrototype> _reactions;
|
||||
|
||||
Reference in New Issue
Block a user