fix: use inventory holders for menus and rework shulker box handling

This commit is contained in:
2026-04-22 00:35:57 +08:00
parent 22708cf75d
commit 1863d04c65
9 changed files with 320 additions and 319 deletions

View File

@@ -1,5 +1,6 @@
package cn.infstar.essentialsC; package cn.infstar.essentialsC;
import org.bukkit.ChatColor;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
@@ -147,6 +148,6 @@ public class LangManager {
* 翻译颜色代码 * 翻译颜色代码
*/ */
private String translateColorCodes(String text) { private String translateColorCodes(String text) {
return text.replace("&", "§"); return text == null ? "" : ChatColor.translateAlternateColorCodes('&', text);
} }
} }

View File

@@ -6,7 +6,6 @@ import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public abstract class BaseCommand implements CommandExecutor { public abstract class BaseCommand implements CommandExecutor {
@@ -32,7 +31,7 @@ public abstract class BaseCommand implements CommandExecutor {
} }
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) { public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (sender instanceof Player player) { if (sender instanceof Player player) {
if (!player.hasPermission(permission)) { if (!player.hasPermission(permission)) {
String message = getLang().getString("messages.no-permission", String message = getLang().getString("messages.no-permission",

View File

@@ -1,6 +1,7 @@
package cn.infstar.essentialsC.commands; package cn.infstar.essentialsC.commands;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.NamespacedKey; import org.bukkit.NamespacedKey;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@@ -8,41 +9,61 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.Inventory; import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataType; import org.bukkit.persistence.PersistentDataType;
import org.jetbrains.annotations.NotNull;
public class BlocksMenuCommand extends BaseCommand implements Listener { public class BlocksMenuCommand extends BaseCommand implements Listener {
private static final int MENU_SIZE = 36; private static final int MENU_SIZE = 36;
private static boolean listenerRegistered = false;
private final NamespacedKey blockKey; private final NamespacedKey blockKey;
private static final class BlocksMenuHolder implements InventoryHolder {
private final Inventory inventory;
private BlocksMenuHolder(String title) {
this.inventory = Bukkit.createInventory(this, MENU_SIZE, title);
}
@Override
public Inventory getInventory() {
return inventory;
}
}
public BlocksMenuCommand() { public BlocksMenuCommand() {
super("essentialsc.command.blocks"); super("essentialsc.command.blocks");
plugin.getServer().getPluginManager().registerEvents(this, plugin); if (!listenerRegistered) {
blockKey = new NamespacedKey(plugin, "block_key"); plugin.getServer().getPluginManager().registerEvents(this, plugin);
listenerRegistered = true;
}
this.blockKey = new NamespacedKey(plugin, "block_key");
} }
@Override @Override
protected boolean execute(@NotNull Player player, String[] args) { protected boolean execute(Player player, String[] args) {
openMenu(player); openMenu(player);
return true; return true;
} }
private void openMenu(Player player) { private void openMenu(Player player) {
String title = plugin.getConfig().getString("blocks-menu.title", "&6&lEssentialsC &8- &e&l功能方块菜单"); String title = plugin.getConfig().getString("blocks-menu.title", "&6&lEssentialsC &8- &e&l鍔熻兘鏂瑰潡鑿滃崟");
Inventory menu = Bukkit.createInventory(null, MENU_SIZE, translateColor(title)); Inventory menu = new BlocksMenuHolder(translateColor(title)).getInventory();
// 从配置中读取所有物品
var itemsConfig = plugin.getConfig().getConfigurationSection("blocks-menu.items"); var itemsConfig = plugin.getConfig().getConfigurationSection("blocks-menu.items");
if (itemsConfig == null) return; if (itemsConfig == null) {
return;
}
for (String key : itemsConfig.getKeys(false)) { for (String key : itemsConfig.getKeys(false)) {
var section = itemsConfig.getConfigurationSection(key); var section = itemsConfig.getConfigurationSection(key);
if (section == null) continue; if (section == null) {
continue;
}
// 检查权限
String permission = section.getString("permission"); String permission = section.getString("permission");
if (permission != null && !player.hasPermission(permission)) { if (permission != null && !player.hasPermission(permission)) {
continue; continue;
@@ -50,12 +71,14 @@ public class BlocksMenuCommand extends BaseCommand implements Listener {
int slot = section.getInt("slot"); int slot = section.getInt("slot");
Material material = Material.matchMaterial(section.getString("material", "STONE")); Material material = Material.matchMaterial(section.getString("material", "STONE"));
if (material == null) material = Material.STONE; if (material == null) {
material = Material.STONE;
}
String name = translateColor(section.getString("name", "&fItem")); String name = translateColor(section.getString("name", "&fItem"));
java.util.List<String> lore = section.getStringList("lore").stream() java.util.List<String> lore = section.getStringList("lore").stream()
.map(this::translateColor) .map(this::translateColor)
.collect(java.util.stream.Collectors.toList()); .toList();
addItem(menu, slot, material, name, lore, key); addItem(menu, slot, material, name, lore, key);
} }
@@ -77,30 +100,27 @@ public class BlocksMenuCommand extends BaseCommand implements Listener {
@EventHandler @EventHandler
public void onMenuClick(InventoryClickEvent event) { public void onMenuClick(InventoryClickEvent event) {
// 动态获取配置的标题 if (!(event.getView().getTopInventory().getHolder(false) instanceof BlocksMenuHolder)) {
String configTitle = plugin.getConfig().getString("blocks-menu.title", "&6&lEssentialsC &8- &e&l功能方块菜单"); return;
String actualTitle = translateColor(configTitle); }
if (!(event.getWhoClicked() instanceof Player player)) {
if (!event.getView().getTitle().equals(actualTitle)) return; return;
if (!(event.getWhoClicked() instanceof Player player)) return; }
event.setCancelled(true); event.setCancelled(true);
ItemStack clicked = event.getCurrentItem(); ItemStack clicked = event.getCurrentItem();
if (clicked == null || !clicked.hasItemMeta()) return; if (clicked == null || !clicked.hasItemMeta()) {
return;
}
ItemMeta meta = clicked.getItemMeta(); ItemMeta meta = clicked.getItemMeta();
String key = meta.getPersistentDataContainer().get(this.blockKey, PersistentDataType.STRING); String key = meta.getPersistentDataContainer().get(this.blockKey, PersistentDataType.STRING);
// 点击后执行对应命令并播放音效(如果有)
if (key != null && HelpCommand.COMMAND_CACHE.containsKey(key)) { if (key != null && HelpCommand.COMMAND_CACHE.containsKey(key)) {
playBlockOpenSound(player, key); playBlockOpenSound(player, key);
HelpCommand.COMMAND_CACHE.get(key).execute(player, new String[]{}); HelpCommand.COMMAND_CACHE.get(key).execute(player, new String[0]);
} }
} }
/**
* 播放对应方块的打开音效(优先使用交互音效)
*/
private void playBlockOpenSound(Player player, String key) { private void playBlockOpenSound(Player player, String key) {
org.bukkit.Sound sound = switch (key) { org.bukkit.Sound sound = switch (key) {
case "workbench" -> org.bukkit.Sound.BLOCK_WOOD_HIT; case "workbench" -> org.bukkit.Sound.BLOCK_WOOD_HIT;
@@ -119,10 +139,7 @@ public class BlocksMenuCommand extends BaseCommand implements Listener {
} }
} }
/**
* 转换颜色代码 & -> §
*/
private String translateColor(String text) { private String translateColor(String text) {
return text.replace("&", "§"); return text == null ? "" : ChatColor.translateAlternateColorCodes('&', text);
} }
} }

View File

@@ -7,10 +7,8 @@ import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter; import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
public class HelpCommand extends BaseCommand implements TabCompleter { public class HelpCommand extends BaseCommand implements TabCompleter {
@@ -44,7 +42,7 @@ public class HelpCommand extends BaseCommand implements TabCompleter {
} }
@Override @Override
protected boolean execute(@NotNull Player player, String[] args) { protected boolean execute(Player player, String[] args) {
return handleCommand(player, player, args); return handleCommand(player, player, args);
} }
@@ -223,11 +221,14 @@ public class HelpCommand extends BaseCommand implements TabCompleter {
* 获取命令对应的权限节点 * 获取命令对应的权限节点
*/ */
private String getPermissionForCommand(String command) { private String getPermissionForCommand(String command) {
if (command.equals("mobdrops")) {
return "essentialsc.mobdrops.enderman";
}
return "essentialsc.command." + command; return "essentialsc.command." + command;
} }
@Override @Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) { public List<String> onTabComplete(CommandSender sender, Command command, String label, String[] args) {
if (args.length == 1) { if (args.length == 1) {
List<String> completions = new ArrayList<>(); List<String> completions = new ArrayList<>();
String partial = args[0].toLowerCase(); String partial = args[0].toLowerCase();

View File

@@ -5,6 +5,7 @@ import org.bukkit.Bukkit;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory; import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
@@ -16,6 +17,19 @@ import java.util.Arrays;
*/ */
public class MobDropCommand extends BaseCommand { public class MobDropCommand extends BaseCommand {
public static final class MobDropMenuHolder implements InventoryHolder {
private final Inventory inventory;
public MobDropMenuHolder(String title) {
this.inventory = Bukkit.createInventory(this, 27, title);
}
@Override
public Inventory getInventory() {
return inventory;
}
}
public MobDropCommand() { public MobDropCommand() {
super("essentialsc.mobdrops.enderman"); super("essentialsc.mobdrops.enderman");
} }
@@ -34,7 +48,7 @@ public class MobDropCommand extends BaseCommand {
boolean endermanEnabled = plugin.getConfig().getBoolean("mob-drops.enderman.enabled", true); boolean endermanEnabled = plugin.getConfig().getBoolean("mob-drops.enderman.enabled", true);
// 创建菜单 // 创建菜单
Inventory menu = Bukkit.createInventory(null, 27, "§6§l生物掉落控制"); Inventory menu = new MobDropMenuHolder("§6§l生物掉落控制").getInventory();
// 末影人控制项 // 末影人控制项
ItemStack endermanItem = new ItemStack(Material.ENDER_PEARL); ItemStack endermanItem = new ItemStack(Material.ENDER_PEARL);

View File

@@ -21,7 +21,6 @@ public class MobDropListener implements Listener {
loadConfig(); loadConfig();
// 注册监听器 // 注册监听器
plugin.getServer().getPluginManager().registerEvents(this, plugin);
} }
/** /**

View File

@@ -25,7 +25,7 @@ public class MobDropMenuListener implements Listener {
@EventHandler @EventHandler
public void onInventoryClick(InventoryClickEvent event) { public void onInventoryClick(InventoryClickEvent event) {
if (!event.getView().getTitle().equals("§6§l生物掉落控制")) { if (!(event.getView().getTopInventory().getHolder(false) instanceof cn.infstar.essentialsC.commands.MobDropCommand.MobDropMenuHolder)) {
return; return;
} }
@@ -71,7 +71,7 @@ public class MobDropMenuListener implements Listener {
private void openMobDropMenu(Player player) { private void openMobDropMenu(Player player) {
boolean endermanEnabled = plugin.getConfig().getBoolean("mob-drops.enderman.enabled", true); boolean endermanEnabled = plugin.getConfig().getBoolean("mob-drops.enderman.enabled", true);
Inventory menu = Bukkit.createInventory(null, 27, "§6§l生物掉落控制"); Inventory menu = new cn.infstar.essentialsC.commands.MobDropCommand.MobDropMenuHolder("§6§l生物掉落控制").getInventory();
ItemStack endermanItem = new ItemStack(Material.ENDER_PEARL); ItemStack endermanItem = new ItemStack(Material.ENDER_PEARL);
ItemMeta endermanMeta = endermanItem.getItemMeta(); ItemMeta endermanMeta = endermanItem.getItemMeta();

View File

@@ -2,19 +2,28 @@ package cn.infstar.essentialsC.listeners;
import cn.infstar.essentialsC.EssentialsC; import cn.infstar.essentialsC.EssentialsC;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.block.ShulkerBox; import org.bukkit.block.ShulkerBox;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.block.Action; import org.bukkit.event.block.Action;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryDragEvent;
import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.server.PluginDisableEvent;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.Inventory; import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.InventoryView;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.bukkit.inventory.meta.BlockStateMeta; import org.bukkit.inventory.meta.BlockStateMeta;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -23,11 +32,8 @@ import java.util.UUID;
public class ShulkerBoxListener implements Listener { public class ShulkerBoxListener implements Listener {
private final EssentialsC plugin; private static final int SHULKER_SIZE = 27;
// 存储玩家打开的潜影盒玩家UUID -> (原始物品快照, 当前物品引用)
private final Map<UUID, ShulkerBoxData> openShulkerBoxes = new HashMap<>();
// 预定义所有潜影盒材质(性能优化)
private static final Set<Material> SHULKER_BOX_MATERIALS = Set.of( private static final Set<Material> SHULKER_BOX_MATERIALS = Set.of(
Material.SHULKER_BOX, Material.SHULKER_BOX,
Material.WHITE_SHULKER_BOX, Material.WHITE_SHULKER_BOX,
@@ -48,105 +54,136 @@ public class ShulkerBoxListener implements Listener {
Material.BLACK_SHULKER_BOX Material.BLACK_SHULKER_BOX
); );
/** private final EssentialsC plugin;
* 潜影盒 Inventory Holder - 用于识别自定义 inventory private final Map<UUID, OpenShulkerSession> openShulkerBoxes = new HashMap<>();
*/
private static class ShulkerBoxHolder implements org.bukkit.inventory.InventoryHolder {
private final Inventory inventory;
private final ItemStack shulkerBoxItem;
private final ItemStack[] currentContents; // 实时追踪的物品内容
ShulkerBoxHolder(ItemStack shulkerBoxItem, String title) { private static final class ShulkerBoxHolder implements InventoryHolder {
this.shulkerBoxItem = shulkerBoxItem; private final Inventory inventory;
this.inventory = Bukkit.createInventory(this, 27, title);
this.currentContents = new ItemStack[27]; private ShulkerBoxHolder(String title) {
this.inventory = Bukkit.createInventory(this, SHULKER_SIZE, title);
} }
@Override @Override
public @NotNull Inventory getInventory() { public Inventory getInventory() {
return this.inventory; return this.inventory;
} }
public ItemStack getShulkerBoxItem() {
return shulkerBoxItem;
}
/**
* 更新指定槽位的物品(供外部调用)
*/
public void updateSlot(int slot, ItemStack item) {
if (slot >= 0 && slot < 27) {
currentContents[slot] = item;
}
}
/**
* 获取当前追踪的所有物品内容
*/
public ItemStack[] getCurrentContents() {
return currentContents.clone();
}
} }
/** private static final class OpenShulkerSession {
* 潜影盒数据记录 private final ItemStack sourceItem;
*/ private final EquipmentSlot sourceHand;
private static class ShulkerBoxData { private final int preferredSlot;
int slotIndex; // 玩家背包中的槽位索引
ItemStack originalSnapshot; // 打开时的物品快照(用于验证)
int totalItems; // 打开时的物品总数(用于防刷)
ShulkerBoxData(int slot, ItemStack snapshot, int items) { private OpenShulkerSession(ItemStack sourceItem, EquipmentSlot sourceHand, int preferredSlot) {
this.slotIndex = slot; this.sourceItem = sourceItem;
this.originalSnapshot = snapshot; this.sourceHand = sourceHand;
this.totalItems = items; this.preferredSlot = preferredSlot;
} }
} }
public ShulkerBoxListener(EssentialsC plugin) { public ShulkerBoxListener(EssentialsC plugin) {
this.plugin = plugin; this.plugin = plugin;
plugin.getServer().getPluginManager().registerEvents(this, plugin);
} }
@EventHandler @EventHandler(priority = EventPriority.HIGHEST)
public void onPlayerInteract(PlayerInteractEvent event) { public void onPlayerInteract(PlayerInteractEvent event) {
// 只处理右键点击空气或方块的事件
if (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK) { if (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK) {
return; return;
} }
Player player = event.getPlayer(); Player player = event.getPlayer();
if (!player.isSneaking() || !player.hasPermission("essentialsc.shulkerbox.open")) {
// 检查权限
if (!player.hasPermission("essentialsc.shulkerbox.open")) {
return; return;
} }
ItemStack item = event.getItem(); if (openShulkerBoxes.containsKey(player.getUniqueId())) {
if (item == null || !isShulkerBox(item)) {
return; return;
} }
// 只有潜行+右键才打开潜影盒 EquipmentSlot hand = event.getHand() == EquipmentSlot.OFF_HAND ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND;
if (!player.isSneaking()) { ItemStack sourceItem = getItemFromHand(player, hand);
if (!isShulkerBox(sourceItem)) {
return; return;
} }
// 取消默认行为(防止放置潜影盒) if (!(sourceItem.getItemMeta() instanceof BlockStateMeta blockStateMeta)) {
event.setCancelled(true); return;
}
if (!(blockStateMeta.getBlockState() instanceof ShulkerBox shulkerBox)) {
return;
}
// 查找物品在玩家背包中的槽位 event.setUseItemInHand(org.bukkit.event.Event.Result.DENY);
int slotIndex = -1; event.setUseInteractedBlock(org.bukkit.event.Event.Result.DENY);
for (int i = 0; i < player.getInventory().getSize(); i++) {
ItemStack invItem = player.getInventory().getItem(i); ItemStack sourceSnapshot = sourceItem.clone();
if (invItem != null && invItem.isSimilar(item)) { plugin.getServer().getScheduler().runTask(plugin, () -> {
slotIndex = i; if (!player.isOnline() || openShulkerBoxes.containsKey(player.getUniqueId())) {
break; return;
}
openShulkerBox(player, hand, sourceSnapshot, shulkerBox);
});
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onInventoryClick(InventoryClickEvent event) {
if (!(event.getWhoClicked() instanceof Player player)) {
return;
}
InventoryView view = event.getView();
Inventory topInventory = view.getTopInventory();
if (!(topInventory.getHolder(false) instanceof ShulkerBoxHolder)) {
return;
}
int topSize = topInventory.getSize();
boolean clickTopInventory = event.getRawSlot() >= 0 && event.getRawSlot() < topSize;
if (clickTopInventory && isShulkerBox(event.getCursor())) {
event.setCancelled(true);
sendNestedMessage(player);
return;
}
if (clickTopInventory && event.getClick() == ClickType.NUMBER_KEY) {
ItemStack hotbarItem = player.getInventory().getItem(event.getHotbarButton());
if (isShulkerBox(hotbarItem)) {
event.setCancelled(true);
sendNestedMessage(player);
return;
} }
} }
// 打开潜影盒(传入槽位索引) if (event.isShiftClick() && isShulkerBox(event.getCurrentItem())) {
openShulkerBox(player, item, slotIndex); event.setCancelled(true);
sendNestedMessage(player);
}
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onInventoryDrag(InventoryDragEvent event) {
InventoryView view = event.getView();
Inventory topInventory = view.getTopInventory();
if (!(topInventory.getHolder(false) instanceof ShulkerBoxHolder)) {
return;
}
if (!isShulkerBox(event.getOldCursor())) {
return;
}
int topSize = topInventory.getSize();
for (int rawSlot : event.getRawSlots()) {
if (rawSlot >= 0 && rawSlot < topSize) {
event.setCancelled(true);
if (event.getWhoClicked() instanceof Player player) {
sendNestedMessage(player);
}
return;
}
}
} }
@EventHandler @EventHandler
@@ -155,202 +192,131 @@ public class ShulkerBoxListener implements Listener {
return; return;
} }
Inventory closedInventory = event.getInventory(); commitOpenShulker(player, event.getInventory());
// 检查是否是潜影盒 inventory
if (!(closedInventory.getHolder(false) instanceof ShulkerBoxHolder holder)) {
return;
}
UUID playerId = player.getUniqueId();
ShulkerBoxData data = openShulkerBoxes.remove(playerId);
if (data == null) {
return;
}
plugin.getLogger().info("=== 潜影盒关闭(数据已在点击时实时保存) ===");
} }
@EventHandler @EventHandler
public void onInventoryOpen(org.bukkit.event.inventory.InventoryOpenEvent event) { public void onPlayerQuit(PlayerQuitEvent event) {
if (!(event.getPlayer() instanceof Player player)) { Player player = event.getPlayer();
commitOpenShulker(player, player.getOpenInventory().getTopInventory());
}
@EventHandler
public void onPluginDisable(PluginDisableEvent event) {
if (event.getPlugin() != plugin) {
return; return;
} }
Inventory openedInventory = event.getInventory(); for (Player player : Bukkit.getOnlinePlayers()) {
if (openedInventory.getHolder(false) instanceof ShulkerBoxHolder) { commitOpenShulker(player, player.getOpenInventory().getTopInventory());
plugin.getLogger().info("[Open] ✅ 潜影盒 inventory 已打开");
} }
} }
@EventHandler(priority = org.bukkit.event.EventPriority.LOWEST) private void commitOpenShulker(Player player, Inventory inventory) {
public void onInventoryClickDebug(InventoryClickEvent event) { if (!(inventory.getHolder(false) instanceof ShulkerBoxHolder)) {
if (!(event.getWhoClicked() instanceof Player player)) {
return; return;
} }
Inventory clickedInventory = event.getClickedInventory(); OpenShulkerSession session = openShulkerBoxes.remove(player.getUniqueId());
if (clickedInventory == null) { if (session == null) {
return; return;
} }
if (clickedInventory.getHolder(false) instanceof ShulkerBoxHolder) { ItemStack updatedShulker = session.sourceItem.clone();
plugin.getLogger().info("[Click-LOWEST] 检测到点击事件 | 槽位: " + event.getSlot() + writeInventoryBack(updatedShulker, inventory.getContents());
" | 物品: " + (event.getCurrentItem() != null ? event.getCurrentItem().getType() : "null")); restoreItemToPlayer(player, session, updatedShulker);
}
} }
if (!(event.getWhoClicked() instanceof Player player)) {
private void openShulkerBox(Player player, EquipmentSlot hand, ItemStack sourceItem, ShulkerBox shulkerBox) {
removeItemFromHand(player, hand);
ShulkerBoxHolder holder = new ShulkerBoxHolder(resolveTitle(sourceItem));
holder.getInventory().setContents(cloneContents(shulkerBox.getInventory().getContents()));
int preferredSlot = hand == EquipmentSlot.HAND ? player.getInventory().getHeldItemSlot() : -1;
openShulkerBoxes.put(player.getUniqueId(), new OpenShulkerSession(sourceItem, hand, preferredSlot));
player.openInventory(holder.getInventory());
}
private void writeInventoryBack(ItemStack shulkerItem, ItemStack[] contents) {
if (!(shulkerItem.getItemMeta() instanceof BlockStateMeta blockStateMeta)) {
plugin.getLogger().warning("Failed to save shulker box contents: missing BlockStateMeta.");
return; return;
} }
Inventory clickedInventory = event.getClickedInventory(); if (!(blockStateMeta.getBlockState() instanceof ShulkerBox shulkerBox)) {
if (clickedInventory == null) { plugin.getLogger().warning("Failed to save shulker box contents: block state is not a ShulkerBox.");
return; return;
} }
// 检查是否是潜影盒 inventory shulkerBox.getInventory().setContents(cloneContents(contents));
if (!(clickedInventory.getHolder(false) instanceof ShulkerBoxHolder holder)) { blockStateMeta.setBlockState(shulkerBox);
return; shulkerItem.setItemMeta(blockStateMeta);
} }
UUID playerId = player.getUniqueId(); private void restoreItemToPlayer(Player player, OpenShulkerSession session, ItemStack shulkerItem) {
ShulkerBoxData data = openShulkerBoxes.get(playerId); PlayerInventory inventory = player.getInventory();
if (data == null) {
return;
}
// 阻止嵌套潜影盒 if (session.sourceHand == EquipmentSlot.OFF_HAND) {
ItemStack clickedItem = event.getCurrentItem(); if (isEmpty(inventory.getItemInOffHand())) {
if (clickedItem != null && isShulkerBox(clickedItem)) { inventory.setItemInOffHand(shulkerItem);
event.setCancelled(true);
player.sendMessage(EssentialsC.getLangManager().getString("prefix") +
EssentialsC.getLangManager().getString("messages.shulkerbox-nested"));
return;
}
// ✅ CMILib 方式:延迟 1 tick 后立即保存到潜影盒 NBT
plugin.getServer().getScheduler().runTask(plugin, () -> {
plugin.getLogger().info("[Click] === 开始处理点击事件 ===");
// 读取当前 inventory 内容
ItemStack[] contents = clickedInventory.getContents();
// 调试
int nonEmpty = 0;
StringBuilder items = new StringBuilder();
for (int i = 0; i < contents.length; i++) {
if (contents[i] != null && !contents[i].getType().isAir()) {
nonEmpty++;
items.append(String.format("\n [%d] %s x%d", i, contents[i].getType(), contents[i].getAmount()));
}
}
plugin.getLogger().info("[Click] 非空槽位: " + nonEmpty);
plugin.getLogger().info("[Click] 物品详情:" + items);
// 获取玩家背包中的潜影盒物品
ItemStack shulkerItem = null;
if (data.slotIndex >= 0 && data.slotIndex < player.getInventory().getSize()) {
shulkerItem = player.getInventory().getItem(data.slotIndex);
plugin.getLogger().info("[Click] 槽位 " + data.slotIndex + " 物品: " +
(shulkerItem != null ? shulkerItem.getType() : "null"));
}
if (shulkerItem == null || shulkerItem.getType().isAir()) {
plugin.getLogger().warning("[Click] ❌ 潜影盒物品不存在");
return; return;
} }
} else if (session.preferredSlot >= 0 && isEmpty(inventory.getItem(session.preferredSlot))) {
// 更新潜影盒的 BlockState inventory.setItem(session.preferredSlot, shulkerItem);
if (shulkerItem.getItemMeta() instanceof BlockStateMeta blockStateMeta) {
if (blockStateMeta.getBlockState() instanceof ShulkerBox shulkerBox) {
// 设置 inventory 内容
for (int i = 0; i < 27 && i < contents.length; i++) {
shulkerBox.getInventory().setItem(i, contents[i]);
}
// 更新元数据
blockStateMeta.setBlockState(shulkerBox);
shulkerItem.setItemMeta(blockStateMeta);
// 写回玩家背包
player.getInventory().setItem(data.slotIndex, shulkerItem);
plugin.getLogger().info("[Click] ✅ 已实时保存 " + nonEmpty + " 个物品槽");
} else {
plugin.getLogger().warning("[Click] ❌ BlockState 不是 ShulkerBox");
}
} else {
plugin.getLogger().warning("[Click] ❌ 没有 BlockStateMeta");
}
});
}
/**
* 检查物品是否为潜影盒O(1) 时间复杂度)
*/
private boolean isShulkerBox(ItemStack item) {
return SHULKER_BOX_MATERIALS.contains(item.getType());
}
/**
* 打开潜影盒
*/
private void openShulkerBox(Player player, ItemStack shulkerBox, int slotIndex) {
// 获取潜影盒的 BlockStateMeta
if (!(shulkerBox.getItemMeta() instanceof BlockStateMeta blockStateMeta)) {
return; return;
} }
// 获取潜影盒的方块状态 Map<Integer, ItemStack> leftovers = inventory.addItem(shulkerItem);
if (!(blockStateMeta.getBlockState() instanceof ShulkerBox shulkerBoxBlock)) { for (ItemStack leftover : leftovers.values()) {
return; player.getWorld().dropItemNaturally(player.getLocation(), leftover);
} }
}
// 创建物品快照(用于后续验证) private ItemStack[] cloneContents(ItemStack[] contents) {
ItemStack snapshot = shulkerBox.clone(); ItemStack[] copied = new ItemStack[SHULKER_SIZE];
for (int i = 0; i < SHULKER_SIZE && i < contents.length; i++) {
// 计算当前物品总数(用于防刷检查) copied[i] = contents[i] == null ? null : contents[i].clone();
int totalItems = 0;
for (ItemStack item : shulkerBoxBlock.getInventory().getContents()) {
if (item != null && !item.getType().isAir()) {
totalItems += item.getAmount();
}
} }
return copied;
}
// 获取潜影盒的自定义名称,如果没有则使用配置中的默认标题 private ItemStack getItemFromHand(Player player, EquipmentSlot hand) {
String title; return hand == EquipmentSlot.OFF_HAND
if (shulkerBox.hasItemMeta() && shulkerBox.getItemMeta().hasDisplayName()) { ? player.getInventory().getItemInOffHand()
// 使用潜影盒的自定义名称 : player.getInventory().getItemInMainHand();
title = shulkerBox.getItemMeta().getDisplayName(); }
private void removeItemFromHand(Player player, EquipmentSlot hand) {
if (hand == EquipmentSlot.OFF_HAND) {
player.getInventory().setItemInOffHand(null);
} else { } else {
// 使用配置文件中的默认标题 player.getInventory().setItem(player.getInventory().getHeldItemSlot(), null);
String defaultTitle = plugin.getConfig().getString("shulkerbox.default-title", ""); }
if (defaultTitle != null && !defaultTitle.isEmpty()) { }
// 转换颜色代码 & -> §
title = defaultTitle.replace('&', '§'); private String resolveTitle(ItemStack shulkerBox) {
} else { if (shulkerBox.hasItemMeta() && shulkerBox.getItemMeta().hasDisplayName()) {
// 如果配置为空,使用 "Shulker Box"(客户端会自动翻译) return shulkerBox.getItemMeta().getDisplayName();
title = "Shulker Box";
}
} }
// 创建 ShulkerBoxHolder会自动创建 inventory String defaultTitle = plugin.getConfig().getString("shulkerbox.default-title", "");
ShulkerBoxHolder holder = new ShulkerBoxHolder(shulkerBox, title); if (defaultTitle != null && !defaultTitle.isEmpty()) {
Inventory inventory = holder.getInventory(); return ChatColor.translateAlternateColorCodes('&', defaultTitle);
// 复制潜影盒的内容到 inventory
ItemStack[] contents = shulkerBoxBlock.getInventory().getContents();
for (int i = 0; i < 27 && i < contents.length; i++) {
inventory.setItem(i, contents[i]);
} }
return "Shulker Box";
}
// 记录玩家打开的潜影盒(保存槽位索引和快照) private void sendNestedMessage(Player player) {
openShulkerBoxes.put(player.getUniqueId(), new ShulkerBoxData(slotIndex, snapshot, totalItems)); player.sendMessage(EssentialsC.getLangManager().getString("prefix")
+ EssentialsC.getLangManager().getString("messages.shulkerbox-nested"));
}
// 打开 inventory private boolean isShulkerBox(ItemStack item) {
player.openInventory(inventory); return item != null && !item.getType().isAir() && SHULKER_BOX_MATERIALS.contains(item.getType());
}
private boolean isEmpty(ItemStack item) {
return item == null || item.getType().isAir();
} }
} }

View File

@@ -5,7 +5,7 @@ version: ${version}
main: cn.infstar.essentialsC.EssentialsC main: cn.infstar.essentialsC.EssentialsC
api-version: '1.21' api-version: '1.21'
load: POSTWORLD load: POSTWORLD
folia-supported: true folia-supported: false
authors: [ Coldsmiles_7 ] authors: [ Coldsmiles_7 ]
website: www.infstar.cn website: www.infstar.cn
@@ -62,6 +62,9 @@ permissions:
essentialsc.command.repair: essentialsc.command.repair:
description: Allows use of /repair command description: Allows use of /repair command
default: op default: op
essentialsc.command.repair.all:
description: Allows use of /repair all
default: op
essentialsc.command.blocks: essentialsc.command.blocks:
description: Allows use of /essc blocks command description: Allows use of /essc blocks command
default: true default: true
@@ -99,6 +102,7 @@ permissions:
essentialsc.command.reload: true essentialsc.command.reload: true
essentialsc.command.feed: true essentialsc.command.feed: true
essentialsc.command.repair: true essentialsc.command.repair: true
essentialsc.command.repair.all: true
essentialsc.command.help: true essentialsc.command.help: true
essentialsc.shulkerbox.open: true essentialsc.shulkerbox.open: true
essentialsc.mobdrops.enderman: true essentialsc.mobdrops.enderman: true