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/