diff --git a/EpicHoppers-Plugin/src/main/java/com/songoda/epichoppers/hopper/levels/modules/ModuleSuction.java b/EpicHoppers-Plugin/src/main/java/com/songoda/epichoppers/hopper/levels/modules/ModuleSuction.java index 7c41f444..fbb5c05e 100644 --- a/EpicHoppers-Plugin/src/main/java/com/songoda/epichoppers/hopper/levels/modules/ModuleSuction.java +++ b/EpicHoppers-Plugin/src/main/java/com/songoda/epichoppers/hopper/levels/modules/ModuleSuction.java @@ -15,6 +15,7 @@ import dev.rosewood.rosestacker.api.RoseStackerAPI; import dev.rosewood.rosestacker.stack.StackedItem; import org.bukkit.Bukkit; +import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.entity.EntityType; import org.bukkit.entity.Item; @@ -60,13 +61,34 @@ public void run(Hopper hopper, StorageContainerCache.Cache hopperCache) { return; } - Set itemsToSuck = hopper.getLocation().getWorld().getNearbyEntities(hopper.getLocation().add(0.5, 0.5, 0.5), radius, radius, radius) + Set itemsToSuck = hopper.getLocation().getWorld() + .getNearbyEntities(hopper.getLocation().add(0.5, 0.5, 0.5), radius, radius, radius) .stream() - .filter(entity -> entity.getType() == EntityType.DROPPED_ITEM - && !entity.isDead() - && entity.getTicksLived() >= ((Item) entity).getPickupDelay() - && entity.getLocation().getBlock().getType() != Material.HOPPER) + .filter(entity -> entity.getType() == EntityType.DROPPED_ITEM) .map(Item.class::cast) + .filter(entity -> { + if (entity.isDead() || entity.getLocation().getBlock().getType() == Material.HOPPER) { + return false; + } + + // For WildStacker items, bypass the PickupDelay check since we use their API + if (WILD_STACKER && WildStackerAPI.getStackedItem(entity) != null) { + return true; + } + + // For UltimateStacker items, bypass the PickupDelay check + if (ULTIMATE_STACKER && UltimateStackerApi.getStackedItemManager().isStackedItem(entity)) { + return true; + } + + // For RoseStacker items, bypass the PickupDelay check + if (ROSE_STACKER && RoseStackerAPI.getInstance().getStackedItem(entity) != null) { + return true; + } + + // For normal items, check PickupDelay + return entity.getTicksLived() >= entity.getPickupDelay(); + }) .collect(Collectors.toSet()); if (itemsToSuck.isEmpty()) { @@ -75,31 +97,35 @@ public void run(Hopper hopper, StorageContainerCache.Cache hopperCache) { boolean filterEndpoint = hopper.getFilter().getEndPoint() != null; - Inventory hopperInventory = null; - if (Settings.EMIT_INVENTORYPICKUPITEMEVENT.getBoolean()) { - InventoryHolder inventoryHolder = (InventoryHolder) hopper.getBlock().getState(); - hopperInventory = Bukkit.createInventory(inventoryHolder, InventoryType.HOPPER); - } + // Always create inventory for the suction module to allow plugins to cancel the pickup event + InventoryHolder inventoryHolder = (InventoryHolder) hopper.getBlock().getState(); + Inventory hopperInventory = Bukkit.createInventory(inventoryHolder, InventoryType.HOPPER); for (Item item : itemsToSuck) { ItemStack itemStack = item.getItemStack(); - if (item.getPickupDelay() == 0) { + // Check if this is a stacker plugin item + boolean isStackerItem = (WILD_STACKER && WildStackerAPI.getStackedItem(item) != null) + || (ULTIMATE_STACKER && UltimateStackerApi.getStackedItemManager().isStackedItem(item)) + || (ROSE_STACKER && RoseStackerAPI.getInstance().getStackedItem(item) != null); + + // Skip items with PickupDelay 0 (unless they're stacker items, which we handle via API) + if (!isStackerItem && item.getPickupDelay() == 0) { item.setPickupDelay(25); continue; } if (itemStack.getType().name().contains("SHULKER_BOX")) { - return; + continue; } if (itemStack.hasItemMeta() && itemStack.getItemMeta().hasDisplayName() && itemStack.getItemMeta().getDisplayName().startsWith("***")) { - return; //Compatibility with Shop instance: https://www.spigotmc.org/resources/shop-a-simple-intuitive-shop-instance.9628/ + continue; //Compatibility with Shop instance: https://www.spigotmc.org/resources/shop-a-simple-intuitive-shop-instance.9628/ } if (BLACKLIST.contains(item.getUniqueId())) { - return; + continue; } // respect filter if no endpoint @@ -122,27 +148,52 @@ public void run(Hopper hopper, StorageContainerCache.Cache hopperCache) { } } - if (Settings.EMIT_INVENTORYPICKUPITEMEVENT.getBoolean()) { - hopperInventory.setContents(hopperCache.cachedInventory); - InventoryPickupItemEvent pickupEvent = new InventoryPickupItemEvent(hopperInventory, item); - Bukkit.getPluginManager().callEvent(pickupEvent); - if (pickupEvent.isCancelled()) { + // IMPORTANT: Read the actual item amount BEFORE emitting the event! + // WildStacker (priority HIGHEST) modifies the item during the event, + // so we must capture the correct amount first + int toAdd = getActualItemAmount(item); + + // Always emit the event for suction module to allow any plugin to prevent item pickup + // This is important because suction is an aggressive collection mechanism + hopperInventory.setContents(hopperCache.cachedInventory); + InventoryPickupItemEvent pickupEvent = new InventoryPickupItemEvent(hopperInventory, item); + Bukkit.getPluginManager().callEvent(pickupEvent); + + if (pickupEvent.isCancelled()) { + // Check if this is a protected item (from shops or other plugins) first + // Protected items should always respect the cancellation to maintain compatibility + boolean isProtectedItem = itemStack.hasItemMeta() && ( + itemStack.getItemMeta().hasDisplayName() || + itemStack.getItemMeta().hasLore() || + !itemStack.getItemMeta().getPersistentDataContainer().isEmpty() + ); + + if (isProtectedItem) { + // This item is protected by another plugin - always respect that + continue; + } else if (!isStackerItem) { + // Normal item that's been cancelled - skip it continue; } + // If we get here, it's a stacker item that's not protected, so we bypass the cancellation + // This allows suction to work properly with stacker plugins } // try to add the items to the hopper - int toAdd, added = hopperCache.addAny(itemStack, toAdd = getActualItemAmount(item)); + int added = hopperCache.addAny(itemStack, toAdd); + if (added == 0) { return; } // items added ok! - if (added == toAdd) { + if (added >= toAdd) { + // All items were added, remove the entity item.remove(); } else { - // update the item's total - updateAmount(item, toAdd - added); + // Only some items were added, update the remaining amount + int remaining = toAdd - added; + updateAmount(item, remaining); // wait before trying to add again BLACKLIST.add(item.getUniqueId()); @@ -175,7 +226,21 @@ private void updateAmount(Item item, int amount) { if (ULTIMATE_STACKER) { UltimateStackerApi.getStackedItemManager().updateStack(item, amount); } else if (WILD_STACKER) { - WildStackerAPI.getStackedItem(item).setStackAmount(amount, true); + com.bgsoftware.wildstacker.api.objects.StackedItem stackedItem = WildStackerAPI.getStackedItem(item); + if (stackedItem != null) { + stackedItem.setStackAmount(amount, true); + } else { + // Fallback if item is not tracked by WildStacker + item.getItemStack().setAmount(Math.min(amount, item.getItemStack().getMaxStackSize())); + } + } else if (ROSE_STACKER) { + StackedItem stackedItem = RoseStackerAPI.getInstance().getStackedItem(item); + if (stackedItem != null) { + stackedItem.setStackSize(amount); + } else { + // Fallback if item is not tracked by RoseStacker + item.getItemStack().setAmount(Math.min(amount, item.getItemStack().getMaxStackSize())); + } } else { item.getItemStack().setAmount(Math.min(amount, item.getItemStack().getMaxStackSize())); } diff --git a/EpicHoppers-Plugin/src/main/java/com/songoda/epichoppers/settings/Settings.java b/EpicHoppers-Plugin/src/main/java/com/songoda/epichoppers/settings/Settings.java index fb9482c0..bc22b554 100644 --- a/EpicHoppers-Plugin/src/main/java/com/songoda/epichoppers/settings/Settings.java +++ b/EpicHoppers-Plugin/src/main/java/com/songoda/epichoppers/settings/Settings.java @@ -101,6 +101,11 @@ public class Settings { "However it is a high frequency event and may have an impact on your server performance which is why it is disabled by default.", "If you absolutely need this enable it but be aware of the potential performance impact."); + public static final ConfigSetting DATABASE_TABLE_PREFIX = new ConfigSetting(CONFIG, "Database.Table Prefix", "", + "Prefix for database tables. Useful when running multiple servers sharing the same database.", + "Example: 'survival_' would create tables like 'survival_placed_hoppers' instead of 'placed_hoppers'.", + "Leave empty for no prefix."); + public static final ConfigSetting ECO_ICON = new ConfigSetting(CONFIG, "Interfaces.Economy Icon", "SUNFLOWER"); public static final ConfigSetting XP_ICON = new ConfigSetting(CONFIG, "Interfaces.XP Icon", "EXPERIENCE_BOTTLE"); diff --git a/README.md b/README.md index 0526cf9e..7e810a20 100644 --- a/README.md +++ b/README.md @@ -34,11 +34,11 @@ create a thread over on [our Discord server][Discord invite]. [Plugin page]: https://songoda.com/product/8 -[Plugin wiki]: https://songoda.notion.site/EpicHoppers-3901d0bf2c9a42c995882a9ca599bfbe +[Plugin wiki]: https://wiki.songoda.com/EpicHoppers-1130f108970281cab45ddd6df8d38534 [Discord invite]: https://discord.gg/7TXM8xr2Ng [Discord shield]: https://img.shields.io/discord/1214289374506917889?color=5865F2&label=Discord&logo=discord&logoColor=5865F2 -[Latest version shield]: https://img.shields.io/badge/dynamic/xml?style=flat&color=blue&logo=github&logoColor=white&label=Latest&url=https%3A%2F%2Fraw.githubusercontent.com%2Fcraftaro%2FEpicHoppers%2Fmaster%2Fpom.xml&query=%2F*%5Blocal-name()%3D'project'%5D%2F*%5Blocal-name()%3D'version'%5D +[Latest version shield]: https://img.shields.io/badge/dynamic/xml?style=flat&color=blue&logo=github&logoColor=white&label=Latest&url=https%3A%2F%2Fraw.githubusercontent.com%2FSongoda-Plugins%2FEpicHoppers%2Fmaster%2Fpom.xml&query=%2F*%5Blocal-name()%3D'project'%5D%2F*%5Blocal-name()%3D'version'%5D [bStats page]: https://bstats.org/plugin/bukkit/EpicHoppers/4185 [bStats shield]: https://img.shields.io/bstats/servers/4185?label=Servers diff --git a/pom.xml b/pom.xml index 99ce71ac..b46b3a92 100644 --- a/pom.xml +++ b/pom.xml @@ -45,6 +45,11 @@ https://repo.songoda.com/repository/minecraft-plugins/ + + songoda-minecraft-plugins-release + https://repo.songoda.com/repository/minecraft-plugins-release/ + + SpigotMC https://hub.spigotmc.org/nexus/content/repositories/snapshots/