From 16de01b728046f87b388d85adebae3b26df87b17 Mon Sep 17 00:00:00 2001 From: Kiko-Dev-Tech Date: Wed, 30 Jul 2025 03:26:22 +0200 Subject: [PATCH 1/5] Add Regex Filter, Item Rate, Support for Minecarts --- README.md | 10 ++ pom.xml | 29 +++- .../earthmc/hopperfilter/HopperFilter.java | 22 ++- .../command/HopperFilterCommand.java | 34 +++++ .../listener/InventoryActionListener.java | 140 +++++++++++++----- src/main/resources/config.yml | 3 + src/main/resources/paper.yml | 4 + src/main/resources/plugin.yml | 13 +- 8 files changed, 209 insertions(+), 46 deletions(-) create mode 100644 src/main/java/net/earthmc/hopperfilter/command/HopperFilterCommand.java create mode 100644 src/main/resources/config.yml create mode 100644 src/main/resources/paper.yml diff --git a/README.md b/README.md index 36cc92d..68aa47e 100644 --- a/README.md +++ b/README.md @@ -3,5 +3,15 @@ Eliminate complicated hopper setups with this little plugin. Filter items throug HopperFilter has been designed from the ground up to be as performant as possible. Any pull requests improving performance are welcome. +# Extra Feature +- Regex Syntax support with "r:{regex}" +
e.g. r:^(diamond|emerald|iron_ingot|gold_ingot|netherite_ingot|netherite_scrap|lapis_lazuli|quartz|amethyst_shard|copper_ingot)$ +
This filters for diamond, emeralds etc. +
To write regex you can use [Regex101](https://regex101.com) +- Config file /plugins/HopperFilter/config.yml +- Edit move rate for all hopper types (via config) +- Now move item from every slot in the inventory if it matches + ## Credits Thanks to LiveOverflow for the inspiration for this plugin in [this](https://youtu.be/Gi2PPBCEHuM?t=224) video. +
Contributor: [Kiko](https://github.com/Kiko-Dev-Tech) (Extra Features) \ No newline at end of file diff --git a/pom.xml b/pom.xml index af57489..2556a79 100644 --- a/pom.xml +++ b/pom.xml @@ -6,18 +6,35 @@ net.earthmc HopperFilter - 0.3.7 + 0.4.1 jar HopperFilter - Name hoppers. Filter items. + Name hoppers. Filter items. Now with Regex Filter! - 17 - UTF-8 - + 17 + UTF-8 + https://earthmc.net - + + + jwkerr + Fruitloopins + https://github.com/jwkerr + + creator + + + + kiko + Kiko + https://github.com/Kiko-Dev-Tech + + contributor(Regex Filer, rate config and optimization) + + + diff --git a/src/main/java/net/earthmc/hopperfilter/HopperFilter.java b/src/main/java/net/earthmc/hopperfilter/HopperFilter.java index bd8c7fd..abb89c2 100644 --- a/src/main/java/net/earthmc/hopperfilter/HopperFilter.java +++ b/src/main/java/net/earthmc/hopperfilter/HopperFilter.java @@ -1,5 +1,6 @@ package net.earthmc.hopperfilter; +import net.earthmc.hopperfilter.command.HopperFilterCommand; import net.earthmc.hopperfilter.listener.HopperRenameListener; import net.earthmc.hopperfilter.listener.InventoryActionListener; import org.bukkit.event.Listener; @@ -8,17 +9,34 @@ public final class HopperFilter extends JavaPlugin { private static HopperFilter instance; - + public int itemsPerTransfer = 1; @Override public void onEnable() { instance = this; registerListeners( new HopperRenameListener(), - new InventoryActionListener() + new InventoryActionListener(this) ); + saveDefaultConfig(); // Copies config.yml to plugin folder if not present + reloadConfig(); // Loads or reloads config from disk + loadSettings(); + + //add Reload Command for config + this.getCommand("hopperfilter").setExecutor(new HopperFilterCommand(this)); + int itemsPerTransfer = getConfig().getInt("transfer.items-per-transfer", 1); + getLogger().info("Transfer rate set to: " + itemsPerTransfer + " items per 8 ticks."); + } + + public void loadSettings() { + itemsPerTransfer = getConfig().getInt("transfer.items-per-transfer", 1); } + public int getItemsPerTransfer() { + return itemsPerTransfer; + } + + private void registerListeners(Listener... listeners) { for (Listener listener : listeners) { getServer().getPluginManager().registerEvents(listener, this); diff --git a/src/main/java/net/earthmc/hopperfilter/command/HopperFilterCommand.java b/src/main/java/net/earthmc/hopperfilter/command/HopperFilterCommand.java new file mode 100644 index 0000000..717cb54 --- /dev/null +++ b/src/main/java/net/earthmc/hopperfilter/command/HopperFilterCommand.java @@ -0,0 +1,34 @@ +package net.earthmc.hopperfilter.command; + +import net.earthmc.hopperfilter.HopperFilter; +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; + +public class HopperFilterCommand implements CommandExecutor { + + private final HopperFilter plugin; + + public HopperFilterCommand(HopperFilter plugin) { + this.plugin = plugin; + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (args.length == 1 && args[0].equalsIgnoreCase("reload")) { + if (!sender.hasPermission("hopperfilter.reload")) { + sender.sendMessage(ChatColor.RED + "You don't have permission to do that."); + return true; + } + + plugin.reloadConfig(); // Reload config.yml + plugin.loadSettings(); + sender.sendMessage(ChatColor.GREEN + "HopperFilter config reloaded."); + return true; + } + + sender.sendMessage(ChatColor.YELLOW + "Usage: /" + label + " reload"); + return true; + } +} diff --git a/src/main/java/net/earthmc/hopperfilter/listener/InventoryActionListener.java b/src/main/java/net/earthmc/hopperfilter/listener/InventoryActionListener.java index 3f19a5b..a40916e 100644 --- a/src/main/java/net/earthmc/hopperfilter/listener/InventoryActionListener.java +++ b/src/main/java/net/earthmc/hopperfilter/listener/InventoryActionListener.java @@ -1,5 +1,6 @@ package net.earthmc.hopperfilter.listener; +import net.earthmc.hopperfilter.HopperFilter; import net.earthmc.hopperfilter.util.ContainerUtil; import net.earthmc.hopperfilter.util.PatternUtil; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; @@ -9,8 +10,10 @@ import org.bukkit.block.BlockFace; import org.bukkit.block.Hopper; import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; import org.bukkit.entity.minecart.HopperMinecart; import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.inventory.InventoryMoveItemEvent; import org.bukkit.event.inventory.InventoryPickupItemEvent; @@ -24,46 +27,95 @@ import org.bukkit.potion.PotionEffectType; import org.bukkit.potion.PotionType; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.regex.PatternSyntaxException; public class InventoryActionListener implements Listener { - @EventHandler + private final HopperFilter plugin; + + public InventoryActionListener(HopperFilter plugin) { + super(); + this.plugin = plugin; + } + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onInventoryMoveItem(final InventoryMoveItemEvent event) { + InventoryHolder sourceHolder = event.getSource().getHolder(false); + InventoryHolder destHolder = event.getDestination().getHolder(false); + + if (!isHopperOrMinecart(sourceHolder) && !isHopperOrMinecart(destHolder)) return; + + event.setCancelled(true); // cancel default behavior + Bukkit.getScheduler().runTaskLater(plugin, () -> moveItem(event), 1L); + } + + private void moveItem(InventoryMoveItemEvent event) { + final Inventory source = event.getSource(); final Inventory destination = event.getDestination(); - if (!destination.getType().equals(InventoryType.HOPPER)) return; - final ItemStack item = event.getItem(); - final InventoryHolder holder = destination.getHolder(false); + final InventoryHolder sourceHolder = source.getHolder(false); + final InventoryHolder destHolder = destination.getHolder(false); - String hopperName; - if (holder instanceof final Hopper hopper) { - hopperName = PatternUtil.serialiseComponent(hopper.customName()); - } else if (holder instanceof final HopperMinecart hopperMinecart) { - hopperName = PatternUtil.serialiseComponent(hopperMinecart.customName()); + String filterName = null; + + boolean isDefaultHopper = false; + // Prefer filter on source (for output) + if (isHopperWithFilter(sourceHolder)) { + filterName = getCustomName(sourceHolder); + } + // Fallback: check destination (for input filters) + else if (isHopperWithFilter(destHolder)) { + filterName = getCustomName(destHolder); } else { - return; + isDefaultHopper = true; } - if (!canItemPassHopper(hopperName, item)) { - event.setCancelled(true); - return; + for (int i = 0; i < source.getSize(); i++) { + ItemStack stack = source.getItem(i); + if (stack == null || stack.getType().isAir()) continue; + + if (!canItemPassHopper(filterName, stack) && !isDefaultHopper) continue; + + int maxToMove = Math.min(plugin.getItemsPerTransfer(), stack.getAmount()); + ItemStack toMove = stack.clone(); + toMove.setAmount(maxToMove); + + Map leftovers = destination.addItem(toMove); + int moved = maxToMove - leftovers.values().stream() + .mapToInt(ItemStack::getAmount) + .sum(); + + if (moved > 0) { + stack.setAmount(stack.getAmount() - moved); + source.setItem(i, stack.getAmount() > 0 ? stack : null); + } + + break; // Only move one stack } + } - // Checks below only matter for hopper blocks - if (!(holder instanceof Hopper hopper)) return; - // If there was a filter on this hopper we don't need to check for a more suitable hopper since this one is specifically filtering for this item - if (hopperName != null) return; + private boolean isHopperOrMinecart(InventoryHolder holder) { + return holder instanceof Hopper + || holder instanceof HopperMinecart + || holder instanceof org.bukkit.entity.minecart.StorageMinecart; + } - final Inventory source = event.getSource(); + private boolean isHopperWithFilter(InventoryHolder holder) { + if (holder instanceof Hopper h) return h.customName() != null; + if (holder instanceof HopperMinecart h) return h.customName() != null; + return false; + } - // If the item can pass, but there is a more suitable hopper with a filter we do this - if (shouldCancelDueToMoreSuitableHopper(source, hopper, item)) event.setCancelled(true); + private String getCustomName(InventoryHolder holder) { + if (holder instanceof Hopper h) return PatternUtil.serialiseComponent(h.customName()); + if (holder instanceof HopperMinecart h) return PatternUtil.serialiseComponent(h.customName()); + return null; } - @EventHandler + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onInventoryPickupItem(final InventoryPickupItemEvent event) { final Inventory inventory = event.getInventory(); if (!inventory.getType().equals(InventoryType.HOPPER)) return; @@ -125,6 +177,9 @@ private boolean shouldCancelDueToMoreSuitableHopper(final Inventory source, fina private boolean canItemPassHopper(final String hopperName, final ItemStack item) { if (hopperName == null) return true; + if (hopperName.startsWith("r:")) { + return filterByRegex(hopperName, item); + } nextCondition: for (final String condition : hopperName.split(",")) { nextAnd: for (final String andString : condition.split("&")) { for (final String orString : andString.split("\\|")) { @@ -139,36 +194,49 @@ private boolean canItemPassHopper(final String hopperName, final ItemStack item) return false; } - private boolean canItemPassPattern(final String pattern, final ItemStack item) { + /** Add Regex Support by Kiko (https://github.com/Kiko-Dev-Tech) + * + * @param pattern + * @param item + * @return + */ + private boolean filterByRegex(final String pattern, final ItemStack item) { final String itemName = item.getType().getKey().getKey(); + if (pattern.isEmpty() || pattern.equals(itemName)) return true; + final String regex = pattern.substring(2); + try { + return itemName.matches(regex); + } catch (PatternSyntaxException e) { + Bukkit.getLogger().warning("Invalid regex pattern in hopper filter: " + regex); + return false; + } + } + private boolean canItemPassPattern(final String pattern, final ItemStack item) { + final String itemName = item.getType().getKey().getKey(); if (pattern.isEmpty() || pattern.equals(itemName)) return true; - final char prefix = pattern.charAt(0); // The character at the start of the pattern - final String string = pattern.substring(1); // Anything after the prefix + final char prefix = pattern.charAt(0); + final String string = pattern.substring(1); return switch (prefix) { - case '!' -> !canItemPassPattern(string, item); // NOT operator - case '*' -> itemName.contains(string); // Contains specified pattern - case '^' -> itemName.startsWith(string); // Starts with specified pattern - case '$' -> itemName.endsWith(string); // Ends with specified pattern - case '#' -> { // Item has specified tag + case '!' -> !canItemPassPattern(string, item); + case '*' -> itemName.contains(string); + case '^' -> itemName.startsWith(string); + case '$' -> itemName.endsWith(string); + case '#' -> { final NamespacedKey key = NamespacedKey.fromString(string); if (key == null) yield false; - Tag tag = Bukkit.getTag(Tag.REGISTRY_BLOCKS, key, Material.class); if (tag == null) tag = Bukkit.getTag(Tag.REGISTRY_ITEMS, key, Material.class); - yield tag != null && tag.isTagged(item.getType()); } - case '~' -> doesItemHaveSpecifiedPotionEffect(item, string); // Item has specified potion effect - case '+' -> doesItemHaveSpecifiedEnchantment(item, string); // Item has specified enchantment - case '=' -> { // Item has specified name (including renames) + case '~' -> doesItemHaveSpecifiedPotionEffect(item, string); + case '+' -> doesItemHaveSpecifiedEnchantment(item, string); + case '=' -> { String displayName = PlainTextComponentSerializer.plainText().serialize(item.displayName()) .toLowerCase() .replaceAll(" ", "_"); - displayName = displayName.substring(1, displayName.length() - 1); - yield displayName.equals(string); } default -> false; diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..d0745ec --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,3 @@ + +transfer: + items-per-transfer: 1 #how many items are moved every 8 ticks \ No newline at end of file diff --git a/src/main/resources/paper.yml b/src/main/resources/paper.yml new file mode 100644 index 0000000..ca08ee2 --- /dev/null +++ b/src/main/resources/paper.yml @@ -0,0 +1,4 @@ +hopper: + ignore_hopper_move_events: false # must be false + cooldown: 8 + tick-inactive: true \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index eef23c8..5f19891 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -2,7 +2,16 @@ name: HopperFilter version: "${project.version}" main: net.earthmc.hopperfilter.HopperFilter api-version: "1.20" -authors: [Fruitloopins] -description: Name hoppers. Filter items. +authors: [Fruitloopins, Kiko] +description: Name hoppers. Filter items. Now with Regex. website: https://earthmc.net folia-supported: true +commands: + hopperfilter: + description: Reload the HopperFilter plugin + usage: / reload + permission: hopperfilter.reload +permissions: + hopperfilter.reload: + description: Allows reloading the plugin + default: op \ No newline at end of file From 7630438fa74abf8afe90f2a4b7cf8badd4553ee0 Mon Sep 17 00:00:00 2001 From: Kiko-Dev-Tech Date: Wed, 30 Jul 2025 03:40:58 +0200 Subject: [PATCH 2/5] Fix version number --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2556a79..93558a2 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ net.earthmc HopperFilter - 0.4.1 + 0.4.0 jar HopperFilter From 03a59d5c3df3b83599b22a6cb3c5b637a94b6ad0 Mon Sep 17 00:00:00 2001 From: Kiko-Dev-Tech Date: Wed, 30 Jul 2025 15:39:51 +0200 Subject: [PATCH 3/5] Fix Furnace Bug --- pom.xml | 2 +- .../listener/InventoryActionListener.java | 40 +++++++++++++++++-- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 93558a2..2556a79 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ net.earthmc HopperFilter - 0.4.0 + 0.4.1 jar HopperFilter diff --git a/src/main/java/net/earthmc/hopperfilter/listener/InventoryActionListener.java b/src/main/java/net/earthmc/hopperfilter/listener/InventoryActionListener.java index a40916e..d1a816d 100644 --- a/src/main/java/net/earthmc/hopperfilter/listener/InventoryActionListener.java +++ b/src/main/java/net/earthmc/hopperfilter/listener/InventoryActionListener.java @@ -6,9 +6,7 @@ import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import org.apache.commons.lang3.tuple.Pair; import org.bukkit.*; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.block.Hopper; +import org.bukkit.block.*; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.Player; import org.bukkit.entity.minecart.HopperMinecart; @@ -47,10 +45,31 @@ public void onInventoryMoveItem(final InventoryMoveItemEvent event) { if (!isHopperOrMinecart(sourceHolder) && !isHopperOrMinecart(destHolder)) return; + if (isFurnace(sourceHolder)) { + String hopperName = getFilterName(event.getDestination().getHolder(false)); + if (hopperName != null && !canItemPassHopper(hopperName, event.getItem())) { + event.setCancelled(true); // deny pulling this item + } + return; // let vanilla handle transfer rate + } event.setCancelled(true); // cancel default behavior Bukkit.getScheduler().runTaskLater(plugin, () -> moveItem(event), 1L); } + private String getFilterName(InventoryHolder holder) { + if (holder instanceof Hopper h && h.customName() != null) + return PatternUtil.serialiseComponent(h.customName()); + if (holder instanceof HopperMinecart h && h.customName() != null) + return PatternUtil.serialiseComponent(h.customName()); + return null; + } + + private boolean isFurnace(InventoryHolder holder) { + return holder instanceof Furnace + || holder instanceof BlastFurnace + || holder instanceof Smoker; + } + private void moveItem(InventoryMoveItemEvent event) { final Inventory source = event.getSource(); final Inventory destination = event.getDestination(); @@ -60,6 +79,15 @@ private void moveItem(InventoryMoveItemEvent event) { String filterName = null; + if (sourceHolder instanceof Furnace + || sourceHolder instanceof BlastFurnace + || sourceHolder instanceof Smoker + || sourceHolder instanceof Campfire + || sourceHolder instanceof Container + && source.getType().toString().toLowerCase().contains("furnace")) { + return; // skip custom logic + } + boolean isDefaultHopper = false; // Prefer filter on source (for output) if (isHopperWithFilter(sourceHolder)) { @@ -73,6 +101,12 @@ else if (isHopperWithFilter(destHolder)) { } for (int i = 0; i < source.getSize(); i++) { + if (sourceHolder instanceof Furnace) { + ItemStack output = source.getItem(2); + if (output != null && !output.getType().isAir()) { + return; // skip pulling from furnace output + } + } ItemStack stack = source.getItem(i); if (stack == null || stack.getType().isAir()) continue; From e51e025d6b2e225542ce21ab20de4fc42e7698a2 Mon Sep 17 00:00:00 2001 From: Kiko-Dev-Tech Date: Wed, 30 Jul 2025 19:11:28 +0200 Subject: [PATCH 4/5] Fix Another Furnace Bug --- pom.xml | 2 +- .../listener/InventoryActionListener.java | 80 ++++++++++++------- 2 files changed, 53 insertions(+), 29 deletions(-) diff --git a/pom.xml b/pom.xml index 2556a79..25e242f 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ net.earthmc HopperFilter - 0.4.1 + 0.4.2 jar HopperFilter diff --git a/src/main/java/net/earthmc/hopperfilter/listener/InventoryActionListener.java b/src/main/java/net/earthmc/hopperfilter/listener/InventoryActionListener.java index d1a816d..2092d8b 100644 --- a/src/main/java/net/earthmc/hopperfilter/listener/InventoryActionListener.java +++ b/src/main/java/net/earthmc/hopperfilter/listener/InventoryActionListener.java @@ -7,6 +7,7 @@ import org.apache.commons.lang3.tuple.Pair; import org.bukkit.*; import org.bukkit.block.*; +import org.bukkit.block.data.BlockData; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.Player; import org.bukkit.entity.minecart.HopperMinecart; @@ -16,9 +17,7 @@ import org.bukkit.event.inventory.InventoryMoveItemEvent; import org.bukkit.event.inventory.InventoryPickupItemEvent; import org.bukkit.event.inventory.InventoryType; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.InventoryHolder; -import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.*; import org.bukkit.inventory.meta.EnchantmentStorageMeta; import org.bukkit.inventory.meta.PotionMeta; import org.bukkit.potion.PotionEffect; @@ -77,36 +76,18 @@ private void moveItem(InventoryMoveItemEvent event) { final InventoryHolder sourceHolder = source.getHolder(false); final InventoryHolder destHolder = destination.getHolder(false); + boolean isDefaultHopper = false; String filterName = null; - if (sourceHolder instanceof Furnace - || sourceHolder instanceof BlastFurnace - || sourceHolder instanceof Smoker - || sourceHolder instanceof Campfire - || sourceHolder instanceof Container - && source.getType().toString().toLowerCase().contains("furnace")) { - return; // skip custom logic - } - - boolean isDefaultHopper = false; - // Prefer filter on source (for output) if (isHopperWithFilter(sourceHolder)) { filterName = getCustomName(sourceHolder); - } - // Fallback: check destination (for input filters) - else if (isHopperWithFilter(destHolder)) { + } else if (isHopperWithFilter(destHolder)) { filterName = getCustomName(destHolder); } else { isDefaultHopper = true; } for (int i = 0; i < source.getSize(); i++) { - if (sourceHolder instanceof Furnace) { - ItemStack output = source.getItem(2); - if (output != null && !output.getType().isAir()) { - return; // skip pulling from furnace output - } - } ItemStack stack = source.getItem(i); if (stack == null || stack.getType().isAir()) continue; @@ -116,27 +97,70 @@ else if (isHopperWithFilter(destHolder)) { ItemStack toMove = stack.clone(); toMove.setAmount(maxToMove); - Map leftovers = destination.addItem(toMove); - int moved = maxToMove - leftovers.values().stream() - .mapToInt(ItemStack::getAmount) - .sum(); + int moved = 0; + + if (destination instanceof FurnaceInventory furnaceInv) { + Block sourceBlock = getBlockFromHolder(sourceHolder); + Block destBlock = getBlockFromHolder(destHolder); + if (sourceBlock == null || destBlock == null) return; + + BlockFace direction = destBlock.getFace(sourceBlock); + if (direction == null) direction = BlockFace.UP; // fallback: assume from below + + int slot = switch (direction) { + case UP -> 0; // from above = input + case DOWN -> 2; // from below = output (disallowed) + default -> 1; // from side = fuel + }; + + if (slot == 2) return; + if (slot == 0 && !hasFurnaceRecipe(toMove)) return; + if (slot == 1 && !toMove.getType().isFuel()) return; + + ItemStack current = furnaceInv.getItem(slot); + if (current == null || current.getType().isAir()) { + furnaceInv.setItem(slot, toMove); + moved = toMove.getAmount(); + } else if (current.isSimilar(toMove) && current.getAmount() < current.getMaxStackSize()) { + int insertable = Math.min(toMove.getAmount(), current.getMaxStackSize() - current.getAmount()); + current.setAmount(current.getAmount() + insertable); + furnaceInv.setItem(slot, current); + moved = insertable; + } + + } else { + Map leftovers = destination.addItem(toMove); + moved = toMove.getAmount() - leftovers.values().stream().mapToInt(ItemStack::getAmount).sum(); + } if (moved > 0) { stack.setAmount(stack.getAmount() - moved); source.setItem(i, stack.getAmount() > 0 ? stack : null); } - break; // Only move one stack + break; } } + + private boolean isHopperOrMinecart(InventoryHolder holder) { return holder instanceof Hopper || holder instanceof HopperMinecart || holder instanceof org.bukkit.entity.minecart.StorageMinecart; } + private Block getBlockFromHolder(InventoryHolder holder) { + if (holder instanceof BlockState state) return state.getBlock(); + return null; + } + + private boolean hasFurnaceRecipe(ItemStack item) { + return Bukkit.getRecipesFor(item).stream() + .anyMatch(r -> r instanceof FurnaceRecipe); + } + private boolean isHopperWithFilter(InventoryHolder holder) { if (holder instanceof Hopper h) return h.customName() != null; if (holder instanceof HopperMinecart h) return h.customName() != null; From bf0ca791d023ddda2cca640e2a03ff50d63b0c37 Mon Sep 17 00:00:00 2001 From: Kiko-Dev-Tech Date: Wed, 30 Jul 2025 20:38:15 +0200 Subject: [PATCH 5/5] Exclude Crafter --- pom.xml | 2 +- .../earthmc/hopperfilter/listener/InventoryActionListener.java | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 25e242f..c7f377a 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ net.earthmc HopperFilter - 0.4.2 + 0.4.3 jar HopperFilter diff --git a/src/main/java/net/earthmc/hopperfilter/listener/InventoryActionListener.java b/src/main/java/net/earthmc/hopperfilter/listener/InventoryActionListener.java index 2092d8b..1c368e3 100644 --- a/src/main/java/net/earthmc/hopperfilter/listener/InventoryActionListener.java +++ b/src/main/java/net/earthmc/hopperfilter/listener/InventoryActionListener.java @@ -43,6 +43,8 @@ public void onInventoryMoveItem(final InventoryMoveItemEvent event) { InventoryHolder destHolder = event.getDestination().getHolder(false); if (!isHopperOrMinecart(sourceHolder) && !isHopperOrMinecart(destHolder)) return; + //excluded crafter + if (sourceHolder instanceof Crafter || destHolder instanceof Crafter) return; if (isFurnace(sourceHolder)) { String hopperName = getFilterName(event.getDestination().getHolder(false));