From 60e7d8221b6f11ea4bcac4300091a357b559f0ac Mon Sep 17 00:00:00 2001 From: MrSimpleJS <54013002+MrSimpleJS@users.noreply.github.com> Date: Tue, 23 Jun 2026 03:38:02 +0200 Subject: [PATCH 1/5] Add Minecraft 26.2 support, item input, and optional ChestSort/WorldGuard hooks Updated OpenShulker for Minecraft 26.2 while keeping api-version: 1.14 for backward compatibility. Added direct item insertion into shulker boxes via right-click interactions. Added optional ChestSort and WorldGuard integration. Fixed protected-container interaction by no longer uncancelling PlayerInteractEvent. Custom shulker display names are now used as the opened inventory title. Details Paper API updated to 26.2.build.31-alpha. Java target remains Java 8 for old server compatibility. Added softdepend: [ChestSort, WorldGuard]. Added config toggles:Hooks.ChestSort Hooks.WorldGuard ChestSort hook marks virtual shulker inventories as unsortable. WorldGuard hook checks Flags.BUILD before opening/inputting shulkers from protected container inventories. Right-click item input supports:item cursor on shulker box shulker box cursor on item Prevents shulker-in-shulker insertion. --- README.md | 8 +- pom.xml | 13 +++- .../me/entity303/openshulker/OpenShulker.java | 4 + .../openshulker/hooks/ChestSortHook.java | 23 ++++++ .../openshulker/hooks/WorldGuardHook.java | 37 ++++++++++ .../listener/ShulkerOpenCloseListener.java | 73 ++++++++++++++++--- .../openshulker/util/ShulkerActions.java | 59 ++++++++++++++- src/main/resources/config.yml | 5 +- src/main/resources/plugin.yml | 3 +- 9 files changed, 205 insertions(+), 20 deletions(-) create mode 100644 src/main/java/me/entity303/openshulker/hooks/ChestSortHook.java create mode 100644 src/main/java/me/entity303/openshulker/hooks/WorldGuardHook.java diff --git a/README.md b/README.md index b417e44..fca7e93 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # OpenShulker -OpenShulker is a plugin for 1.14 to 1.20.1 that makes Shulkerboxes more useful. +OpenShulker is a plugin for 1.14 to 26.2 that makes Shulkerboxes more useful. You can open a Shulker by Shift-Rightclicking it in your hand or Inventory. @@ -21,6 +21,9 @@ You can download it on spigotmc: [OpenShulker](https://www.spigotmc.org/resource OpenSound: "BLOCK_SHULKER_BOX_OPEN" # Sound for closing a Shulker CloseSound: "BLOCK_SHULKER_BOX_CLOSE" +Hooks: + ChestSort: true + WorldGuard: true Messages: Prefix: "&8[&2OpenShulker&8] &7" CannotBreakContainer: "§cYou cannot break this container, since there's an opened shulker in it" @@ -47,7 +50,4 @@ Github: [Issues Page](https://github.com/Test-Account666/OpenShulker/issues) # Todo -- Add ability to input items by right clicking an item on/with a shulker box -- Add ChestSort Hook -- Add WorldGuard Hook - Add update checker diff --git a/pom.xml b/pom.xml index 0ce8740..0642f40 100644 --- a/pom.xml +++ b/pom.xml @@ -13,6 +13,7 @@ 1.8 + 26.2.build.31-alpha UTF-8 @@ -61,13 +62,23 @@ sonatype https://oss.sonatype.org/content/groups/public/ + + enginehub-repo + https://maven.enginehub.org/repo/ + io.papermc.paper paper-api - 1.19.4-R0.1-SNAPSHOT + ${paper.version} + provided + + + com.sk89q.worldguard + worldguard-bukkit + 7.0.17 provided diff --git a/src/main/java/me/entity303/openshulker/OpenShulker.java b/src/main/java/me/entity303/openshulker/OpenShulker.java index 2d2c568..1464b18 100644 --- a/src/main/java/me/entity303/openshulker/OpenShulker.java +++ b/src/main/java/me/entity303/openshulker/OpenShulker.java @@ -18,6 +18,8 @@ public final class OpenShulker extends JavaPlugin implements Listener { public boolean _allowContainerOpen = true; public boolean _allowEnderChestOpen = true; public boolean _allowHandOpen = true; + public boolean _hookChestSort = true; + public boolean _hookWorldGuard = true; private ShulkerActions _shulkerActions; @Override @@ -92,6 +94,8 @@ public void InitializeConfig() { this._allowContainerOpen = this.getConfig().getBoolean("OpenMethods.AllowContainerOpen"); this._allowEnderChestOpen = this.getConfig().getBoolean("OpenMethods.AllowEnderChestOpen"); this._allowHandOpen = this.getConfig().getBoolean("OpenMethods.AllowHandOpen"); + this._hookChestSort = this.getConfig().getBoolean("Hooks.ChestSort", true); + this._hookWorldGuard = this.getConfig().getBoolean("Hooks.WorldGuard", true); } public ShulkerActions GetShulkerActions() { diff --git a/src/main/java/me/entity303/openshulker/hooks/ChestSortHook.java b/src/main/java/me/entity303/openshulker/hooks/ChestSortHook.java new file mode 100644 index 0000000..40f10cc --- /dev/null +++ b/src/main/java/me/entity303/openshulker/hooks/ChestSortHook.java @@ -0,0 +1,23 @@ +package me.entity303.openshulker.hooks; + +import org.bukkit.Bukkit; +import org.bukkit.inventory.Inventory; + +import java.lang.reflect.Method; + +public final class ChestSortHook { + private ChestSortHook() { + } + + public static void SetUnsortable(Inventory inventory) { + if (Bukkit.getPluginManager().getPlugin("ChestSort") == null) return; + + try { + Class chestSortApi = Class.forName("de.jeff_media.chestsort.api.ChestSortAPI"); + Method setUnsortable = chestSortApi.getMethod("setUnsortable", Inventory.class); + setUnsortable.invoke(null, inventory); + } catch (Throwable ignored) { + // ChestSort is optional and older builds may not expose the API. + } + } +} diff --git a/src/main/java/me/entity303/openshulker/hooks/WorldGuardHook.java b/src/main/java/me/entity303/openshulker/hooks/WorldGuardHook.java new file mode 100644 index 0000000..943fede --- /dev/null +++ b/src/main/java/me/entity303/openshulker/hooks/WorldGuardHook.java @@ -0,0 +1,37 @@ +package me.entity303.openshulker.hooks; + +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.bukkit.WorldGuardPlugin; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.regions.RegionContainer; +import com.sk89q.worldguard.protection.regions.RegionQuery; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +public final class WorldGuardHook { + private WorldGuardHook() { + } + + public static boolean CanBuild(Player player, Location location) { + if (Bukkit.getPluginManager().getPlugin("WorldGuard") == null) return true; + if (location == null || location.getWorld() == null) return true; + + try { + LocalPlayer localPlayer = WorldGuardPlugin.inst().wrapPlayer(player); + + if (WorldGuard.getInstance().getPlatform().getSessionManager().hasBypass(localPlayer, BukkitAdapter.adapt(location.getWorld()))) { + return true; + } + + RegionContainer container = WorldGuard.getInstance().getPlatform().getRegionContainer(); + RegionQuery query = container.createQuery(); + + return query.testState(BukkitAdapter.adapt(location), localPlayer, Flags.BUILD); + } catch (Throwable ignored) { + return true; + } + } +} diff --git a/src/main/java/me/entity303/openshulker/listener/ShulkerOpenCloseListener.java b/src/main/java/me/entity303/openshulker/listener/ShulkerOpenCloseListener.java index 6a368af..06f306d 100644 --- a/src/main/java/me/entity303/openshulker/listener/ShulkerOpenCloseListener.java +++ b/src/main/java/me/entity303/openshulker/listener/ShulkerOpenCloseListener.java @@ -1,6 +1,7 @@ package me.entity303.openshulker.listener; import me.entity303.openshulker.OpenShulker; +import me.entity303.openshulker.hooks.WorldGuardHook; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; @@ -47,12 +48,16 @@ public void OnShulkerOpen(PlayerInteractEvent event) { //Don't open shulkerbox when interact with hopper if (event.getClickedBlock() != null && event.getClickedBlock().getType() == Material.HOPPER) return; - event.setCancelled(this._openShulker.GetShulkerActions().AttemptToOpenShulkerBox(event.getPlayer())); + if (this._openShulker.GetShulkerActions().AttemptToOpenShulkerBox(event.getPlayer())) event.setCancelled(true); } @EventHandler(ignoreCancelled = true) public void OnShulkerOpenAlternative(InventoryClickEvent event) { - if (!event.getWhoClicked().hasPermission("openshulker.use")) return; + if (!(event.getWhoClicked() instanceof Player)) return; + + Player player = (Player) event.getWhoClicked(); + + if (!player.hasPermission("openshulker.use")) return; if (event.getClickedInventory() == null) return; @@ -64,6 +69,8 @@ public void OnShulkerOpenAlternative(InventoryClickEvent event) { if (clickedItemStack.getType() == Material.AIR) return; + if (this.HandleShulkerItemInput(event, player, clickedItemStack, clickedSlot)) return; + if (!clickedItemStack.getType().name().contains(Material.SHULKER_BOX.name())) return; if (!event.isRightClick()) return; @@ -74,17 +81,17 @@ public void OnShulkerOpenAlternative(InventoryClickEvent event) { if (!this._openShulker._allowInventoryOpen) return; if (event.getView().getTopInventory().getType() == InventoryType.SHULKER_BOX) { - if (this._openShulker.GetShulkerActions().HasOpenShulkerBox((Player) event.getWhoClicked())) { - ItemStack shulkerBox = this._openShulker.GetShulkerActions().SearchShulkerBox((Player) event.getWhoClicked()); + if (this._openShulker.GetShulkerActions().HasOpenShulkerBox(player)) { + ItemStack shulkerBox = this._openShulker.GetShulkerActions().SearchShulkerBox(player); - this._openShulker.GetShulkerActions().SaveShulkerBox(shulkerBox, event.getView().getTopInventory(), (Player) event.getWhoClicked()); + this._openShulker.GetShulkerActions().SaveShulkerBox(shulkerBox, event.getView().getTopInventory(), player); } //Close inventory to prevent overriding open shulker contents event.getWhoClicked().closeInventory(); } - boolean open = this._openShulker.GetShulkerActions().AttemptToOpenShulkerBox((Player) event.getWhoClicked(), clickedItemStack); + boolean open = this._openShulker.GetShulkerActions().AttemptToOpenShulkerBox(player, clickedItemStack); if (!open) return; event.setCancelled(true); @@ -93,9 +100,9 @@ public void OnShulkerOpenAlternative(InventoryClickEvent event) { if (event.getClickedInventory().getType() == InventoryType.ENDER_CHEST) { if (!this._openShulker._allowEnderChestOpen) return; - if (!this.IsOwnerOfEnderChest((Player) event.getWhoClicked(), clickedItemStack, clickedSlot)) return; + if (!this.IsOwnerOfEnderChest(player, clickedItemStack, clickedSlot)) return; - boolean open = this._openShulker.GetShulkerActions().AttemptToOpenShulkerBox((Player) event.getWhoClicked(), clickedItemStack, true); + boolean open = this._openShulker.GetShulkerActions().AttemptToOpenShulkerBox(player, clickedItemStack, true); if (!open) return; event.setCancelled(true); @@ -108,12 +115,60 @@ public void OnShulkerOpenAlternative(InventoryClickEvent event) { if (location == null) return; - boolean open = this._openShulker.GetShulkerActions().AttemptToOpenShulkerBox((Player) event.getWhoClicked(), clickedItemStack, location); + if (!this.CanUseInventoryLocation(player, location)) return; + + boolean open = this._openShulker.GetShulkerActions().AttemptToOpenShulkerBox(player, clickedItemStack, location); if (!open) return; event.setCancelled(true); } + private boolean HandleShulkerItemInput(InventoryClickEvent event, Player player, ItemStack clickedItemStack, int clickedSlot) { + if (!event.isRightClick()) return false; + + ItemStack cursorItemStack = event.getCursor(); + + if (cursorItemStack == null) return false; + + if (cursorItemStack.getType() == Material.AIR) return false; + + Location location = event.getClickedInventory().getLocation(); + + if (!this.CanUseInventoryLocation(player, location)) return false; + + if (this._openShulker.GetShulkerActions().CanInputIntoShulkerBox(player, clickedItemStack, cursorItemStack)) { + ItemStack leftover = this._openShulker.GetShulkerActions().InputItemIntoShulkerBox(clickedItemStack, cursorItemStack); + + event.getClickedInventory().setItem(clickedSlot, clickedItemStack); + event.setCursor(leftover); + event.setCancelled(true); + return true; + } + + if (this._openShulker.GetShulkerActions().CanInputIntoShulkerBox(player, cursorItemStack, clickedItemStack)) { + ItemStack leftover = this._openShulker.GetShulkerActions().InputItemIntoShulkerBox(cursorItemStack, clickedItemStack); + + event.setCursor(cursorItemStack); + event.getClickedInventory().setItem(clickedSlot, leftover); + event.setCancelled(true); + return true; + } + + return false; + } + + private boolean CanUseInventoryLocation(Player player, Location location) { + if (!this._openShulker._hookWorldGuard) return true; + + if (location == null) return true; + + try { + return WorldGuardHook.CanBuild(player, location); + } catch (Throwable ignored) { + return true; + } + } + //Awful code, I know, but I don't see a better way private boolean IsOwnerOfEnderChest(Player player, ItemStack clickedItemStack, int clickedSlot) { ItemMeta clickedItemMeta = clickedItemStack.getItemMeta(); diff --git a/src/main/java/me/entity303/openshulker/util/ShulkerActions.java b/src/main/java/me/entity303/openshulker/util/ShulkerActions.java index 5570e55..710d8a0 100644 --- a/src/main/java/me/entity303/openshulker/util/ShulkerActions.java +++ b/src/main/java/me/entity303/openshulker/util/ShulkerActions.java @@ -1,6 +1,7 @@ package me.entity303.openshulker.util; import me.entity303.openshulker.OpenShulker; +import me.entity303.openshulker.hooks.ChestSortHook; import org.bukkit.*; import org.bukkit.block.Block; import org.bukkit.block.Container; @@ -14,6 +15,9 @@ import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataType; +import java.util.HashMap; +import java.util.Map; + public class ShulkerActions { private final NamespacedKey _openShulkerKey; private final NamespacedKey _openShulkerLocationKey; @@ -49,7 +53,7 @@ public void SaveShulkerBox(ItemStack shulkerBoxStack, Inventory inventory, Playe container.remove(this._openShulkerLocationKey); try { - player.playSound(player, Sound.valueOf(this._openShulker.getConfig().getString("CloseSound")), 1F, 1F); + player.playSound(player.getLocation(), Sound.valueOf(this._openShulker.getConfig().getString("CloseSound")), 1F, 1F); } catch (Throwable ignored) { //Ignore the exception, it's probably just a message about not being able to find the correct sound //We have an info message in OpenShulker#onEnable for this @@ -177,6 +181,51 @@ public boolean IsOpenShulker(ItemStack itemStack) { return this.IsOpenShulker(itemStack, null); } + public boolean IsShulkerBox(ItemStack itemStack) { + if (itemStack == null) return false; + + if (itemStack.getType() == Material.AIR) return false; + + return itemStack.getType().name().contains(Material.SHULKER_BOX.name()); + } + + public boolean CanInputIntoShulkerBox(Player player, ItemStack shulkerBoxStack, ItemStack itemStack) { + if (!player.hasPermission("openshulker.write")) return false; + + if (!this.IsShulkerBox(shulkerBoxStack)) return false; + + if (itemStack == null) return false; + + if (itemStack.getType() == Material.AIR) return false; + + if (this.IsShulkerBox(itemStack)) return false; + + if (shulkerBoxStack.getAmount() != 1) return false; + + if (!(shulkerBoxStack.getItemMeta() instanceof BlockStateMeta)) return false; + + BlockStateMeta blockStateMeta = (BlockStateMeta) shulkerBoxStack.getItemMeta(); + + return blockStateMeta.getBlockState() instanceof ShulkerBox; + } + + public ItemStack InputItemIntoShulkerBox(ItemStack shulkerBoxStack, ItemStack itemStack) { + BlockStateMeta blockStateMeta = (BlockStateMeta) shulkerBoxStack.getItemMeta(); + ShulkerBox shulkerBox = (ShulkerBox) blockStateMeta.getBlockState(); + Inventory inventory = shulkerBox.getInventory(); + + HashMap leftovers = inventory.addItem(itemStack.clone()); + + blockStateMeta.setBlockState(shulkerBox); + shulkerBoxStack.setItemMeta(blockStateMeta); + + if (leftovers.isEmpty()) return null; + + for (Map.Entry entry : leftovers.entrySet()) return entry.getValue(); + + return null; + } + public boolean AttemptToOpenShulkerBox(Player player) { ItemStack itemStack = player.getInventory().getItemInMainHand(); @@ -214,9 +263,11 @@ public boolean AttemptToOpenShulkerBox(Player player, ItemStack itemStack) { container.set(this._openShulkerKey, PersistentDataType.STRING, player.getUniqueId().toString()); - Inventory inventory = Bukkit.createInventory(null, InventoryType.SHULKER_BOX); + String inventoryTitle = meta.hasDisplayName() ? meta.getDisplayName() : null; + Inventory inventory = inventoryTitle == null ? Bukkit.createInventory(null, InventoryType.SHULKER_BOX) : Bukkit.createInventory(null, InventoryType.SHULKER_BOX, inventoryTitle); + + if (this._openShulker._hookChestSort) ChestSortHook.SetUnsortable(inventory); - PersistentDataContainer playerContainer = player.getPersistentDataContainer(); Bukkit.getScheduler().runTaskLater(this._openShulker, () -> { inventory.setContents(shulker.getInventory().getContents()); @@ -224,7 +275,7 @@ public boolean AttemptToOpenShulkerBox(Player player, ItemStack itemStack) { }, this._openShulker.getConfig().getLong("WaitSecondsBeforeOpen", 0) * 20); try { - player.playSound(player, Sound.valueOf(this._openShulker.getConfig().getString("OpenSound")), 1F, 1F); + player.playSound(player.getLocation(), Sound.valueOf(this._openShulker.getConfig().getString("OpenSound")), 1F, 1F); } catch (Throwable ignored) { //Ignore the exception, it's probably just a message about not being able to find the correct sound //We have an info message in OpenShulker#onEnable for this diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 2819d4e..a78ba60 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -6,9 +6,12 @@ OpenMethods: WaitSecondsBeforeOpen: 0 OpenSound: "BLOCK_SHULKER_BOX_OPEN" CloseSound: "BLOCK_SHULKER_BOX_CLOSE" +Hooks: + ChestSort: true + WorldGuard: true Messages: Prefix: "&8[&2OpenShulker&8] &7" CannotBreakContainer: "§cYou cannot break this container, since there's an opened shulker in it" OpenShulkerCommand: Syntax: "&cSyntax: &4/