From 7bf80fd4b8e49967af2b4614cb0c483d832d3d5f Mon Sep 17 00:00:00 2001 From: py01 <60152240+collinlunn@users.noreply.github.com> Date: Sun, 10 Jan 2021 02:41:55 -0600 Subject: [PATCH] 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 --- .../Chemistry/ChemMaster/ChemMasterWindow.cs | 4 +- .../Chemistry/SolutionContainerComponent.cs | 18 +- .../Body/Behavior/StomachBehavior.cs | 4 +- .../Body/Circulatory/BloodstreamComponent.cs | 2 +- .../Chemistry/ChemMasterComponent.cs | 31 +- .../Chemistry/SolutionContainerComponent.cs | 247 +-------------- .../Components/Fluids/PuddleComponent.cs | 2 +- .../Components/Fluids/SprayComponent.cs | 2 +- .../Interactable/WelderComponent.cs | 2 +- Content.Shared/Chemistry/ReagentUnit.cs | 6 +- Content.Shared/Chemistry/Solution.cs | 5 + .../ChemMaster/SharedChemMasterComponent.cs | 10 +- .../SharedSolutionContainerComponent.cs | 288 +++++++++++++----- .../EntitySystems/ChemicalReactionSystem.cs | 1 + 14 files changed, 260 insertions(+), 362 deletions(-) diff --git a/Content.Client/GameObjects/Components/Chemistry/ChemMaster/ChemMasterWindow.cs b/Content.Client/GameObjects/Components/Chemistry/ChemMaster/ChemMasterWindow.cs index 0524a87ff7..2e6fd8d51c 100644 --- a/Content.Client/GameObjects/Components/Chemistry/ChemMaster/ChemMasterWindow.cs +++ b/Content.Client/GameObjects/Components/Chemistry/ChemMaster/ChemMasterWindow.cs @@ -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); diff --git a/Content.Client/GameObjects/Components/Chemistry/SolutionContainerComponent.cs b/Content.Client/GameObjects/Components/Chemistry/SolutionContainerComponent.cs index e52a03c747..cb1e8e61d1 100644 --- a/Content.Client/GameObjects/Components/Chemistry/SolutionContainerComponent.cs +++ b/Content.Client/GameObjects/Components/Chemistry/SolutionContainerComponent.cs @@ -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; - } } } diff --git a/Content.Server/GameObjects/Components/Body/Behavior/StomachBehavior.cs b/Content.Server/GameObjects/Components/Body/Behavior/StomachBehavior.cs index 0f1dd177c7..30107dbd03 100644 --- a/Content.Server/GameObjects/Components/Body/Behavior/StomachBehavior.cs +++ b/Content.Server/GameObjects/Components/Body/Behavior/StomachBehavior.cs @@ -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) { diff --git a/Content.Server/GameObjects/Components/Body/Circulatory/BloodstreamComponent.cs b/Content.Server/GameObjects/Components/Body/Circulatory/BloodstreamComponent.cs index b38b2db3a7..e915c526ce 100644 --- a/Content.Server/GameObjects/Components/Body/Circulatory/BloodstreamComponent.cs +++ b/Content.Server/GameObjects/Components/Body/Circulatory/BloodstreamComponent.cs @@ -68,7 +68,7 @@ namespace Content.Server.GameObjects.Components.Body.Circulatory return false; } - _internalSolution.TryAddSolution(solution, false, true); + _internalSolution.TryAddSolution(solution); return true; } diff --git a/Content.Server/GameObjects/Components/Chemistry/ChemMasterComponent.cs b/Content.Server/GameObjects/Components/Chemistry/ChemMasterComponent.cs index 3de7bbd767..ad6dee4f94 100644 --- a/Content.Server/GameObjects/Components/Chemistry/ChemMasterComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/ChemMasterComponent.cs @@ -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($"{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(), BufferSolution.ReagentList.ToList(), _bufferModeTransfer, BufferSolution.CurrentVolume, BufferSolution.MaxVolume); + "", Owner.Name, new List(), BufferSolution.Contents, _bufferModeTransfer, BufferSolution.TotalVolume); } var solution = beaker.GetComponent(); 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(); 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(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(out var pillSolution); pillSolution?.TryAddSolution(bufferSolution); diff --git a/Content.Server/GameObjects/Components/Chemistry/SolutionContainerComponent.cs b/Content.Server/GameObjects/Components/Chemistry/SolutionContainerComponent.cs index 48cf119f7a..79a003913e 100644 --- a/Content.Server/GameObjects/Components/Chemistry/SolutionContainerComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/SolutionContainerComponent.cs @@ -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 { - /// - /// ECS component that manages a liquid solution of reagents. - /// [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 _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; - - /// - /// The volume without reagents remaining in the container. - /// - [ViewVariables] - public ReagentUnit EmptyVolume => MaxVolume - CurrentVolume; - public IReadOnlyList 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); - - /// - 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(); - _chemistrySystem = _entitySystemManager.GetEntitySystem(); - _reactions = _prototypeManager.EnumeratePrototypes(); - _reactionSystem = _entitySystemManager.GetEntitySystem(); - } - - protected override void Startup() - { - base.Startup(); - RecalculateColor(); - if (!string.IsNullOrEmpty(_fillInitState)) - { - _spriteComponent = Owner.GetComponent(); - _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; - } - - /// - /// Attempt to remove the specified quantity from this solution - /// - /// Quantity of this solution to remove - /// Whether or not the solution was successfully removed - 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; - } - /// /// Transfers solution from the held container to the target container. /// @@ -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))); - } - } - } - /// /// Transfers solution from a target container to the held container. /// @@ -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(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); - } } } diff --git a/Content.Server/GameObjects/Components/Fluids/PuddleComponent.cs b/Content.Server/GameObjects/Components/Fluids/PuddleComponent.cs index d3f3275c53..9221cd60a0 100644 --- a/Content.Server/GameObjects/Components/Fluids/PuddleComponent.cs +++ b/Content.Server/GameObjects/Components/Fluids/PuddleComponent.cs @@ -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 { diff --git a/Content.Server/GameObjects/Components/Fluids/SprayComponent.cs b/Content.Server/GameObjects/Components/Fluids/SprayComponent.cs index 71f09c780e..8a45830637 100644 --- a/Content.Server/GameObjects/Components/Fluids/SprayComponent.cs +++ b/Content.Server/GameObjects/Components/Fluids/SprayComponent.cs @@ -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); } diff --git a/Content.Server/GameObjects/Components/Interactable/WelderComponent.cs b/Content.Server/GameObjects/Components/Interactable/WelderComponent.cs index 175c174aec..ac0a8b1bb7 100644 --- a/Content.Server/GameObjects/Components/Interactable/WelderComponent.cs +++ b/Content.Server/GameObjects/Components/Interactable/WelderComponent.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using System; using System.Threading.Tasks; using Content.Server.Atmos; diff --git a/Content.Shared/Chemistry/ReagentUnit.cs b/Content.Shared/Chemistry/ReagentUnit.cs index 0697d4c421..67a900ec5f 100644 --- a/Content.Shared/Chemistry/ReagentUnit.cs +++ b/Content.Shared/Chemistry/ReagentUnit.cs @@ -1,10 +1,14 @@ -using System; +using System; using System.Globalization; using System.Linq; using Robust.Shared.Interfaces.Serialization; namespace Content.Shared.Chemistry { + /// + /// 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. + /// [Serializable] public struct ReagentUnit : ISelfSerialize, IComparable, IEquatable { diff --git a/Content.Shared/Chemistry/Solution.cs b/Content.Shared/Chemistry/Solution.cs index 9730bae940..ea334527b3 100644 --- a/Content.Shared/Chemistry/Solution.cs +++ b/Content.Shared/Chemistry/Solution.cs @@ -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) diff --git a/Content.Shared/GameObjects/Components/Chemistry/ChemMaster/SharedChemMasterComponent.cs b/Content.Shared/GameObjects/Components/Chemistry/ChemMaster/SharedChemMasterComponent.cs index 8aed76f83b..11c6437475 100644 --- a/Content.Shared/GameObjects/Components/Chemistry/ChemMaster/SharedChemMasterComponent.cs +++ b/Content.Shared/GameObjects/Components/Chemistry/ChemMaster/SharedChemMasterComponent.cs @@ -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 /// /// A list of the reagents and their amounts within the beaker/reagent container, if applicable. /// - public readonly List ContainerReagents; + public readonly IReadOnlyList ContainerReagents; /// /// A list of the reagents and their amounts within the buffer, if applicable. /// - public readonly List BufferReagents; + public readonly IReadOnlyList 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 containerReagents, List bufferReagents, bool bufferModeTransfer, ReagentUnit bufferCurrentVolume, ReagentUnit bufferMaxVolume) + string dispenserName, IReadOnlyList containerReagents, IReadOnlyList 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; } } diff --git a/Content.Shared/GameObjects/Components/Chemistry/SharedSolutionContainerComponent.cs b/Content.Shared/GameObjects/Components/Chemistry/SharedSolutionContainerComponent.cs index e65a975be4..c7c35f4295 100644 --- a/Content.Shared/GameObjects/Components/Chemistry/SharedSolutionContainerComponent.cs +++ b/Content.Shared/GameObjects/Components/Chemistry/SharedSolutionContainerComponent.cs @@ -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 + /// + /// Holds a with a limited volume. + /// + public abstract class SharedSolutionContainerComponent : Component, IExamine { public override string Name => "SolutionContainer"; /// public sealed override uint? NetID => ContentNetIDs.SOLUTION; - private Solution _solution = new(); - private ReagentUnit _maxVolume; - private Color _substanceColor; - - /// - /// The contained solution. - /// [ViewVariables] - public Solution Solution - { - get => _solution; - set - { - if (_solution == value) - { - return; - } + public Solution Solution { get; private set; } = new(); - _solution = value; - Dirty(); - } - } + public IReadOnlyList ReagentList => Solution.Contents; + + [ViewVariables(VVAccess.ReadWrite)] + public ReagentUnit MaxVolume { get; set; } - /// - /// The total volume of all the of the reagents in the container. - /// [ViewVariables] public ReagentUnit CurrentVolume => Solution.TotalVolume; /// - /// The maximum volume of the container. + /// Volume needed to fill this container. /// - [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; /// - /// The current blended color of all the reagents in the container. + /// If reactions will be checked for when adding reagents to the container. /// [ViewVariables(VVAccess.ReadWrite)] - public virtual Color SubstanceColor - { - get => _substanceColor; - set - { - if (_substanceColor == value) - { - return; - } + public bool CanReact { get; set; } - _substanceColor = value; - Dirty(); - } - } - - /// - /// The current capabilities of this container (is the top open to pour? can I inject it into another object?). - /// [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(); + } + + /// + /// Adds reagent of an Id to the container. + /// + /// The Id of the reagent to add. + /// The amount of reagent to add. + /// The amount of reagent sucesfully added. + /// If all the reagent could be added. + 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; + } + + /// + /// Removes reagent of an Id to the container. + /// + /// The Id of the reagent to remove. + /// The amount of reagent to remove. + /// If the reagent to remove was found in the container. + public bool TryRemoveReagent(string reagentId, ReagentUnit quantity) + { + if (!Solution.ContainsReagent(reagentId)) + return false; + + Solution.RemoveReagent(reagentId, quantity); + ChemicalsRemoved(); + return true; + } + + /// + /// Removes part of the solution in the container. + /// + /// the volume of solution to remove. + /// The solution that was removed. + public Solution SplitSolution(ReagentUnit quantity) + { + var splitSol = Solution.SplitSolution(quantity); + ChemicalsRemoved(); + return splitSol; + } + + /// + /// Checks if a solution can fit into the container. + /// + /// The solution that is trying to be added. + /// If the solution can be fully added. + public bool CanAddSolution(Solution solution) + { + return solution.TotalVolume <= EmptyVolume; + } + + /// + /// Adds a solution to the container, if it can fully fit. + /// + /// The solution to try to add. + /// If the solution could be added. + 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() + .HandleSolutionChange(Owner); + } + + private void ProcessReactions() + { + if (!CanReact) + return; + + EntitySystem.Get() + .FullyReactSolution(Solution, Owner, MaxVolume); + } + + void IExamine.Examine(FormattedMessage message, bool inDetailsRange) + { + if (!CanExamineContents) + return; + + var prototypeManager = IoCManager.Resolve(); + + 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(out var appearance)) + return; + + appearance.SetData(SolutionContainerVisuals.VisualState, GetVisualState()); + } + + private SolutionContainerVisualState GetVisualState() + { + var filledVolumeFraction = CurrentVolume.Float() / MaxVolume.Float(); + + return new SolutionContainerVisualState(Color, filledVolumeFraction); + } - /// 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 = state.Solution; + Solution = containerState.Solution; + } + } + + [Serializable, NetSerializable] + public enum SolutionContainerVisuals : byte + { + VisualState + } + + [Serializable, NetSerializable] + public class SolutionContainerVisualState + { + public readonly Color Color; + + /// + /// Represents how full the container is, as a fraction equivalent to /. + /// + public readonly byte FilledVolumeFraction; + + /// The fraction of the container's volume that is filled. + public SolutionContainerVisualState(Color color, float filledVolumeFraction) + { + Color = color; + FilledVolumeFraction = (byte) (byte.MaxValue * filledVolumeFraction); } } diff --git a/Content.Shared/GameObjects/EntitySystems/ChemicalReactionSystem.cs b/Content.Shared/GameObjects/EntitySystems/ChemicalReactionSystem.cs index 0c43fff0d9..811c97756d 100644 --- a/Content.Shared/GameObjects/EntitySystems/ChemicalReactionSystem.cs +++ b/Content.Shared/GameObjects/EntitySystems/ChemicalReactionSystem.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; namespace Content.Shared.GameObjects.EntitySystems { + //TODO: Reimplement sounds for reactions public class ChemicalReactionSystem : EntitySystem { private IEnumerable _reactions;