diff --git a/Content.IntegrationTests/Tests/Fluids/AbsorbentTest.cs b/Content.IntegrationTests/Tests/Fluids/AbsorbentTest.cs new file mode 100644 index 0000000000..de5226202c --- /dev/null +++ b/Content.IntegrationTests/Tests/Fluids/AbsorbentTest.cs @@ -0,0 +1,344 @@ +using Content.Server.Fluids.EntitySystems; +using Content.Shared.Chemistry.Components; +using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.FixedPoint; +using Content.Shared.Fluids; +using Robust.Shared.GameObjects; +using Robust.Shared.Prototypes; +using System.Collections.Generic; +using System.Linq; + +namespace Content.IntegrationTests.Tests.Fluids; + +[TestFixture] +[TestOf(typeof(AbsorbentComponent))] +public sealed class AbsorbentTest +{ + private const string UserDummyId = "UserDummy"; + private const string AbsorbentDummyId = "AbsorbentDummy"; + private const string RefillableDummyId = "RefillableDummy"; + private const string SmallRefillableDummyId = "SmallRefillableDummy"; + + private const string EvaporablePrototypeId = "Water"; + private const string NonEvaporablePrototypeId = "Cola"; + + [TestPrototypes] + private const string Prototypes = $@" +- type: entity + name: {UserDummyId} + id: {UserDummyId} + +- type: entity + name: {AbsorbentDummyId} + id: {AbsorbentDummyId} + components: + - type: Absorbent + - type: SolutionContainerManager + solutions: + absorbed: + maxVol: 100 + +- type: entity + name: {RefillableDummyId} + id: {RefillableDummyId} + components: + - type: SolutionContainerManager + solutions: + refillable: + maxVol: 200 + - type: RefillableSolution + solution: refillable + +- type: entity + name: {SmallRefillableDummyId} + id: {SmallRefillableDummyId} + components: + - type: SolutionContainerManager + solutions: + refillable: + maxVol: 20 + - type: RefillableSolution + solution: refillable +"; + public sealed record TestSolutionReagents(FixedPoint2 VolumeOfEvaporable, FixedPoint2 VolumeOfNonEvaporable); + + public record TestSolutionCase( + string Case, // Only for clarity purposes + TestSolutionReagents InitialAbsorbentSolution, + TestSolutionReagents InitialRefillableSolution, + TestSolutionReagents ExpectedAbsorbentSolution, + TestSolutionReagents ExpectedRefillableSolution); + + [TestCaseSource(nameof(TestCasesToRun))] + public async Task AbsorbentOnRefillableTest(TestSolutionCase testCase) + { + await using var pair = await PoolManager.GetServerClient(); + var server = pair.Server; + + var testMap = await pair.CreateTestMap(); + var coordinates = testMap.GridCoords; + + var entityManager = server.ResolveDependency(); + var absorbentSystem = entityManager.System(); + var solutionContainerSystem = entityManager.System(); + var prototypeManager = server.ResolveDependency(); + + EntityUid user = default; + EntityUid absorbent = default; + EntityUid refillable = default; + AbsorbentComponent component = null; + await server.WaitAssertion(() => + { + user = entityManager.SpawnEntity(UserDummyId, coordinates); + absorbent = entityManager.SpawnEntity(AbsorbentDummyId, coordinates); + refillable = entityManager.SpawnEntity(RefillableDummyId, coordinates); + + entityManager.TryGetComponent(absorbent, out component); + solutionContainerSystem.TryGetSolution(absorbent, AbsorbentComponent.SolutionName, out var absorbentSolution); + solutionContainerSystem.TryGetRefillableSolution(refillable, out var refillableSolution); + + // Arrange + if (testCase.InitialAbsorbentSolution.VolumeOfEvaporable > FixedPoint2.Zero) + solutionContainerSystem.AddSolution(absorbent, absorbentSolution, new Solution(EvaporablePrototypeId, testCase.InitialAbsorbentSolution.VolumeOfEvaporable)); + if (testCase.InitialAbsorbentSolution.VolumeOfNonEvaporable > FixedPoint2.Zero) + solutionContainerSystem.AddSolution(absorbent, absorbentSolution, new Solution(NonEvaporablePrototypeId, testCase.InitialAbsorbentSolution.VolumeOfNonEvaporable)); + + if (testCase.InitialRefillableSolution.VolumeOfEvaporable > FixedPoint2.Zero) + solutionContainerSystem.AddSolution(refillable, refillableSolution, new Solution(EvaporablePrototypeId, testCase.InitialRefillableSolution.VolumeOfEvaporable)); + if (testCase.InitialRefillableSolution.VolumeOfNonEvaporable > FixedPoint2.Zero) + solutionContainerSystem.AddSolution(refillable, refillableSolution, new Solution(NonEvaporablePrototypeId, testCase.InitialRefillableSolution.VolumeOfNonEvaporable)); + + // Act + absorbentSystem.Mop(user, refillable, absorbent, component); + + // Assert + var absorbentComposition = absorbentSolution.GetReagentPrototypes(prototypeManager).ToDictionary(r => r.Key.ID, r => r.Value); + var refillableComposition = refillableSolution.GetReagentPrototypes(prototypeManager).ToDictionary(r => r.Key.ID, r => r.Value); + Assert.Multiple(() => + { + Assert.That(VolumeOfPrototypeInComposition(absorbentComposition, EvaporablePrototypeId), Is.EqualTo(testCase.ExpectedAbsorbentSolution.VolumeOfEvaporable)); + Assert.That(VolumeOfPrototypeInComposition(absorbentComposition, NonEvaporablePrototypeId), Is.EqualTo(testCase.ExpectedAbsorbentSolution.VolumeOfNonEvaporable)); + Assert.That(VolumeOfPrototypeInComposition(refillableComposition, EvaporablePrototypeId), Is.EqualTo(testCase.ExpectedRefillableSolution.VolumeOfEvaporable)); + Assert.That(VolumeOfPrototypeInComposition(refillableComposition, NonEvaporablePrototypeId), Is.EqualTo(testCase.ExpectedRefillableSolution.VolumeOfNonEvaporable)); + }); + }); + await pair.RunTicksSync(5); + + await pair.CleanReturnAsync(); + } + + [TestCaseSource(nameof(TestCasesToRunOnSmallRefillable))] + public async Task AbsorbentOnSmallRefillableTest(TestSolutionCase testCase) + { + await using var pair = await PoolManager.GetServerClient(); + var server = pair.Server; + + var testMap = await pair.CreateTestMap(); + var coordinates = testMap.GridCoords; + + var entityManager = server.ResolveDependency(); + var absorbentSystem = entityManager.System(); + var solutionContainerSystem = entityManager.System(); + var prototypeManager = server.ResolveDependency(); + + EntityUid user = default; + EntityUid absorbent = default; + EntityUid refillable = default; + AbsorbentComponent component = null; + await server.WaitAssertion(() => + { + user = entityManager.SpawnEntity(UserDummyId, coordinates); + absorbent = entityManager.SpawnEntity(AbsorbentDummyId, coordinates); + refillable = entityManager.SpawnEntity(SmallRefillableDummyId, coordinates); + + entityManager.TryGetComponent(absorbent, out component); + solutionContainerSystem.TryGetSolution(absorbent, AbsorbentComponent.SolutionName, out var absorbentSolution); + solutionContainerSystem.TryGetRefillableSolution(refillable, out var refillableSolution); + + // Arrange + solutionContainerSystem.AddSolution(absorbent, absorbentSolution, new Solution(EvaporablePrototypeId, testCase.InitialAbsorbentSolution.VolumeOfEvaporable)); + if (testCase.InitialAbsorbentSolution.VolumeOfNonEvaporable > FixedPoint2.Zero) + solutionContainerSystem.AddSolution(absorbent, absorbentSolution, new Solution(NonEvaporablePrototypeId, testCase.InitialAbsorbentSolution.VolumeOfNonEvaporable)); + + if (testCase.InitialRefillableSolution.VolumeOfEvaporable > FixedPoint2.Zero) + solutionContainerSystem.AddSolution(refillable, refillableSolution, new Solution(EvaporablePrototypeId, testCase.InitialRefillableSolution.VolumeOfEvaporable)); + if (testCase.InitialRefillableSolution.VolumeOfNonEvaporable > FixedPoint2.Zero) + solutionContainerSystem.AddSolution(refillable, refillableSolution, new Solution(NonEvaporablePrototypeId, testCase.InitialRefillableSolution.VolumeOfNonEvaporable)); + + // Act + absorbentSystem.Mop(user, refillable, absorbent, component); + + // Assert + var absorbentComposition = absorbentSolution.GetReagentPrototypes(prototypeManager).ToDictionary(r => r.Key.ID, r => r.Value); + var refillableComposition = refillableSolution.GetReagentPrototypes(prototypeManager).ToDictionary(r => r.Key.ID, r => r.Value); + Assert.Multiple(() => + { + Assert.That(VolumeOfPrototypeInComposition(absorbentComposition, EvaporablePrototypeId), Is.EqualTo(testCase.ExpectedAbsorbentSolution.VolumeOfEvaporable)); + Assert.That(VolumeOfPrototypeInComposition(absorbentComposition, NonEvaporablePrototypeId), Is.EqualTo(testCase.ExpectedAbsorbentSolution.VolumeOfNonEvaporable)); + Assert.That(VolumeOfPrototypeInComposition(refillableComposition, EvaporablePrototypeId), Is.EqualTo(testCase.ExpectedRefillableSolution.VolumeOfEvaporable)); + Assert.That(VolumeOfPrototypeInComposition(refillableComposition, NonEvaporablePrototypeId), Is.EqualTo(testCase.ExpectedRefillableSolution.VolumeOfNonEvaporable)); + }); + }); + await pair.RunTicksSync(5); + + await pair.CleanReturnAsync(); + } + + private static FixedPoint2 VolumeOfPrototypeInComposition(Dictionary composition, string prototypeId) + { + return composition.TryGetValue(prototypeId, out var value) ? value : FixedPoint2.Zero; + } + + public static readonly TestSolutionCase[] TestCasesToRun = new TestSolutionCase[] + { + // Both empty case + new( + "Both empty - no transfer", + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.Zero), + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.Zero), + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.Zero), + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.Zero) + ), + // Just water cases + new( + "Transfer water to empty refillable", + new TestSolutionReagents(FixedPoint2.New(50), FixedPoint2.Zero), + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.Zero), + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.Zero), + new TestSolutionReagents(FixedPoint2.New(50), FixedPoint2.Zero) + ), + new( + "Transfer water to empty absorbent", + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.Zero), + new TestSolutionReagents(FixedPoint2.New(50), FixedPoint2.Zero), + new TestSolutionReagents(FixedPoint2.New(50), FixedPoint2.Zero), + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.Zero) + ), + new( + "Both partially filled with water while everything fits in absorbent", + new TestSolutionReagents(FixedPoint2.New(50), FixedPoint2.Zero), + new TestSolutionReagents(FixedPoint2.New(40), FixedPoint2.Zero), + new TestSolutionReagents(FixedPoint2.New(90), FixedPoint2.Zero), + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.Zero) + ), + new( + "Both partially filled with water while not everything fits in absorbent", + new TestSolutionReagents(FixedPoint2.New(70), FixedPoint2.Zero), + new TestSolutionReagents(FixedPoint2.New(50), FixedPoint2.Zero), + new TestSolutionReagents(FixedPoint2.New(100), FixedPoint2.Zero), + new TestSolutionReagents(FixedPoint2.New(20), FixedPoint2.Zero) + ), + // Just contaminants cases + new( + "Transfer contaminants to empty refillable", + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.New(50)), + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.Zero), + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.Zero), + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.New(50)) + ), + new( + "Do not transfer contaminants back to empty absorbent", + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.Zero), + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.New(50)), + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.Zero), + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.New(50)) + ), + new( + "Add contaminants to preexisting while everything fits in refillable", + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.New(50)), + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.New(130)), + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.Zero), + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.New(180)) + ), + new( + "Add contaminants to preexisting while not everything fits in refillable", + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.New(90)), + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.New(130)), + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.New(20)), + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.New(200)) + ), + // Mixed: water and contaminants cases + new( + "Transfer just contaminants into empty refillable", + new TestSolutionReagents(FixedPoint2.New(50), FixedPoint2.New(50)), + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.Zero), + new TestSolutionReagents(FixedPoint2.New(50), FixedPoint2.Zero), + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.New(50)) + ), + new( + "Transfer just contaminants into non-empty refillable while everything fits", + new TestSolutionReagents(FixedPoint2.New(50), FixedPoint2.New(50)), + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.New(60)), + new TestSolutionReagents(FixedPoint2.New(50), FixedPoint2.Zero), + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.New(110)) + ), + new( + "Transfer just contaminants into non-empty refillable while not everything fits", + new TestSolutionReagents(FixedPoint2.New(50), FixedPoint2.New(50)), + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.New(170)), + new TestSolutionReagents(FixedPoint2.New(50), FixedPoint2.New(20)), + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.New(200)) + ), + new( + "Transfer just contaminants and absorb water from water refillable", + new TestSolutionReagents(FixedPoint2.New(50), FixedPoint2.New(50)), + new TestSolutionReagents(FixedPoint2.New(70), FixedPoint2.Zero), + new TestSolutionReagents(FixedPoint2.New(100), FixedPoint2.Zero), + new TestSolutionReagents(FixedPoint2.New(20), FixedPoint2.New(50)) + ), + new( + "Transfer just contaminants and absorb water from a full water refillable", + new TestSolutionReagents(FixedPoint2.New(50), FixedPoint2.New(50)), + new TestSolutionReagents(FixedPoint2.New(200), FixedPoint2.Zero), + new TestSolutionReagents(FixedPoint2.New(100), FixedPoint2.Zero), + new TestSolutionReagents(FixedPoint2.New(150), FixedPoint2.New(50)) + ), + new( + "Transfer just contaminants and absorb water from a full mixed refillable", + new TestSolutionReagents(FixedPoint2.New(50), FixedPoint2.New(50)), + new TestSolutionReagents(FixedPoint2.New(100), FixedPoint2.New(100)), + new TestSolutionReagents(FixedPoint2.New(100), FixedPoint2.Zero), + new TestSolutionReagents(FixedPoint2.New(50), FixedPoint2.New(150)) + ), + new( + "Transfer just contaminants and absorb water from a low-water mixed refillable", + new TestSolutionReagents(FixedPoint2.New(50), FixedPoint2.New(50)), + new TestSolutionReagents(FixedPoint2.New(10), FixedPoint2.New(100)), + new TestSolutionReagents(FixedPoint2.New(60), FixedPoint2.Zero), + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.New(150)) + ), + new( + "Contaminants for water exchange", + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.New(100)), + new TestSolutionReagents(FixedPoint2.New(200), FixedPoint2.Zero), + new TestSolutionReagents(FixedPoint2.New(100), FixedPoint2.Zero), + new TestSolutionReagents(FixedPoint2.New(100), FixedPoint2.New(100)) + ) + }; + + public static readonly TestSolutionCase[] TestCasesToRunOnSmallRefillable = new TestSolutionCase[] + { + // Only testing cases where small refillable AvailableVolume makes a difference + new( + "Transfer water to empty refillable", + new TestSolutionReagents(FixedPoint2.New(50), FixedPoint2.Zero), + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.Zero), + new TestSolutionReagents(FixedPoint2.New(30), FixedPoint2.Zero), + new TestSolutionReagents(FixedPoint2.New(20), FixedPoint2.Zero) + ), + new( + "Transfer contaminants to empty refillable", + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.New(50)), + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.Zero), + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.New(30)), + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.New(20)) + ), + new( + "Mixed transfer in limited space", + new TestSolutionReagents(FixedPoint2.New(20), FixedPoint2.New(25)), + new TestSolutionReagents(FixedPoint2.New(10), FixedPoint2.New(5)), + new TestSolutionReagents(FixedPoint2.New(30), FixedPoint2.New(10)), + new TestSolutionReagents(FixedPoint2.Zero, FixedPoint2.New(20)) + ) + }; +} diff --git a/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs b/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs index 55f56ab9d1..a2c89d4e42 100644 --- a/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs +++ b/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs @@ -8,7 +8,6 @@ using Content.Shared.Interaction; using Content.Shared.Timing; using Content.Shared.Weapons.Melee; using Robust.Server.Audio; -using Robust.Server.GameObjects; using Robust.Shared.Map; using Robust.Shared.Prototypes; using Robust.Shared.Utility; @@ -27,7 +26,6 @@ public sealed class AbsorbentSystem : SharedAbsorbentSystem [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SolutionContainerSystem _solutionSystem = default!; [Dependency] private readonly UseDelaySystem _useDelay = default!; - [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; public override void Initialize() { @@ -81,7 +79,7 @@ public sealed class AbsorbentSystem : SharedAbsorbentSystem if (component.Progress.Equals(oldProgress)) return; - Dirty(component); + Dirty(uid, component); } private void OnInteractNoHand(EntityUid uid, AbsorbentComponent component, InteractNoHandEvent args) @@ -102,29 +100,27 @@ public sealed class AbsorbentSystem : SharedAbsorbentSystem args.Handled = true; } - private void Mop(EntityUid user, EntityUid target, EntityUid used, AbsorbentComponent component) + public void Mop(EntityUid user, EntityUid target, EntityUid used, AbsorbentComponent component) { - if (!_solutionSystem.TryGetSolution(used, AbsorbentComponent.SolutionName, out var absorberSoln)) + if (!_solutionSystem.TryGetSolution(used, AbsorbentComponent.SolutionName, out var absorbentSolution)) return; if (_useDelay.ActiveDelay(used)) return; // If it's a puddle try to grab from - if (!TryPuddleInteract(user, used, target, component, absorberSoln)) + if (!TryPuddleInteract(user, used, target, component, absorbentSolution)) { - // Do a transfer, try to get water onto us and transfer anything else to them. - - // If it's anything else transfer to - if (!TryTransferAbsorber(user, used, target, component, absorberSoln)) + // If it's refillable try to transfer + if (!TryRefillableInteract(user, used, target, component, absorbentSolution)) return; } } /// - /// Attempt to fill an absorber from some refillable solution. + /// Logic for an absorbing entity interacting with a refillable. /// - private bool TryTransferAbsorber(EntityUid user, EntityUid used, EntityUid target, AbsorbentComponent component, Solution absorberSoln) + private bool TryRefillableInteract(EntityUid user, EntityUid used, EntityUid target, AbsorbentComponent component, Solution absorbentSolution) { if (!TryComp(target, out RefillableSolutionComponent? refillable)) return false; @@ -134,74 +130,15 @@ public sealed class AbsorbentSystem : SharedAbsorbentSystem if (refillableSolution.Volume <= 0) { - var msg = Loc.GetString("mopping-system-target-container-empty", ("target", target)); - _popups.PopupEntity(msg, user, user); - return false; - } - - // Remove the non-water reagents. - // Remove water on target - // Then do the transfer. - var nonWater = absorberSoln.SplitSolutionWithout(component.PickupAmount, PuddleSystem.EvaporationReagents); - _solutionContainerSystem.UpdateChemicals(used, absorberSoln, true); - - if (nonWater.Volume == FixedPoint2.Zero && absorberSoln.AvailableVolume == FixedPoint2.Zero) - { - _popups.PopupEntity(Loc.GetString("mopping-system-puddle-space", ("used", used)), user, user); - return false; - } - - var transferAmount = component.PickupAmount < absorberSoln.AvailableVolume ? - component.PickupAmount : - absorberSoln.AvailableVolume; - - var water = refillableSolution.SplitSolutionWithOnly(transferAmount, PuddleSystem.EvaporationReagents); - _solutionContainerSystem.UpdateChemicals(target, refillableSolution); - - if (water.Volume == FixedPoint2.Zero && nonWater.Volume == FixedPoint2.Zero) - { - _popups.PopupEntity(Loc.GetString("mopping-system-target-container-empty-water", ("target", target)), user, user); - return false; - } - - if (water.Volume > 0 && !_solutionContainerSystem.TryAddSolution(used, absorberSoln, water)) - { - _popups.PopupEntity(Loc.GetString("mopping-system-full", ("used", used)), used, user); - } - - // Attempt to transfer the full nonWater solution to the bucket. - if (nonWater.Volume > 0) - { - bool fullTransferSuccess = _solutionContainerSystem.TryAddSolution(target, refillableSolution, nonWater); - - // If full transfer was unsuccessful, try a partial transfer. - if (!fullTransferSuccess) - { - var partiallyTransferSolution = nonWater.SplitSolution(refillableSolution.AvailableVolume); - - // Try to transfer the split solution to the bucket. - if (_solutionContainerSystem.TryAddSolution(target, refillableSolution, partiallyTransferSolution)) - { - // The transfer was successful. nonWater now contains the amount that wasn't transferred. - // If there's any leftover nonWater solution, add it back to the mop. - if (nonWater.Volume > 0) - { - absorberSoln.AddSolution(nonWater, _prototype); - _solutionContainerSystem.UpdateChemicals(used, absorberSoln); - } - } - else - { - // If the transfer was unsuccessful, combine both solutions and return them to the mop. - nonWater.AddSolution(partiallyTransferSolution, _prototype); - absorberSoln.AddSolution(nonWater, _prototype); - _solutionContainerSystem.UpdateChemicals(used, absorberSoln); - } - } + // Target empty - only transfer absorbent contents into refillable + if (!TryTransferFromAbsorbentToRefillable(user, used, target, component, absorbentSolution, refillableSolution)) + return false; } else { - _popups.PopupEntity(Loc.GetString("mopping-system-full", ("used", target)), user, user); + // Target non-empty - do a two-way transfer + if (!TryTwoWayAbsorbentRefillableTransfer(user, used, target, component, absorbentSolution, refillableSolution)) + return false; } _audio.PlayPvs(component.TransferSound, target); @@ -209,6 +146,119 @@ public sealed class AbsorbentSystem : SharedAbsorbentSystem return true; } + /// + /// Logic for an transferring solution from absorber to an empty refillable. + /// + private bool TryTransferFromAbsorbentToRefillable( + EntityUid user, + EntityUid used, + EntityUid target, + AbsorbentComponent component, + Solution absorbentSolution, + Solution refillableSolution) + { + if (absorbentSolution.Volume <= 0) + { + _popups.PopupEntity(Loc.GetString("mopping-system-target-container-empty", ("target", target)), user, user); + return false; + } + + var transferAmount = component.PickupAmount < refillableSolution.AvailableVolume ? + component.PickupAmount : + refillableSolution.AvailableVolume; + + if (transferAmount <= 0) + { + _popups.PopupEntity(Loc.GetString("mopping-system-full", ("used", used)), used, user); + return false; + } + + // Prioritize transferring non-evaporatives if absorbent has any + var contaminants = absorbentSolution.SplitSolutionWithout(transferAmount, PuddleSystem.EvaporationReagents); + if (contaminants.Volume > 0) + { + _solutionSystem.UpdateChemicals(used, absorbentSolution, true); + _solutionSystem.TryAddSolution(target, refillableSolution, contaminants); + } + else + { + var evaporatives = absorbentSolution.SplitSolution(transferAmount); + _solutionSystem.UpdateChemicals(used, absorbentSolution, true); + _solutionSystem.TryAddSolution(target, refillableSolution, evaporatives); + } + + return true; + } + + /// + /// Logic for an transferring contaminants to a non-empty refillable & reabsorbing water if any available. + /// + private bool TryTwoWayAbsorbentRefillableTransfer( + EntityUid user, + EntityUid used, + EntityUid target, + AbsorbentComponent component, + Solution absorbentSolution, + Solution refillableSolution) + { + var contaminantsFromAbsorbent = absorbentSolution.SplitSolutionWithout(component.PickupAmount, PuddleSystem.EvaporationReagents); + _solutionSystem.UpdateChemicals(used, absorbentSolution, true); + + if (contaminantsFromAbsorbent.Volume == FixedPoint2.Zero && absorbentSolution.AvailableVolume == FixedPoint2.Zero) + { + // Nothing to transfer to refillable and no room to absorb anything extra + _popups.PopupEntity(Loc.GetString("mopping-system-puddle-space", ("used", used)), user, user); + + // We can return cleanly because nothing was split from absorbent solution + return false; + } + + var waterPulled = component.PickupAmount < absorbentSolution.AvailableVolume ? + component.PickupAmount : + absorbentSolution.AvailableVolume; + + var waterFromRefillable = refillableSolution.SplitSolutionWithOnly(waterPulled, PuddleSystem.EvaporationReagents); + _solutionSystem.UpdateChemicals(target, refillableSolution); + + if (waterFromRefillable.Volume == FixedPoint2.Zero && contaminantsFromAbsorbent.Volume == FixedPoint2.Zero) + { + // Nothing to transfer in either direction + _popups.PopupEntity(Loc.GetString("mopping-system-target-container-empty-water", ("target", target)), user, user); + + // We can return cleanly because nothing was split from refillable solution + return false; + } + + var anyTransferOccurred = false; + + if (waterFromRefillable.Volume > FixedPoint2.Zero) + { + // transfer water to absorbent + _solutionSystem.TryAddSolution(used, absorbentSolution, waterFromRefillable); + anyTransferOccurred = true; + } + + if (contaminantsFromAbsorbent.Volume > 0) + { + if (refillableSolution.AvailableVolume <= 0) + { + _popups.PopupEntity(Loc.GetString("mopping-system-full", ("used", target)), user, user); + } + else + { + // transfer as much contaminants to refillable as will fit + var contaminantsForRefillable = contaminantsFromAbsorbent.SplitSolution(refillableSolution.AvailableVolume); + _solutionSystem.TryAddSolution(target, refillableSolution, contaminantsForRefillable); + anyTransferOccurred = true; + } + + // absorb everything that did not fit in the refillable back by the absorbent + _solutionSystem.TryAddSolution(used, absorbentSolution, contaminantsFromAbsorbent); + } + + return anyTransferOccurred; + } + /// /// Logic for an absorbing entity interacting with a puddle. ///