]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Moved Serverside solution container code to shared (yes that includes ensureSolution...
authorJezithyr <jezithyr@gmail.com>
Tue, 7 May 2024 00:27:36 +0000 (17:27 -0700)
committerGitHub <noreply@github.com>
Tue, 7 May 2024 00:27:36 +0000 (17:27 -0700)
* Added warning to tryGetSolution, moved SolutionContainer code to shared
- Added an optional warning (false by default) to print an error if a solution is missing when using tryGetSolution methods
- Moved ensuring solution containers to shared, left the old method stubs for compatability and marked them as obsolete.

* Update SharedSolutionContainerSystem.cs

* Update SharedSolutionContainerSystem.cs

* Update SolutionContainerSystem.cs

* Update SharedSolutionContainerSystem.cs

* Fixing ensuring chem solutions always returning false on client
- ensuring chem solutions will only return false on the client if it is waiting for a server solutionEntity to be synced

* Added concentration helpers

* fix whitespace

Content.Server/Chemistry/Containers/EntitySystems/SolutionContainerSystem.cs
Content.Shared/Chemistry/EntitySystems/SharedSolutionContainerSystem.cs

index 755312554c26495160f3bf27d871d51f6879b5ee..3d99db1129ceb7f7c3ce68ce535b696722d22906 100644 (file)
@@ -9,180 +9,37 @@ using System.Numerics;
 
 namespace Content.Server.Chemistry.Containers.EntitySystems;
 
+[Obsolete("This is being depreciated. Use SharedSolutionContainerSystem instead!")]
 public sealed partial class SolutionContainerSystem : SharedSolutionContainerSystem
 {
-    public override void Initialize()
-    {
-        base.Initialize();
-
-        SubscribeLocalEvent<SolutionContainerManagerComponent, MapInitEvent>(OnMapInit);
-        SubscribeLocalEvent<SolutionContainerManagerComponent, ComponentShutdown>(OnComponentShutdown);
-        SubscribeLocalEvent<ContainedSolutionComponent, ComponentShutdown>(OnComponentShutdown);
-    }
-
-
+    [Obsolete("This is being depreciated. Use the ensure methods in SharedSolutionContainerSystem instead!")]
     public Solution EnsureSolution(Entity<MetaDataComponent?> entity, string name)
         => EnsureSolution(entity, name, out _);
 
+    [Obsolete("This is being depreciated. Use the ensure methods in SharedSolutionContainerSystem instead!")]
     public Solution EnsureSolution(Entity<MetaDataComponent?> entity, string name, out bool existed)
         => EnsureSolution(entity, name, FixedPoint2.Zero, out existed);
 
+    [Obsolete("This is being depreciated. Use the ensure methods in SharedSolutionContainerSystem instead!")]
     public Solution EnsureSolution(Entity<MetaDataComponent?> entity, string name, FixedPoint2 maxVol, out bool existed)
         => EnsureSolution(entity, name, maxVol, null, out existed);
 
+    [Obsolete("This is being depreciated. Use the ensure methods in SharedSolutionContainerSystem instead!")]
     public Solution EnsureSolution(Entity<MetaDataComponent?> entity, string name, FixedPoint2 maxVol, Solution? prototype, out bool existed)
     {
-        var (uid, meta) = entity;
-        if (!Resolve(uid, ref meta))
-            throw new InvalidOperationException("Attempted to ensure solution on invalid entity.");
-
-        var manager = EnsureComp<SolutionContainerManagerComponent>(uid);
-        if (meta.EntityLifeStage >= EntityLifeStage.MapInitialized)
-            return EnsureSolutionEntity((uid, manager), name, maxVol, prototype, out existed).Comp.Solution;
-        else
-            return EnsureSolutionPrototype((uid, manager), name, maxVol, prototype, out existed);
-    }
-
-    public void EnsureAllSolutions(Entity<SolutionContainerManagerComponent> entity)
-    {
-        if (entity.Comp.Solutions is not { } prototypes)
-            return;
-
-        foreach (var (name, prototype) in prototypes)
-        {
-            EnsureSolutionEntity((entity.Owner, entity.Comp), name, prototype.MaxVolume, prototype, out _);
-        }
-
-        entity.Comp.Solutions = null;
-        Dirty(entity);
-    }
-
-    public Entity<SolutionComponent> EnsureSolutionEntity(Entity<SolutionContainerManagerComponent?> entity, string name, FixedPoint2 maxVol, Solution? prototype, out bool existed)
-    {
-        existed = true;
-
-        var (uid, container) = entity;
-
-        var solutionSlot = ContainerSystem.EnsureContainer<ContainerSlot>(uid, $"solution@{name}", out existed);
-        if (!Resolve(uid, ref container, logMissing: false))
-        {
-            existed = false;
-            container = AddComp<SolutionContainerManagerComponent>(uid);
-            container.Containers.Add(name);
-        }
-        else if (!existed)
-        {
-            container.Containers.Add(name);
-            Dirty(uid, container);
-        }
-
-        var needsInit = false;
-        SolutionComponent solutionComp;
-        if (solutionSlot.ContainedEntity is not { } solutionId)
-        {
-            prototype ??= new() { MaxVolume = maxVol };
-            prototype.Name = name;
-            (solutionId, solutionComp, _) = SpawnSolutionUninitialized(solutionSlot, name, maxVol, prototype);
-            existed = false;
-            needsInit = true;
-            Dirty(uid, container);
-        }
-        else
-        {
-            solutionComp = Comp<SolutionComponent>(solutionId);
-            DebugTools.Assert(TryComp(solutionId, out ContainedSolutionComponent? relation) && relation.Container == uid && relation.ContainerName == name);
-            DebugTools.Assert(solutionComp.Solution.Name == name);
-
-            var solution = solutionComp.Solution;
-            solution.MaxVolume = FixedPoint2.Max(solution.MaxVolume, maxVol);
-
-            // Depending on MapInitEvent order some systems can ensure solution empty solutions and conflict with the prototype solutions.
-            // We want the reagents from the prototype to exist even if something else already created the solution.
-            if (prototype is { Volume.Value: > 0 })
-                solution.AddSolution(prototype, PrototypeManager);
-
-            Dirty(solutionId, solutionComp);
-        }
-
-        if (needsInit)
-            EntityManager.InitializeAndStartEntity(solutionId, Transform(solutionId).MapID);
-
-        return (solutionId, solutionComp);
-    }
-
-    private Solution EnsureSolutionPrototype(Entity<SolutionContainerManagerComponent?> entity, string name, FixedPoint2 maxVol, Solution? prototype, out bool existed)
-    {
-        existed = true;
-
-        var (uid, container) = entity;
-        if (!Resolve(uid, ref container, logMissing: false))
-        {
-            container = AddComp<SolutionContainerManagerComponent>(uid);
-            existed = false;
-        }
-
-        if (container.Solutions is null)
-            container.Solutions = new(SolutionContainerManagerComponent.DefaultCapacity);
-
-        if (!container.Solutions.TryGetValue(name, out var solution))
-        {
-            solution = prototype ?? new() { Name = name, MaxVolume = maxVol };
-            container.Solutions.Add(name, solution);
-            existed = false;
-        }
-        else
-            solution.MaxVolume = FixedPoint2.Max(solution.MaxVolume, maxVol);
-
-        Dirty(uid, container);
-        return solution;
+        EnsureSolution(entity, name, maxVol, prototype, out existed, out var solution);
+        return solution!;//solution is only ever null on the client, so we can suppress this
     }
 
-
-    private Entity<SolutionComponent, ContainedSolutionComponent> SpawnSolutionUninitialized(ContainerSlot container, string name, FixedPoint2 maxVol, Solution prototype)
+    [Obsolete("This is being depreciated. Use the ensure methods in SharedSolutionContainerSystem instead!")]
+    public Entity<SolutionComponent> EnsureSolutionEntity(
+        Entity<SolutionContainerManagerComponent?> entity,
+        string name,
+        FixedPoint2 maxVol,
+        Solution? prototype,
+        out bool existed)
     {
-        var coords = new EntityCoordinates(container.Owner, Vector2.Zero);
-        var uid = EntityManager.CreateEntityUninitialized(null, coords, null);
-
-        var solution = new SolutionComponent() { Solution = prototype };
-        AddComp(uid, solution);
-
-        var relation = new ContainedSolutionComponent() { Container = container.Owner, ContainerName = name };
-        AddComp(uid, relation);
-
-        MetaData.SetEntityName(uid, $"solution - {name}");
-        ContainerSystem.Insert(uid, container, force: true);
-
-        return (uid, solution, relation);
+        EnsureSolutionEntity(entity, name, out existed, out var solEnt, maxVol, prototype);
+        return solEnt!.Value;//solEnt is only ever null on the client, so we can suppress this
     }
-
-    #region Event Handlers
-
-    private void OnMapInit(Entity<SolutionContainerManagerComponent> entity, ref MapInitEvent args)
-    {
-        EnsureAllSolutions(entity);
-    }
-
-    private void OnComponentShutdown(Entity<SolutionContainerManagerComponent> entity, ref ComponentShutdown args)
-    {
-        foreach (var name in entity.Comp.Containers)
-        {
-            if (ContainerSystem.TryGetContainer(entity, $"solution@{name}", out var solutionContainer))
-                ContainerSystem.ShutdownContainer(solutionContainer);
-        }
-        entity.Comp.Containers.Clear();
-    }
-
-    private void OnComponentShutdown(Entity<ContainedSolutionComponent> entity, ref ComponentShutdown args)
-    {
-        if (TryComp(entity.Comp.Container, out SolutionContainerManagerComponent? container))
-        {
-            container.Containers.Remove(entity.Comp.ContainerName);
-            Dirty(entity.Comp.Container, container);
-        }
-
-        if (ContainerSystem.TryGetContainer(entity, $"solution@{entity.Comp.ContainerName}", out var solutionContainer))
-            ContainerSystem.ShutdownContainer(solutionContainer);
-    }
-
-    #endregion Event Handlers
 }
index 4b9100708557db0c2c8787ce4ba1d55122a22063..fdb2f550f9694e0d16e03f0fae57e189c7d80cc2 100644 (file)
@@ -11,10 +11,13 @@ using Robust.Shared.Prototypes;
 using Robust.Shared.Utility;
 using System.Diagnostics.CodeAnalysis;
 using System.Linq;
+using System.Numerics;
 using System.Runtime.CompilerServices;
 using System.Text;
 using Content.Shared.Hands.Components;
 using Content.Shared.Hands.EntitySystems;
+using Robust.Shared.Map;
+using Robust.Shared.Network;
 using Dependency = Robust.Shared.IoC.DependencyAttribute;
 
 namespace Content.Shared.Chemistry.EntitySystems;
@@ -58,6 +61,7 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
     [Dependency] protected readonly SharedHandsSystem Hands = default!;
     [Dependency] protected readonly SharedContainerSystem ContainerSystem = default!;
     [Dependency] protected readonly MetaDataSystem MetaData = default!;
+    [Dependency] protected readonly INetManager NetManager = default!;
 
     public override void Initialize()
     {
@@ -66,13 +70,18 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
         InitializeRelays();
 
         SubscribeLocalEvent<SolutionComponent, ComponentInit>(OnComponentInit);
-        SubscribeLocalEvent<SolutionComponent, ComponentStartup>(OnComponentStartup);
-        SubscribeLocalEvent<SolutionComponent, ComponentShutdown>(OnComponentShutdown);
-
-        SubscribeLocalEvent<SolutionContainerManagerComponent, ComponentInit>(OnComponentInit);
-
+        SubscribeLocalEvent<SolutionComponent, ComponentStartup>(OnSolutionStartup);
+        SubscribeLocalEvent<SolutionComponent, ComponentShutdown>(OnSolutionShutdown);
+        SubscribeLocalEvent<SolutionContainerManagerComponent, ComponentInit>(OnContainerManagerInit);
         SubscribeLocalEvent<ExaminableSolutionComponent, ExaminedEvent>(OnExamineSolution);
         SubscribeLocalEvent<ExaminableSolutionComponent, GetVerbsEvent<ExamineVerb>>(OnSolutionExaminableVerb);
+        SubscribeLocalEvent<SolutionContainerManagerComponent, MapInitEvent>(OnMapInit);
+
+        if (NetManager.IsServer)
+        {
+            SubscribeLocalEvent<SolutionContainerManagerComponent, ComponentShutdown>(OnContainerManagerShutdown);
+            SubscribeLocalEvent<ContainedSolutionComponent, ComponentShutdown>(OnContainedSolutionShutdown);
+        }
     }
 
 
@@ -121,8 +130,14 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
     /// <param name="name">The name of the solution entity to fetch.</param>
     /// <param name="entity">Returns the solution entity that was fetched.</param>
     /// <param name="solution">Returns the solution state of the solution entity that was fetched.</param>
+    /// /// <param name="errorOnMissing">Should we print an error if the solution specified by name is missing</param>
     /// <returns></returns>
-    public bool TryGetSolution(Entity<SolutionContainerManagerComponent?> container, string? name, [NotNullWhen(true)] out Entity<SolutionComponent>? entity, [NotNullWhen(true)] out Solution? solution)
+    public bool TryGetSolution(
+        Entity<SolutionContainerManagerComponent?> container,
+        string? name,
+        [NotNullWhen(true)] out Entity<SolutionComponent>? entity,
+        [NotNullWhen(true)] out Solution? solution,
+        bool errorOnMissing = false)
     {
         if (!TryGetSolution(container, name, out entity))
         {
@@ -135,7 +150,11 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
     }
 
     /// <inheritdoc cref="TryGetSolution"/>
-    public bool TryGetSolution(Entity<SolutionContainerManagerComponent?> container, string? name, [NotNullWhen(true)] out Entity<SolutionComponent>? entity)
+    public bool TryGetSolution(
+        Entity<SolutionContainerManagerComponent?> container,
+        string? name,
+        [NotNullWhen(true)] out Entity<SolutionComponent>? entity,
+        bool errorOnMissing = false)
     {
         if (TryComp(container, out BlockSolutionAccessComponent? blocker))
         {
@@ -155,12 +174,18 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
         else
         {
             entity = null;
+            if (!errorOnMissing)
+                return false;
+            Log.Error($"{ToPrettyString(container)} does not have a solution with ID: {name}");
             return false;
         }
 
         if (!TryComp(uid, out SolutionComponent? comp))
         {
             entity = null;
+            if (!errorOnMissing)
+                return false;
+            Log.Error($"{ToPrettyString(container)} does not have a solution with ID: {name}");
             return false;
         }
 
@@ -171,13 +196,18 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
     /// <summary>
     /// Version of TryGetSolution that doesn't take or return an entity.
     /// Used for prototypes and with old code parity.
-    public bool TryGetSolution(SolutionContainerManagerComponent container, string name, [NotNullWhen(true)] out Solution? solution)
+    public bool TryGetSolution(SolutionContainerManagerComponent container,
+        string name,
+        [NotNullWhen(true)] out Solution? solution,
+        bool errorOnMissing = false)
     {
         solution = null;
-        if (container.Solutions == null)
+        if (container.Solutions != null)
+            return container.Solutions.TryGetValue(name, out solution);
+        if (!errorOnMissing)
             return false;
-
-        return container.Solutions.TryGetValue(name, out solution);
+        Log.Error($"{container} does not have a solution with ID: {name}");
+        return false;
     }
 
     public IEnumerable<(string? Name, Entity<SolutionComponent> Solution)> EnumerateSolutions(Entity<SolutionContainerManagerComponent?> container, bool includeSelf = true)
@@ -703,17 +733,17 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
         entity.Comp.Solution.ValidateSolution();
     }
 
-    private void OnComponentStartup(Entity<SolutionComponent> entity, ref ComponentStartup args)
+    private void OnSolutionStartup(Entity<SolutionComponent> entity, ref ComponentStartup args)
     {
         UpdateChemicals(entity);
     }
 
-    private void OnComponentShutdown(Entity<SolutionComponent> entity, ref ComponentShutdown args)
+    private void OnSolutionShutdown(Entity<SolutionComponent> entity, ref ComponentShutdown args)
     {
         RemoveAllSolution(entity);
     }
 
-    private void OnComponentInit(Entity<SolutionContainerManagerComponent> entity, ref ComponentInit args)
+    private void OnContainerManagerInit(Entity<SolutionContainerManagerComponent> entity, ref ComponentInit args)
     {
         if (entity.Comp.Containers is not { Count: > 0 } containers)
             return;
@@ -904,5 +934,273 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
         return true;
     }
 
+    private void OnMapInit(Entity<SolutionContainerManagerComponent> entity, ref MapInitEvent args)
+    {
+        EnsureAllSolutions(entity);
+    }
+
+    private void OnContainerManagerShutdown(Entity<SolutionContainerManagerComponent> entity, ref ComponentShutdown args)
+    {
+        foreach (var name in entity.Comp.Containers)
+        {
+            if (ContainerSystem.TryGetContainer(entity, $"solution@{name}", out var solutionContainer))
+                ContainerSystem.ShutdownContainer(solutionContainer);
+        }
+        entity.Comp.Containers.Clear();
+    }
+
+    private void OnContainedSolutionShutdown(Entity<ContainedSolutionComponent> entity, ref ComponentShutdown args)
+    {
+        if (TryComp(entity.Comp.Container, out SolutionContainerManagerComponent? container))
+        {
+            container.Containers.Remove(entity.Comp.ContainerName);
+            Dirty(entity.Comp.Container, container);
+        }
+
+        if (ContainerSystem.TryGetContainer(entity, $"solution@{entity.Comp.ContainerName}", out var solutionContainer))
+            ContainerSystem.ShutdownContainer(solutionContainer);
+    }
+
     #endregion Event Handlers
+
+    public bool EnsureSolution(
+        Entity<MetaDataComponent?> entity,
+        string name,
+        [NotNullWhen(true)]out Solution? solution,
+        FixedPoint2 maxVol = default)
+    {
+        return EnsureSolution(entity, name, maxVol, null, out _, out solution);
+    }
+
+    public bool EnsureSolution(
+        Entity<MetaDataComponent?> entity,
+        string name,
+        out bool existed,
+        [NotNullWhen(true)]out Solution? solution,
+        FixedPoint2 maxVol = default)
+    {
+        return EnsureSolution(entity, name, maxVol, null, out existed, out solution);
+    }
+
+    public bool EnsureSolution(
+        Entity<MetaDataComponent?> entity,
+        string name,
+        FixedPoint2 maxVol,
+        Solution? prototype,
+        out bool existed,
+        [NotNullWhen(true)] out Solution? solution)
+    {
+        solution = null;
+        existed = false;
+
+        var (uid, meta) = entity;
+        if (!Resolve(uid, ref meta))
+            throw new InvalidOperationException("Attempted to ensure solution on invalid entity.");
+        var manager = EnsureComp<SolutionContainerManagerComponent>(uid);
+        if (meta.EntityLifeStage >= EntityLifeStage.MapInitialized)
+        {
+            EnsureSolutionEntity((uid, manager), name, out existed,
+                out var solEnt, maxVol, prototype);
+            solution = solEnt!.Value.Comp.Solution;
+            return true;
+        }
+        solution = EnsureSolutionPrototype((uid, manager), name, maxVol, prototype, out existed);
+        return true;
+    }
+
+    public void EnsureAllSolutions(Entity<SolutionContainerManagerComponent> entity)
+    {
+        if (NetManager.IsClient)
+            return;
+
+        if (entity.Comp.Solutions is not { } prototypes)
+            return;
+
+        foreach (var (name, prototype) in prototypes)
+        {
+            EnsureSolutionEntity((entity.Owner, entity.Comp), name, out _, out _, prototype.MaxVolume, prototype);
+        }
+
+        entity.Comp.Solutions = null;
+        Dirty(entity);
+    }
+
+    public bool EnsureSolutionEntity(
+        Entity<SolutionContainerManagerComponent?> entity,
+        string name,
+        [NotNullWhen(true)] out Entity<SolutionComponent>? solutionEntity,
+        FixedPoint2 maxVol = default) =>
+        EnsureSolutionEntity(entity, name, out _, out solutionEntity, maxVol);
+
+    public bool EnsureSolutionEntity(
+        Entity<SolutionContainerManagerComponent?> entity,
+        string name,
+        out bool existed,
+        [NotNullWhen(true)] out Entity<SolutionComponent>? solutionEntity,
+        FixedPoint2 maxVol = default,
+        Solution? prototype = null
+        )
+    {
+        existed = true;
+        solutionEntity = null;
+
+        var (uid, container) = entity;
+
+        var solutionSlot = ContainerSystem.EnsureContainer<ContainerSlot>(uid, $"solution@{name}", out existed);
+        if (!Resolve(uid, ref container, logMissing: false))
+        {
+            existed = false;
+            container = AddComp<SolutionContainerManagerComponent>(uid);
+            container.Containers.Add(name);
+            if (NetManager.IsClient)
+                return false;
+        }
+        else if (!existed)
+        {
+            container.Containers.Add(name);
+            Dirty(uid, container);
+        }
+
+        var needsInit = false;
+        SolutionComponent solutionComp;
+        if (solutionSlot.ContainedEntity is not { } solutionId)
+        {
+            if (NetManager.IsClient)
+                return false;
+            prototype ??= new() { MaxVolume = maxVol };
+            prototype.Name = name;
+            (solutionId, solutionComp, _) = SpawnSolutionUninitialized(solutionSlot, name, maxVol, prototype);
+            existed = false;
+            needsInit = true;
+            Dirty(uid, container);
+        }
+        else
+        {
+            solutionComp = Comp<SolutionComponent>(solutionId);
+            DebugTools.Assert(TryComp(solutionId, out ContainedSolutionComponent? relation) && relation.Container == uid && relation.ContainerName == name);
+            DebugTools.Assert(solutionComp.Solution.Name == name);
+
+            var solution = solutionComp.Solution;
+            solution.MaxVolume = FixedPoint2.Max(solution.MaxVolume, maxVol);
+
+            // Depending on MapInitEvent order some systems can ensure solution empty solutions and conflict with the prototype solutions.
+            // We want the reagents from the prototype to exist even if something else already created the solution.
+            if (prototype is { Volume.Value: > 0 })
+                solution.AddSolution(prototype, PrototypeManager);
+
+            Dirty(solutionId, solutionComp);
+        }
+
+        if (needsInit)
+            EntityManager.InitializeAndStartEntity(solutionId, Transform(solutionId).MapID);
+        solutionEntity = (solutionId, solutionComp);
+        return true;
+    }
+
+    private Solution EnsureSolutionPrototype(Entity<SolutionContainerManagerComponent?> entity, string name, FixedPoint2 maxVol, Solution? prototype, out bool existed)
+    {
+        existed = true;
+
+        var (uid, container) = entity;
+        if (!Resolve(uid, ref container, logMissing: false))
+        {
+            container = AddComp<SolutionContainerManagerComponent>(uid);
+            existed = false;
+        }
+
+        if (container.Solutions is null)
+            container.Solutions = new(SolutionContainerManagerComponent.DefaultCapacity);
+
+        if (!container.Solutions.TryGetValue(name, out var solution))
+        {
+            solution = prototype ?? new() { Name = name, MaxVolume = maxVol };
+            container.Solutions.Add(name, solution);
+            existed = false;
+        }
+        else
+            solution.MaxVolume = FixedPoint2.Max(solution.MaxVolume, maxVol);
+
+        Dirty(uid, container);
+        return solution;
+    }
+    
+    private Entity<SolutionComponent, ContainedSolutionComponent> SpawnSolutionUninitialized(ContainerSlot container, string name, FixedPoint2 maxVol, Solution prototype)
+    {
+        var coords = new EntityCoordinates(container.Owner, Vector2.Zero);
+        var uid = EntityManager.CreateEntityUninitialized(null, coords, null);
+
+        var solution = new SolutionComponent() { Solution = prototype };
+        AddComp(uid, solution);
+
+        var relation = new ContainedSolutionComponent() { Container = container.Owner, ContainerName = name };
+        AddComp(uid, relation);
+
+        MetaData.SetEntityName(uid, $"solution - {name}");
+        ContainerSystem.Insert(uid, container, force: true);
+
+        return (uid, solution, relation);
+    }
+
+    public void AdjustDissolvedReagent(
+        Entity<SolutionComponent> dissolvedSolution,
+        FixedPoint2 volume,
+        ReagentId reagent,
+        float concentrationChange)
+    {
+        if (concentrationChange == 0)
+            return;
+        var dissolvedSol = dissolvedSolution.Comp.Solution;
+        var amtChange =
+            GetReagentQuantityFromConcentration(dissolvedSolution, volume, MathF.Abs(concentrationChange));
+        if (concentrationChange > 0)
+        {
+            dissolvedSol.AddReagent(reagent, amtChange);
+        }
+        else
+        {
+            dissolvedSol.RemoveReagent(reagent,amtChange);
+        }
+        UpdateChemicals(dissolvedSolution);
+    }
+
+    public FixedPoint2 GetReagentQuantityFromConcentration(Entity<SolutionComponent> dissolvedSolution,
+        FixedPoint2 volume,float concentration)
+    {
+        var dissolvedSol = dissolvedSolution.Comp.Solution;
+        if (volume == 0
+            || dissolvedSol.Volume == 0)
+            return 0;
+        return concentration * volume;
+    }
+
+    public float GetReagentConcentration(Entity<SolutionComponent> dissolvedSolution,
+        FixedPoint2 volume, ReagentId dissolvedReagent)
+    {
+        var dissolvedSol = dissolvedSolution.Comp.Solution;
+        if (volume == 0
+            || dissolvedSol.Volume == 0
+            || !dissolvedSol.TryGetReagentQuantity(dissolvedReagent, out var dissolvedVol))
+            return 0;
+        return (float)dissolvedVol / volume.Float();
+    }
+
+    public FixedPoint2 ClampReagentAmountByConcentration(
+        Entity<SolutionComponent> dissolvedSolution,
+        FixedPoint2 volume,
+        ReagentId dissolvedReagent,
+        FixedPoint2 dissolvedReagentAmount,
+        float maxConcentration = 1f)
+    {
+        var dissolvedSol = dissolvedSolution.Comp.Solution;
+        if (volume == 0
+            || dissolvedSol.Volume == 0
+            || !dissolvedSol.TryGetReagentQuantity(dissolvedReagent, out var dissolvedVol))
+            return 0;
+        volume *= maxConcentration;
+        dissolvedVol += dissolvedReagentAmount;
+        var overflow = volume - dissolvedVol;
+        if (overflow < 0)
+            dissolvedReagentAmount += overflow;
+        return dissolvedReagentAmount;
+    }
 }