From 90d5430fd3b9fd2c4a54bb97d8715f1e92a73822 Mon Sep 17 00:00:00 2001 From: Kasper <57477964+kasper-maukkonen@users.noreply.github.com> Date: Tue, 5 May 2026 20:34:17 +0300 Subject: [PATCH] Fix for fluid dupe in Electrolosis chamber and output of fluids. --- .../common/network/type/FluidNetwork.java | 91 ++++++++----------- .../machines/TileElectrolosisChamber.java | 56 ++++++++++-- 2 files changed, 90 insertions(+), 57 deletions(-) diff --git a/src/main/java/electrodynamics/common/network/type/FluidNetwork.java b/src/main/java/electrodynamics/common/network/type/FluidNetwork.java index e1c24f04a..57bab4369 100644 --- a/src/main/java/electrodynamics/common/network/type/FluidNetwork.java +++ b/src/main/java/electrodynamics/common/network/type/FluidNetwork.java @@ -80,13 +80,15 @@ public FluidStack emit(FluidStack inserted, ArrayList ignored, bool return FluidStack.EMPTY; } - // This algorithm is not perfect, but it helps deal with tiles that do not accept the full amount allotted to them + // Greedy distribution with a running remainder. The previous implementation + // pre-divided the available amount across acceptors using truncating integer + // division, so when amount < acceptors * connections the per-share rounded to + // 0 and the trailing 1..N mB never moved, leaving residual fluid in upstream + // tanks indefinitely. Offering the full remainder to each acceptor in turn + // preserves throughput on evenly-divisible amounts while correctly delivering + // the remainder. - FluidStack perTile, prePerTile, perConnection, prePerConnection; - - int size = availableAcceptors.size(); - - int connectionsSize, amtTaken, takenAmt; + int amtTaken; HashSet connections; @@ -98,34 +100,27 @@ public FluidStack emit(FluidStack inserted, ArrayList ignored, bool continue; } - perTile = new FluidStack(initial.getFluid(), (int) ((double) initial.getAmount() / (double) size)); - prePerTile = perTile.copy(); + if (initial.isEmpty()) { + break; + } connections = acceptorInputMap.getOrDefault(tile, new HashSet<>()); - connectionsSize = connections.size(); - for (Direction dir : connections) { - perConnection = new FluidStack(initial.getFluid(), (int) ((double) perTile.getAmount() / (double) connectionsSize)); - prePerConnection = perConnection.copy(); - - amtTaken = FluidUtilities.receiveFluid(tile, dir, perConnection, false); + if (initial.isEmpty()) { + break; + } - perConnection.shrink(amtTaken); + FluidStack offer = new FluidStack(initial.getFluid(), initial.getAmount()); - perTile.shrink(prePerConnection.getAmount() - perConnection.getAmount()); + amtTaken = FluidUtilities.receiveFluid(tile, dir, offer, false); - connectionsSize--; + if (amtTaken > 0) { + initial.shrink(amtTaken); + taken.grow(amtTaken); + } } - - takenAmt = prePerTile.getAmount() - perTile.getAmount(); - - initial.shrink(takenAmt); - - taken.grow(takenAmt); - - size--; } return taken; @@ -179,52 +174,46 @@ private Pair> emitToPumpSet(FluidStack transf FluidStack initial = transfer.copy(); FluidStack taken = new FluidStack(transfer.getFluid(), 0); - FluidStack perTile, prePerTile, perConnection, prePerConnection; + // Greedy distribution; same fix as in emit(). See comment there. HashSet filledPumps = new HashSet<>(); - int size = recievingTiles.size(); - - int connectionsSize, amtTaken, takenAmt; + int amtTaken; HashSet connections; for (TileFluidPipePump tile : recievingTiles) { if (!tile.isPowered() || ignored.contains(tile)) { - size--; continue; } - perTile = new FluidStack(initial.getFluid(), initial.getAmount() / size); - prePerTile = perTile.copy(); + if (initial.isEmpty()) { + break; + } - connections = acceptorInputMap.getOrDefault(tile, new HashSet<>()); + int beforeAmount = initial.getAmount(); - connectionsSize = connections.size(); + connections = acceptorInputMap.getOrDefault(tile, new HashSet<>()); for (Direction dir : connections) { - perConnection = new FluidStack(initial.getFluid(), perTile.getAmount() / connectionsSize); - prePerConnection = perConnection.copy(); + if (initial.isEmpty()) { + break; + } - amtTaken = FluidUtilities.receiveFluid(tile, dir, perConnection, false); + FluidStack offer = new FluidStack(initial.getFluid(), initial.getAmount()); - perConnection.shrink(amtTaken); + amtTaken = FluidUtilities.receiveFluid(tile, dir, offer, false); - perTile.shrink(prePerConnection.getAmount() - perConnection.getAmount()); - - connectionsSize--; + if (amtTaken > 0) { + initial.shrink(amtTaken); + taken.grow(amtTaken); + } } - takenAmt = prePerTile.getAmount() - perTile.getAmount(); - - initial.shrink(takenAmt); - - taken.grow(takenAmt); - - filledPumps.add(tile); - - size--; + if (initial.getAmount() < beforeAmount) { + filledPumps.add(tile); + } } return Pair.of(taken, filledPumps); @@ -297,4 +286,4 @@ public FluidNetwork createInstanceConductor(Set conductors return new FluidNetwork(conductors); } -} +} \ No newline at end of file diff --git a/src/main/java/electrodynamics/common/tile/machines/TileElectrolosisChamber.java b/src/main/java/electrodynamics/common/tile/machines/TileElectrolosisChamber.java index 68d0277d1..74ab8d538 100644 --- a/src/main/java/electrodynamics/common/tile/machines/TileElectrolosisChamber.java +++ b/src/main/java/electrodynamics/common/tile/machines/TileElectrolosisChamber.java @@ -144,6 +144,18 @@ public void tickServer(ComponentTickable tickable) { int amtToProcess = Math.min(room, processAmount.getValue()); + // Clamp to the actual amount available in the input tank. drain() already caps + // to whatever's stored, but the matching fill() below uses amtToProcess directly, + // so without this clamp a partially-filled input tank produces strictly more + // output fluid than was consumed (e.g. 1 mB in -> processAmount mB out at high + // energy levels). This is most visible with intermittent upstream flow. + amtToProcess = Math.min(amtToProcess, fluidHandler.getInputTanks()[0].getFluidAmount()); + + if (amtToProcess <= 0) { + isActive.setValue(false); + return; + } + electro.setJoulesStored(0); isActive.setValue(true); @@ -171,24 +183,52 @@ private static boolean testRecipe(ElectrolosisChamberRecipe recipe, FluidTank[] return false; } + /** + * Index of the fluid-output slave port in the multiblock layout + * ({@code data/electrodynamics/voltaic/multiblock/electrolosischamber.json}). + */ + private static final int FLUID_OUT_SLAVE_INDEX = 39; + private void outputToPipe() { + if (level == null || level.isClientSide()) { + return; + } + + if (!isFormed.getValue()) { + return; + } + ComponentFluidHandlerMulti component = getComponent(IComponentType.FluidHandler); Direction[] outputDirections = component.outputDirections; Direction facing = getFacing(); + // Resolve the fluid-out port's actual world position from slavePositions instead + // of guessing with a fixed (2, 0, 2) offset. The fixed offset only matched the + // port's location for east-facing builds; for the other three facings the + // neighbour lookup hit empty / wrong blocks and no fluid was ever output. + @SuppressWarnings("unchecked") + List positions = (List) slavePositions.getValue(); + if (positions == null || positions.size() <= FLUID_OUT_SLAVE_INDEX) { + return; + } + BlockPos portPos = positions.get(FLUID_OUT_SLAVE_INDEX); + if (portPos == null) { + return; + } + for (Direction relative : outputDirections) { Direction direction = BlockEntityUtils.getRelativeSide(facing, relative); - BlockEntity faceTile = getLevel().getBlockEntity(getBlockPos().relative(direction).offset(2, 0, 2)); + BlockEntity faceTile = level.getBlockEntity(portPos.relative(direction)); if (faceTile == null) { continue; } - IFluidHandler handler = getLevel().getCapability(Capabilities.FluidHandler.BLOCK, faceTile.getBlockPos(), + IFluidHandler handler = level.getCapability(Capabilities.FluidHandler.BLOCK, faceTile.getBlockPos(), faceTile.getBlockState(), faceTile, direction.getOpposite()); if (handler == null) { @@ -199,11 +239,15 @@ private void outputToPipe() { FluidStack tankFluid = fluidTank.getFluid(); - int amtAccepted = handler.fill(tankFluid, IFluidHandler.FluidAction.EXECUTE); + if (tankFluid.isEmpty()) { + continue; + } - FluidStack taken = new FluidStack(tankFluid.getFluid(), amtAccepted); + int amtAccepted = handler.fill(tankFluid, IFluidHandler.FluidAction.EXECUTE); - fluidTank.drain(taken, IFluidHandler.FluidAction.EXECUTE); + if (amtAccepted > 0) { + fluidTank.drain(new FluidStack(tankFluid.getFluid(), amtAccepted), IFluidHandler.FluidAction.EXECUTE); + } } } } @@ -276,4 +320,4 @@ public ResourceLocation getMultiblockId() { public ResourceKey getResourceKey() { return RESOURCE_KEY; } -} +} \ No newline at end of file