From ae4863ba8f95a06b7f4060aa5b6d82bc0ade18ec Mon Sep 17 00:00:00 2001 From: rasmus123d <59487370+RasmusKD@users.noreply.github.com> Date: Sun, 16 Aug 2026 14:40:19 +0200 Subject: [PATCH] Round-robin container pulls so one stuck stack cannot stall the pipe With stack-per-move, the piston always pulled the first stack. A stack no output accepts came back as leftovers to the front of the chest and was pulled again every pulse, so everything behind it never moved. The piston now remembers the last pulled slot and continues from the next one, costing an unroutable stack one idle pulse per rotation instead of blocking the pipe forever. Gated by 'round-robin-pull' (default on). The pull clears the exact slot it read instead of using removeItem, which with a mid-inventory start could take a different, equal stack. --- .../sk89q/craftbook/mechanics/pipe/Pipes.java | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/sk89q/craftbook/mechanics/pipe/Pipes.java b/src/main/java/com/sk89q/craftbook/mechanics/pipe/Pipes.java index 3b6c9f4aa..804b571a0 100644 --- a/src/main/java/com/sk89q/craftbook/mechanics/pipe/Pipes.java +++ b/src/main/java/com/sk89q/craftbook/mechanics/pipe/Pipes.java @@ -22,6 +22,7 @@ import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockTypes; import org.bukkit.Bukkit; +import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.Tag; import org.bukkit.block.Block; @@ -36,6 +37,7 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.block.SignChangeEvent; +import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; import org.bukkit.util.Vector; @@ -44,9 +46,11 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Deque; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; public class Pipes extends AbstractCraftBookMechanic { @@ -364,7 +368,25 @@ private void startPipe(Block block, List items, boolean request) { || facType == Material.CRAFTER || facType == Material.DECORATED_POT || Tag.SHULKER_BOXES.isTagged(facType)) { - for (ItemStack stack : ((InventoryHolder) fac.getState()).getInventory().getContents()) { + Inventory sourceInventory = ((InventoryHolder) fac.getState()).getInventory(); + ItemStack[] contents = sourceInventory.getContents(); + int slots = contents.length; + + // Scan starting after the slot pulled last pulse, so a stack no + // output accepts (returned as leftovers) cannot block everything + // behind it. + int startSlot = 0; + Location pullKey = null; + if (pipeRoundRobinPull && pipeStackPerPull && slots > 0) { + pullKey = block.getLocation(); + Integer cursor = pullCursor.get(pullKey); + if (cursor != null) + startSlot = cursor % slots; + } + + for (int offset = 0; offset < slots; offset++) { + int slot = (startSlot + offset) % slots; + ItemStack stack = contents[slot]; if (!ItemUtil.isStackValid(stack)) continue; @@ -373,9 +395,18 @@ private void startPipe(Block block, List items, boolean request) { continue; items.add(stack); - ((InventoryHolder) fac.getState()).getInventory().removeItem(stack); - if (pipeStackPerPull) + // Clear by slot rather than removeItem: with a mid-inventory + // start, removeItem would take the first equal stack, which + // can be a different slot than the one just read. + sourceInventory.setItem(slot, null); + if (pipeStackPerPull) { + if (pullKey != null) { + if (pullCursor.size() > MAX_PULL_CURSORS) + pullCursor.clear(); + pullCursor.put(pullKey, slot + 1); + } break; + } } PipeSuckEvent event = new PipeSuckEvent(block, new ArrayList<>(items), fac); @@ -507,10 +538,15 @@ public void onPipeRequest(PipeRequestEvent event) { } } + /** Last pulled slot per source piston, so pulls resume after it. */ + private final Map pullCursor = new HashMap<>(); + private static final int MAX_PULL_CURSORS = 4096; + private boolean pipesDiagonal; private BlockStateHolder pipeInsulator; private boolean pipeStackPerPull; private boolean pipeRequireSign; + private boolean pipeRoundRobinPull; @Override public void loadConfiguration (YAMLProcessor config, String path) { @@ -526,5 +562,8 @@ public void loadConfiguration (YAMLProcessor config, String path) { config.setComment(path + "require-sign", "Requires pipes to have a [Pipe] sign connected to them. This is the only way to require permissions to make pipes."); pipeRequireSign = config.getBoolean(path + "require-sign", false); + + config.setComment(path + "round-robin-pull", "With stack-per-move, pull container slots in rotation instead of always taking the first stack, so one stack no output accepts cannot block everything behind it."); + pipeRoundRobinPull = config.getBoolean(path + "round-robin-pull", true); } } \ No newline at end of file