diff --git a/Content.Server/_Starlight/Plumbing/EntitySystems/PlumbingFilterSystem.cs b/Content.Server/_Starlight/Plumbing/EntitySystems/PlumbingFilterSystem.cs index f2b1c8064ad..45a102a5a0d 100644 --- a/Content.Server/_Starlight/Plumbing/EntitySystems/PlumbingFilterSystem.cs +++ b/Content.Server/_Starlight/Plumbing/EntitySystems/PlumbingFilterSystem.cs @@ -1,4 +1,6 @@ using Content.Server._Starlight.Plumbing.Components; +using Content.Server._Starlight.Plumbing.Nodes; +using Content.Shared.NodeContainer; using Content.Server.Popups; using Content.Server.UserInterface; using Content.Shared._Starlight.Plumbing; @@ -27,6 +29,9 @@ public sealed class PlumbingFilterSystem : EntitySystem [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly SharedSolutionContainerSystem _solutionSystem = default!; + [Dependency] private readonly PlumbingPullSystem _pullSystem = default!; + public override void Initialize() { @@ -38,6 +43,54 @@ public override void Initialize() SubscribeLocalEvent(OnRemoveReagent); SubscribeLocalEvent(OnClear); SubscribeLocalEvent(OnUIOpened); + SubscribeLocalEvent(OnDeviceUpdate); + } + + private void OnDeviceUpdate(Entity ent, ref PlumbingDeviceUpdateEvent args) + { + // Use the existing PlumbingInlet component on the same entity for inlet names and transfer amount + if (!TryComp(ent.Owner, out var inletComp)) + return; + + if (!_solutionSystem.TryGetSolution(ent.Owner, ent.Comp.FilteredSolutionName, out var filteredEnt, out var filteredSol)) + return; + + if (!_solutionSystem.TryGetSolution(ent.Owner, ent.Comp.PassthroughSolutionName, out var passthroughEnt, out var passthroughSol)) + return; + + if (filteredEnt.Value.Comp.Solution.AvailableVolume <= 0 && passthroughEnt.Value.Comp.Solution.AvailableVolume <= 0) + return; + + if (!TryComp(ent.Owner, out var nodeContainer)) + return; + + var remaining = inletComp.TransferAmount; + + foreach (var inletName in inletComp.InletNames) + { + if (remaining <= 0 || filteredEnt.Value.Comp.Solution.AvailableVolume <= 0 && passthroughEnt.Value.Comp.Solution.AvailableVolume <= 0) + break; + + if (!nodeContainer.Nodes.TryGetValue(inletName, out var node)) + continue; + + if (node is not PlumbingNode plumbingNode || plumbingNode.PlumbingNet == null) + continue; + + var roundRobinIndex = inletComp.RoundRobinIndices.GetValueOrDefault(inletName, 0); + var (pulled, nextIndex) = _pullSystem.PullFromNetworkSplit( + ent.Owner, + plumbingNode.PlumbingNet, + filteredEnt.Value, + passthroughEnt.Value, + remaining, + roundRobinIndex, + ent.Comp.Enabled, + ent.Comp.FilteredReagents); + + inletComp.RoundRobinIndices[inletName] = nextIndex; + remaining -= pulled; + } } /// diff --git a/Content.Server/_Starlight/Plumbing/EntitySystems/PlumbingPullSystem.cs b/Content.Server/_Starlight/Plumbing/EntitySystems/PlumbingPullSystem.cs index 895be006a91..bc252eddafb 100644 --- a/Content.Server/_Starlight/Plumbing/EntitySystems/PlumbingPullSystem.cs +++ b/Content.Server/_Starlight/Plumbing/EntitySystems/PlumbingPullSystem.cs @@ -1,6 +1,7 @@ using Content.Server._Starlight.Plumbing.NodeGroups; using Content.Server._Starlight.Plumbing.Nodes; using Content.Shared._Starlight.Plumbing.Components; +using Content.Server.Chemistry.Components; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Chemistry.Reagent; @@ -35,6 +36,73 @@ public override void Initialize() _outletQuery = GetEntityQuery(); } + /// + /// Returns all solution entities that should be considered sources for the given outlet. + /// This handles containerSlotId pointing at a single slot as before, and expands + /// ReagentDispenser storage slots so each stored jug can be pulled from individually. + /// + private List> GetOutletSolutionEntities(EntityUid outletOwner, string nodeName, PlumbingOutletComponent outlet) + { + var res = new List>(); + + // If ContainerSlotId is set, default behavior is to return the entity in that slot. + if (outlet.ContainerSlotId != null) + { + // Special-case: if this outlet owner is a ReagentDispenser, expose all storage slot contents + if (TryComp(outletOwner, out var dispenserComp)) + { + foreach (var slotId in dispenserComp.StorageSlotIds) + { + var containerEntity = _itemSlots.GetItemOrNull(outletOwner, slotId); + if (containerEntity == null) + continue; + + // Try drainable solution (any usable solution on the stored container) + if (_solutionSystem.TryGetDrainableSolution(containerEntity.Value, out var solEntEntity, out var sol)) + { + if (solEntEntity != null) + res.Add(solEntEntity.Value); + } + } + + return res; + } + + // Fallback: original single-slot behavior + var containerEntity2 = _itemSlots.GetItemOrNull(outletOwner, outlet.ContainerSlotId); + if (containerEntity2 == null) + return res; + + var solutionName = outlet.SolutionName; + if (_solutionSystem.TryGetSolution(containerEntity2.Value, solutionName, out var solutionEnt, out _)) + { + if (solutionEnt != null) + res.Add(solutionEnt.Value); + } + + return res; + } + + // No ContainerSlotId: use the outletOwner itself, with plumbing filter handling + var targetEntity = outletOwner; + var solutionNameTop = outlet.SolutionName; + if (targetEntity == outletOwner && TryComp(outletOwner, out var filterComp)) + { + if (nodeName.Equals(filterComp.FilterNodeName, StringComparison.OrdinalIgnoreCase)) + solutionNameTop = filterComp.FilteredSolutionName; + else if (nodeName.Equals(filterComp.PassthroughNodeName, StringComparison.OrdinalIgnoreCase)) + solutionNameTop = filterComp.PassthroughSolutionName; + } + + if (_solutionSystem.TryGetSolution(targetEntity, solutionNameTop, out var solEntTop, out _)) + { + if (solEntTop != null) + res.Add(solEntTop.Value); + } + + return res; + } + /// /// Pulls all allowed reagents from outlets on a plumbing network into a destination solution. /// @@ -59,8 +127,8 @@ public override void Initialize() if (remaining <= 0) return (FixedPoint2.Zero, roundRobinIndex); - // Build list of valid outlets to pull from - var outlets = new List<(PlumbingNode Node, PlumbingOutletComponent Outlet)>(); + // Build list of valid outlets to pull from. Each outlet may produce multiple source solutions. + var outlets = new List<(PlumbingNode Node, PlumbingOutletComponent Outlet, Entity Source)>(); foreach (var node in network.Nodes) { if (node is not PlumbingNode plumbingNode || plumbingNode.Owner == puller) @@ -84,7 +152,12 @@ public override void Initialize() if (!isOutletNode) continue; - outlets.Add((plumbingNode, outlet)); + // Expand to one or more source solutions for this outlet + var solutions = GetOutletSolutionEntities(plumbingNode.Owner, plumbingNode.Name, outlet); + foreach (var sol in solutions) + { + outlets.Add((plumbingNode, outlet, sol)); + } } if (outlets.Count == 0) @@ -99,9 +172,9 @@ public override void Initialize() for (var i = 0; i < outlets.Count && remaining > 0; i++) { var index = (startIndex + i) % outlets.Count; - var (plumbingNode, outlet) = outlets[index]; + var (plumbingNode, outlet, sourceSol) = outlets[index]; - var pulled = PullFromOutlet(puller, plumbingNode.Owner, plumbingNode.Name, outlet, destination, remaining); + var pulled = PullFromOutlet(puller, plumbingNode.Owner, plumbingNode.Name, outlet, destination, remaining, sourceSol); totalPulled += pulled; remaining -= pulled; } @@ -132,7 +205,7 @@ public override void Initialize() && passthroughDestination.Comp.Solution.AvailableVolume <= 0) return (FixedPoint2.Zero, roundRobinIndex); - var outlets = new List<(PlumbingNode Node, PlumbingOutletComponent Outlet)>(); + var outlets = new List<(PlumbingNode Node, PlumbingOutletComponent Outlet, Entity Source)>(); foreach (var node in network.Nodes) { if (node is not PlumbingNode plumbingNode || plumbingNode.Owner == puller) @@ -157,7 +230,11 @@ public override void Initialize() if (!isOutletNode) continue; - outlets.Add((plumbingNode, outlet)); + var solutions = GetOutletSolutionEntities(plumbingNode.Owner, plumbingNode.Name, outlet); + foreach (var sol in solutions) + { + outlets.Add((plumbingNode, outlet, sol)); + } } if (outlets.Count == 0) @@ -169,7 +246,7 @@ public override void Initialize() for (var i = 0; i < outlets.Count && remaining > 0; i++) { var index = (startIndex + i) % outlets.Count; - var (plumbingNode, outlet) = outlets[index]; + var (plumbingNode, outlet, sourceSol) = outlets[index]; var pulled = PullFromOutletSplit( puller, @@ -180,7 +257,8 @@ public override void Initialize() passthroughDestination, remaining, filterEnabled, - filteredReagents); + filteredReagents, + sourceSol); totalPulled += pulled; remaining -= pulled; @@ -253,35 +331,36 @@ public Dictionary PullSpecificReagents( if (!isOutletNode) continue; - if (GetOutletSolution(plumbingNode.Owner, plumbingNode.Name, outlet) is not { } sourceSoln) - continue; - - var available = sourceSoln.Comp.Solution.GetReagentQuantity(new ReagentId(reagentId, null)); - if (available <= 0) - continue; + var solutions = GetOutletSolutionEntities(plumbingNode.Owner, plumbingNode.Name, outlet); + foreach (var sourceSoln in solutions) + { + var available = sourceSoln.Comp.Solution.GetReagentQuantity(new ReagentId(reagentId, null)); + if (available <= 0) + continue; - var attemptEv = new PlumbingPullAttemptEvent(puller, plumbingNode.Name, reagentId); - RaiseLocalEvent(plumbingNode.Owner, ref attemptEv); + var attemptEv = new PlumbingPullAttemptEvent(puller, plumbingNode.Name, reagentId); + RaiseLocalEvent(plumbingNode.Owner, ref attemptEv); - if (attemptEv.Cancelled) - continue; + if (attemptEv.Cancelled) + continue; - var toPull = FixedPoint2.Min(available, stillNeeded); - toPull = FixedPoint2.Min(toPull, remaining); + var toPull = FixedPoint2.Min(available, stillNeeded); + toPull = FixedPoint2.Min(toPull, remaining); - var actualPulled = _solutionSystem.RemoveReagentAndReturn(sourceSoln, new ReagentId(reagentId, null), toPull); - if (actualPulled > 0) - { - _solutionSystem.TryAddReagent(destination, new ReagentId(reagentId, null), actualPulled, out var actuallyAdded); + var actualPulled = _solutionSystem.RemoveReagentAndReturn(sourceSoln, new ReagentId(reagentId, null), toPull); + if (actualPulled > 0) + { + _solutionSystem.TryAddReagent(destination, new ReagentId(reagentId, null), actualPulled, out var actuallyAdded); - // Return any excess to source to prevent loss - var excess = actualPulled - actuallyAdded; - if (excess > 0) - _solutionSystem.TryAddReagent(sourceSoln, new ReagentId(reagentId, null), excess, out _); + // Return any excess to source to prevent loss + var excess = actualPulled - actuallyAdded; + if (excess > 0) + _solutionSystem.TryAddReagent(sourceSoln, new ReagentId(reagentId, null), excess, out _); - pulled[reagentId] = pulled.GetValueOrDefault(reagentId, FixedPoint2.Zero) + actuallyAdded; - stillNeeded -= actuallyAdded; - remaining -= actuallyAdded; + pulled[reagentId] = pulled.GetValueOrDefault(reagentId, FixedPoint2.Zero) + actuallyAdded; + stillNeeded -= actuallyAdded; + remaining -= actuallyAdded; + } } } } @@ -298,12 +377,15 @@ private FixedPoint2 PullFromOutlet( string nodeName, PlumbingOutletComponent outlet, Entity destination, - FixedPoint2 maxAmount) + FixedPoint2 maxAmount, + Entity? sourceSolOverride = null) { - if (GetOutletSolution(sourceOwner, nodeName, outlet) is not { } sourceSoln) + Entity? sourceSoln = sourceSolOverride ?? GetOutletSolution(sourceOwner, nodeName, outlet); + if (sourceSoln is null) return FixedPoint2.Zero; - var sourceSolution = sourceSoln.Comp.Solution; + var sourceSolNonNull = sourceSoln.Value; + var sourceSolution = sourceSolNonNull.Comp.Solution; if (sourceSolution.Volume <= 0) return FixedPoint2.Zero; @@ -351,7 +433,7 @@ private FixedPoint2 PullFromOutlet( if (toPull <= 0) continue; - var pulled = _solutionSystem.RemoveReagentAndReturn(sourceSoln, reagent, toPull); + var pulled = _solutionSystem.RemoveReagentAndReturn(sourceSolNonNull, reagent, toPull); if (pulled > 0) { _solutionSystem.TryAddReagent(destination, reagent, pulled, out var actuallyAdded); @@ -359,7 +441,7 @@ private FixedPoint2 PullFromOutlet( // Return any excess to source to prevent loss var excess = pulled - actuallyAdded; if (excess > 0) - _solutionSystem.TryAddReagent(sourceSoln, reagent, excess, out _); + _solutionSystem.TryAddReagent(sourceSolNonNull, reagent, excess, out _); totalPulled += actuallyAdded; remaining -= actuallyAdded; @@ -381,12 +463,15 @@ private FixedPoint2 PullFromOutletSplit( Entity passthroughDestination, FixedPoint2 maxAmount, bool filterEnabled, - HashSet> filteredReagents) + HashSet> filteredReagents, + Entity? sourceSolOverride = null) { - if (GetOutletSolution(sourceOwner, nodeName, outlet) is not { } sourceSoln) + Entity? sourceSoln = sourceSolOverride ?? GetOutletSolution(sourceOwner, nodeName, outlet); + if (sourceSoln is null) return FixedPoint2.Zero; - var sourceSolution = sourceSoln.Comp.Solution; + var sourceSolNonNull = sourceSoln.Value; + var sourceSolution = sourceSolNonNull.Comp.Solution; if (sourceSolution.Volume <= 0) return FixedPoint2.Zero; @@ -435,7 +520,7 @@ private FixedPoint2 PullFromOutletSplit( if (toPull <= 0) continue; - var pulled = _solutionSystem.RemoveReagentAndReturn(sourceSoln, reagent, toPull); + var pulled = _solutionSystem.RemoveReagentAndReturn(sourceSolNonNull, reagent, toPull); if (pulled <= 0) continue; @@ -443,7 +528,7 @@ private FixedPoint2 PullFromOutletSplit( var excess = pulled - actuallyAdded; if (excess > 0) - _solutionSystem.TryAddReagent(sourceSoln, reagent, excess, out _); + _solutionSystem.TryAddReagent(sourceSolNonNull, reagent, excess, out _); totalPulled += actuallyAdded; remaining -= actuallyAdded;