From 1863d04c6595631368dabe51834415b4375f5ac9 Mon Sep 17 00:00:00 2001 From: Coldsmile Date: Wed, 22 Apr 2026 00:35:57 +0800 Subject: [PATCH] fix: use inventory holders for menus and rework shulker box handling --- .../cn/infstar/essentialsC/LangManager.java | 3 +- .../essentialsC/commands/BaseCommand.java | 3 +- .../commands/BlocksMenuCommand.java | 111 ++-- .../essentialsC/commands/HelpCommand.java | 9 +- .../essentialsC/commands/MobDropCommand.java | 16 +- .../listeners/MobDropListener.java | 1 - .../listeners/MobDropMenuListener.java | 4 +- .../listeners/ShulkerBoxListener.java | 486 ++++++++---------- src/main/resources/paper-plugin.yml | 6 +- 9 files changed, 320 insertions(+), 319 deletions(-) diff --git a/src/main/java/cn/infstar/essentialsC/LangManager.java b/src/main/java/cn/infstar/essentialsC/LangManager.java index 6978b5a..ba25389 100644 --- a/src/main/java/cn/infstar/essentialsC/LangManager.java +++ b/src/main/java/cn/infstar/essentialsC/LangManager.java @@ -1,5 +1,6 @@ package cn.infstar.essentialsC; +import org.bukkit.ChatColor; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.plugin.java.JavaPlugin; @@ -147,6 +148,6 @@ public class LangManager { * 翻译颜色代码 */ private String translateColorCodes(String text) { - return text.replace("&", "§"); + return text == null ? "" : ChatColor.translateAlternateColorCodes('&', text); } } diff --git a/src/main/java/cn/infstar/essentialsC/commands/BaseCommand.java b/src/main/java/cn/infstar/essentialsC/commands/BaseCommand.java index a8e8790..31f2bea 100644 --- a/src/main/java/cn/infstar/essentialsC/commands/BaseCommand.java +++ b/src/main/java/cn/infstar/essentialsC/commands/BaseCommand.java @@ -6,7 +6,6 @@ import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; public abstract class BaseCommand implements CommandExecutor { @@ -32,7 +31,7 @@ public abstract class BaseCommand implements CommandExecutor { } @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 (!player.hasPermission(permission)) { String message = getLang().getString("messages.no-permission", diff --git a/src/main/java/cn/infstar/essentialsC/commands/BlocksMenuCommand.java b/src/main/java/cn/infstar/essentialsC/commands/BlocksMenuCommand.java index fe7c5f9..780683d 100644 --- a/src/main/java/cn/infstar/essentialsC/commands/BlocksMenuCommand.java +++ b/src/main/java/cn/infstar/essentialsC/commands/BlocksMenuCommand.java @@ -1,6 +1,7 @@ package cn.infstar.essentialsC.commands; import org.bukkit.Bukkit; +import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.entity.Player; @@ -8,61 +9,83 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.persistence.PersistentDataType; -import org.jetbrains.annotations.NotNull; public class BlocksMenuCommand extends BaseCommand implements Listener { - + private static final int MENU_SIZE = 36; + private static boolean listenerRegistered = false; + 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() { super("essentialsc.command.blocks"); - plugin.getServer().getPluginManager().registerEvents(this, plugin); - blockKey = new NamespacedKey(plugin, "block_key"); + if (!listenerRegistered) { + plugin.getServer().getPluginManager().registerEvents(this, plugin); + listenerRegistered = true; + } + this.blockKey = new NamespacedKey(plugin, "block_key"); } - + @Override - protected boolean execute(@NotNull Player player, String[] args) { + protected boolean execute(Player player, String[] args) { openMenu(player); return true; } - + private void openMenu(Player player) { - String title = plugin.getConfig().getString("blocks-menu.title", "&6&lEssentialsC &8- &e&l功能方块菜单"); - Inventory menu = Bukkit.createInventory(null, MENU_SIZE, translateColor(title)); - - // 从配置中读取所有物品 + String title = plugin.getConfig().getString("blocks-menu.title", "&6&lEssentialsC &8- &e&l鍔熻兘鏂瑰潡鑿滃崟"); + Inventory menu = new BlocksMenuHolder(translateColor(title)).getInventory(); + var itemsConfig = plugin.getConfig().getConfigurationSection("blocks-menu.items"); - if (itemsConfig == null) return; - + if (itemsConfig == null) { + return; + } + for (String key : itemsConfig.getKeys(false)) { var section = itemsConfig.getConfigurationSection(key); - if (section == null) continue; - - // 检查权限 + if (section == null) { + continue; + } + String permission = section.getString("permission"); if (permission != null && !player.hasPermission(permission)) { continue; } - + int slot = section.getInt("slot"); 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")); java.util.List lore = section.getStringList("lore").stream() .map(this::translateColor) - .collect(java.util.stream.Collectors.toList()); - + .toList(); + addItem(menu, slot, material, name, lore, key); } - + player.openInventory(menu); } - + private void addItem(Inventory inv, int slot, Material material, String name, java.util.List lore, String key) { ItemStack item = new ItemStack(material); ItemMeta meta = item.getItemMeta(); @@ -74,33 +97,30 @@ public class BlocksMenuCommand extends BaseCommand implements Listener { } inv.setItem(slot, item); } - + @EventHandler public void onMenuClick(InventoryClickEvent event) { - // 动态获取配置的标题 - String configTitle = plugin.getConfig().getString("blocks-menu.title", "&6&lEssentialsC &8- &e&l功能方块菜单"); - String actualTitle = translateColor(configTitle); - - if (!event.getView().getTitle().equals(actualTitle)) return; - if (!(event.getWhoClicked() instanceof Player player)) return; - + if (!(event.getView().getTopInventory().getHolder(false) instanceof BlocksMenuHolder)) { + return; + } + if (!(event.getWhoClicked() instanceof Player player)) { + return; + } + event.setCancelled(true); ItemStack clicked = event.getCurrentItem(); - if (clicked == null || !clicked.hasItemMeta()) return; - + if (clicked == null || !clicked.hasItemMeta()) { + return; + } + ItemMeta meta = clicked.getItemMeta(); String key = meta.getPersistentDataContainer().get(this.blockKey, PersistentDataType.STRING); - - // 点击后执行对应命令并播放音效(如果有) if (key != null && HelpCommand.COMMAND_CACHE.containsKey(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) { org.bukkit.Sound sound = switch (key) { case "workbench" -> org.bukkit.Sound.BLOCK_WOOD_HIT; @@ -113,16 +133,13 @@ public class BlocksMenuCommand extends BaseCommand implements Listener { case "enderchest" -> org.bukkit.Sound.BLOCK_ENDER_CHEST_OPEN; default -> null; }; - + if (sound != null) { player.playSound(player.getLocation(), sound, 1.0f, 1.0f); } } - - /** - * 转换颜色代码 & -> § - */ + private String translateColor(String text) { - return text.replace("&", "§"); + return text == null ? "" : ChatColor.translateAlternateColorCodes('&', text); } } diff --git a/src/main/java/cn/infstar/essentialsC/commands/HelpCommand.java b/src/main/java/cn/infstar/essentialsC/commands/HelpCommand.java index 1b606fc..a25ad9f 100644 --- a/src/main/java/cn/infstar/essentialsC/commands/HelpCommand.java +++ b/src/main/java/cn/infstar/essentialsC/commands/HelpCommand.java @@ -7,10 +7,8 @@ import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.command.TabCompleter; import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; public class HelpCommand extends BaseCommand implements TabCompleter { @@ -44,7 +42,7 @@ public class HelpCommand extends BaseCommand implements TabCompleter { } @Override - protected boolean execute(@NotNull Player player, String[] args) { + protected boolean execute(Player player, String[] args) { return handleCommand(player, player, args); } @@ -223,11 +221,14 @@ public class HelpCommand extends BaseCommand implements TabCompleter { * 获取命令对应的权限节点 */ private String getPermissionForCommand(String command) { + if (command.equals("mobdrops")) { + return "essentialsc.mobdrops.enderman"; + } return "essentialsc.command." + command; } @Override - public List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) { + public List onTabComplete(CommandSender sender, Command command, String label, String[] args) { if (args.length == 1) { List completions = new ArrayList<>(); String partial = args[0].toLowerCase(); diff --git a/src/main/java/cn/infstar/essentialsC/commands/MobDropCommand.java b/src/main/java/cn/infstar/essentialsC/commands/MobDropCommand.java index 53bdd12..882b42b 100644 --- a/src/main/java/cn/infstar/essentialsC/commands/MobDropCommand.java +++ b/src/main/java/cn/infstar/essentialsC/commands/MobDropCommand.java @@ -5,6 +5,7 @@ import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; @@ -15,6 +16,19 @@ import java.util.Arrays; * /mobdrops - 打开控制菜单 */ 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() { super("essentialsc.mobdrops.enderman"); @@ -34,7 +48,7 @@ public class MobDropCommand extends BaseCommand { 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); diff --git a/src/main/java/cn/infstar/essentialsC/listeners/MobDropListener.java b/src/main/java/cn/infstar/essentialsC/listeners/MobDropListener.java index b33ac5b..dbe8be9 100644 --- a/src/main/java/cn/infstar/essentialsC/listeners/MobDropListener.java +++ b/src/main/java/cn/infstar/essentialsC/listeners/MobDropListener.java @@ -21,7 +21,6 @@ public class MobDropListener implements Listener { loadConfig(); // 注册监听器 - plugin.getServer().getPluginManager().registerEvents(this, plugin); } /** diff --git a/src/main/java/cn/infstar/essentialsC/listeners/MobDropMenuListener.java b/src/main/java/cn/infstar/essentialsC/listeners/MobDropMenuListener.java index 28f0f03..eb6d5d9 100644 --- a/src/main/java/cn/infstar/essentialsC/listeners/MobDropMenuListener.java +++ b/src/main/java/cn/infstar/essentialsC/listeners/MobDropMenuListener.java @@ -25,7 +25,7 @@ public class MobDropMenuListener implements Listener { @EventHandler 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; } @@ -71,7 +71,7 @@ public class MobDropMenuListener implements Listener { private void openMobDropMenu(Player player) { 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); ItemMeta endermanMeta = endermanItem.getItemMeta(); diff --git a/src/main/java/cn/infstar/essentialsC/listeners/ShulkerBoxListener.java b/src/main/java/cn/infstar/essentialsC/listeners/ShulkerBoxListener.java index afc9e55..5ded0cf 100644 --- a/src/main/java/cn/infstar/essentialsC/listeners/ShulkerBoxListener.java +++ b/src/main/java/cn/infstar/essentialsC/listeners/ShulkerBoxListener.java @@ -2,19 +2,28 @@ package cn.infstar.essentialsC.listeners; import cn.infstar.essentialsC.EssentialsC; import org.bukkit.Bukkit; +import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.block.ShulkerBox; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.block.Action; +import org.bukkit.event.inventory.ClickType; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.inventory.InventoryDragEvent; 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.InventoryHolder; +import org.bukkit.inventory.InventoryView; import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; import org.bukkit.inventory.meta.BlockStateMeta; -import org.jetbrains.annotations.NotNull; import java.util.HashMap; import java.util.Map; @@ -22,12 +31,9 @@ import java.util.Set; import java.util.UUID; public class ShulkerBoxListener implements Listener { - - private final EssentialsC plugin; - // 存储玩家打开的潜影盒:玩家UUID -> (原始物品快照, 当前物品引用) - private final Map openShulkerBoxes = new HashMap<>(); - - // 预定义所有潜影盒材质(性能优化) + + private static final int SHULKER_SIZE = 27; + private static final Set SHULKER_BOX_MATERIALS = Set.of( Material.SHULKER_BOX, Material.WHITE_SHULKER_BOX, @@ -47,310 +53,270 @@ public class ShulkerBoxListener implements Listener { Material.RED_SHULKER_BOX, Material.BLACK_SHULKER_BOX ); - - /** - * 潜影盒 Inventory Holder - 用于识别自定义 inventory - */ - private static class ShulkerBoxHolder implements org.bukkit.inventory.InventoryHolder { + + private final EssentialsC plugin; + private final Map openShulkerBoxes = new HashMap<>(); + + private static final class ShulkerBoxHolder implements InventoryHolder { private final Inventory inventory; - private final ItemStack shulkerBoxItem; - private final ItemStack[] currentContents; // 实时追踪的物品内容 - - ShulkerBoxHolder(ItemStack shulkerBoxItem, String title) { - this.shulkerBoxItem = shulkerBoxItem; - 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 - public @NotNull Inventory getInventory() { + public Inventory getInventory() { 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 final int preferredSlot; + + private OpenShulkerSession(ItemStack sourceItem, EquipmentSlot sourceHand, int preferredSlot) { + this.sourceItem = sourceItem; + this.sourceHand = sourceHand; + this.preferredSlot = preferredSlot; } } - - /** - * 潜影盒数据记录 - */ - private static class ShulkerBoxData { - int slotIndex; // 玩家背包中的槽位索引 - ItemStack originalSnapshot; // 打开时的物品快照(用于验证) - int totalItems; // 打开时的物品总数(用于防刷) - - ShulkerBoxData(int slot, ItemStack snapshot, int items) { - this.slotIndex = slot; - this.originalSnapshot = snapshot; - this.totalItems = items; - } - } - + public ShulkerBoxListener(EssentialsC plugin) { this.plugin = plugin; - plugin.getServer().getPluginManager().registerEvents(this, plugin); } - - @EventHandler + + @EventHandler(priority = EventPriority.HIGHEST) public void onPlayerInteract(PlayerInteractEvent event) { - // 只处理右键点击空气或方块的事件 if (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK) { return; } - + Player player = event.getPlayer(); - - // 检查权限 - if (!player.hasPermission("essentialsc.shulkerbox.open")) { + if (!player.isSneaking() || !player.hasPermission("essentialsc.shulkerbox.open")) { return; } - - ItemStack item = event.getItem(); - if (item == null || !isShulkerBox(item)) { + + if (openShulkerBoxes.containsKey(player.getUniqueId())) { return; } - - // 只有潜行+右键才打开潜影盒 - if (!player.isSneaking()) { + + EquipmentSlot hand = event.getHand() == EquipmentSlot.OFF_HAND ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND; + ItemStack sourceItem = getItemFromHand(player, hand); + if (!isShulkerBox(sourceItem)) { return; } - - // 取消默认行为(防止放置潜影盒) - event.setCancelled(true); - - // 查找物品在玩家背包中的槽位 - int slotIndex = -1; - for (int i = 0; i < player.getInventory().getSize(); i++) { - ItemStack invItem = player.getInventory().getItem(i); - if (invItem != null && invItem.isSimilar(item)) { - slotIndex = i; - break; + + if (!(sourceItem.getItemMeta() instanceof BlockStateMeta blockStateMeta)) { + return; + } + if (!(blockStateMeta.getBlockState() instanceof ShulkerBox shulkerBox)) { + return; + } + + event.setUseItemInHand(org.bukkit.event.Event.Result.DENY); + event.setUseInteractedBlock(org.bukkit.event.Event.Result.DENY); + + ItemStack sourceSnapshot = sourceItem.clone(); + plugin.getServer().getScheduler().runTask(plugin, () -> { + if (!player.isOnline() || openShulkerBoxes.containsKey(player.getUniqueId())) { + 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; } } - - // 打开潜影盒(传入槽位索引) - openShulkerBox(player, item, slotIndex); + + if (event.isShiftClick() && isShulkerBox(event.getCurrentItem())) { + 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 public void onInventoryClose(InventoryCloseEvent event) { if (!(event.getPlayer() instanceof Player player)) { return; } - - Inventory closedInventory = 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("=== 潜影盒关闭(数据已在点击时实时保存) ==="); + + commitOpenShulker(player, event.getInventory()); } - + @EventHandler - public void onInventoryOpen(org.bukkit.event.inventory.InventoryOpenEvent event) { - if (!(event.getPlayer() instanceof Player player)) { + public void onPlayerQuit(PlayerQuitEvent event) { + Player player = event.getPlayer(); + commitOpenShulker(player, player.getOpenInventory().getTopInventory()); + } + + @EventHandler + public void onPluginDisable(PluginDisableEvent event) { + if (event.getPlugin() != plugin) { return; } - - Inventory openedInventory = event.getInventory(); - if (openedInventory.getHolder(false) instanceof ShulkerBoxHolder) { - plugin.getLogger().info("[Open] ✅ 潜影盒 inventory 已打开"); + + for (Player player : Bukkit.getOnlinePlayers()) { + commitOpenShulker(player, player.getOpenInventory().getTopInventory()); } } - - @EventHandler(priority = org.bukkit.event.EventPriority.LOWEST) - public void onInventoryClickDebug(InventoryClickEvent event) { - if (!(event.getWhoClicked() instanceof Player player)) { + + private void commitOpenShulker(Player player, Inventory inventory) { + if (!(inventory.getHolder(false) instanceof ShulkerBoxHolder)) { return; } - - Inventory clickedInventory = event.getClickedInventory(); - if (clickedInventory == null) { + + OpenShulkerSession session = openShulkerBoxes.remove(player.getUniqueId()); + if (session == null) { return; } - - if (clickedInventory.getHolder(false) instanceof ShulkerBoxHolder) { - plugin.getLogger().info("[Click-LOWEST] 检测到点击事件 | 槽位: " + event.getSlot() + - " | 物品: " + (event.getCurrentItem() != null ? event.getCurrentItem().getType() : "null")); - } + + ItemStack updatedShulker = session.sourceItem.clone(); + writeInventoryBack(updatedShulker, inventory.getContents()); + 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; } - - Inventory clickedInventory = event.getClickedInventory(); - if (clickedInventory == null) { + + if (!(blockStateMeta.getBlockState() instanceof ShulkerBox shulkerBox)) { + plugin.getLogger().warning("Failed to save shulker box contents: block state is not a ShulkerBox."); return; } - - // 检查是否是潜影盒 inventory - if (!(clickedInventory.getHolder(false) instanceof ShulkerBoxHolder holder)) { - return; - } - - UUID playerId = player.getUniqueId(); - ShulkerBoxData data = openShulkerBoxes.get(playerId); - if (data == null) { - return; - } - - // 阻止嵌套潜影盒 - ItemStack clickedItem = event.getCurrentItem(); - if (clickedItem != null && isShulkerBox(clickedItem)) { - 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] ❌ 潜影盒物品不存在"); + + shulkerBox.getInventory().setContents(cloneContents(contents)); + blockStateMeta.setBlockState(shulkerBox); + shulkerItem.setItemMeta(blockStateMeta); + } + + private void restoreItemToPlayer(Player player, OpenShulkerSession session, ItemStack shulkerItem) { + PlayerInventory inventory = player.getInventory(); + + if (session.sourceHand == EquipmentSlot.OFF_HAND) { + if (isEmpty(inventory.getItemInOffHand())) { + inventory.setItemInOffHand(shulkerItem); return; } - - // 更新潜影盒的 BlockState - 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"); - } - }); - } - + } else if (session.preferredSlot >= 0 && isEmpty(inventory.getItem(session.preferredSlot))) { + inventory.setItem(session.preferredSlot, shulkerItem); + return; + } - - /** - * 检查物品是否为潜影盒(O(1) 时间复杂度) - */ - private boolean isShulkerBox(ItemStack item) { - return SHULKER_BOX_MATERIALS.contains(item.getType()); + Map leftovers = inventory.addItem(shulkerItem); + for (ItemStack leftover : leftovers.values()) { + player.getWorld().dropItemNaturally(player.getLocation(), leftover); + } } - - /** - * 打开潜影盒 - */ - private void openShulkerBox(Player player, ItemStack shulkerBox, int slotIndex) { - // 获取潜影盒的 BlockStateMeta - if (!(shulkerBox.getItemMeta() instanceof BlockStateMeta blockStateMeta)) { - return; + + private ItemStack[] cloneContents(ItemStack[] contents) { + 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(); } - - // 获取潜影盒的方块状态 - if (!(blockStateMeta.getBlockState() instanceof ShulkerBox shulkerBoxBlock)) { - return; - } - - // 创建物品快照(用于后续验证) - ItemStack snapshot = shulkerBox.clone(); - - // 计算当前物品总数(用于防刷检查) - int totalItems = 0; - for (ItemStack item : shulkerBoxBlock.getInventory().getContents()) { - if (item != null && !item.getType().isAir()) { - totalItems += item.getAmount(); - } - } - - // 获取潜影盒的自定义名称,如果没有则使用配置中的默认标题 - String title; - if (shulkerBox.hasItemMeta() && shulkerBox.getItemMeta().hasDisplayName()) { - // 使用潜影盒的自定义名称 - title = shulkerBox.getItemMeta().getDisplayName(); + return copied; + } + + private ItemStack getItemFromHand(Player player, EquipmentSlot hand) { + return hand == EquipmentSlot.OFF_HAND + ? player.getInventory().getItemInOffHand() + : player.getInventory().getItemInMainHand(); + } + + private void removeItemFromHand(Player player, EquipmentSlot hand) { + if (hand == EquipmentSlot.OFF_HAND) { + player.getInventory().setItemInOffHand(null); } else { - // 使用配置文件中的默认标题 - String defaultTitle = plugin.getConfig().getString("shulkerbox.default-title", ""); - if (defaultTitle != null && !defaultTitle.isEmpty()) { - // 转换颜色代码 & -> § - title = defaultTitle.replace('&', '§'); - } else { - // 如果配置为空,使用 "Shulker Box"(客户端会自动翻译) - title = "Shulker Box"; - } + player.getInventory().setItem(player.getInventory().getHeldItemSlot(), null); } - - // 创建 ShulkerBoxHolder(会自动创建 inventory) - ShulkerBoxHolder holder = new ShulkerBoxHolder(shulkerBox, title); - Inventory inventory = holder.getInventory(); - - // 复制潜影盒的内容到 inventory - ItemStack[] contents = shulkerBoxBlock.getInventory().getContents(); - for (int i = 0; i < 27 && i < contents.length; i++) { - inventory.setItem(i, contents[i]); + } + + private String resolveTitle(ItemStack shulkerBox) { + if (shulkerBox.hasItemMeta() && shulkerBox.getItemMeta().hasDisplayName()) { + return shulkerBox.getItemMeta().getDisplayName(); } - - // 记录玩家打开的潜影盒(保存槽位索引和快照) - openShulkerBoxes.put(player.getUniqueId(), new ShulkerBoxData(slotIndex, snapshot, totalItems)); - - // 打开 inventory - player.openInventory(inventory); + + String defaultTitle = plugin.getConfig().getString("shulkerbox.default-title", ""); + if (defaultTitle != null && !defaultTitle.isEmpty()) { + return ChatColor.translateAlternateColorCodes('&', defaultTitle); + } + return "Shulker Box"; + } + + private void sendNestedMessage(Player player) { + player.sendMessage(EssentialsC.getLangManager().getString("prefix") + + EssentialsC.getLangManager().getString("messages.shulkerbox-nested")); + } + + private boolean isShulkerBox(ItemStack item) { + return item != null && !item.getType().isAir() && SHULKER_BOX_MATERIALS.contains(item.getType()); + } + + private boolean isEmpty(ItemStack item) { + return item == null || item.getType().isAir(); } } diff --git a/src/main/resources/paper-plugin.yml b/src/main/resources/paper-plugin.yml index 03a41f2..c7363a6 100644 --- a/src/main/resources/paper-plugin.yml +++ b/src/main/resources/paper-plugin.yml @@ -5,7 +5,7 @@ version: ${version} main: cn.infstar.essentialsC.EssentialsC api-version: '1.21' load: POSTWORLD -folia-supported: true +folia-supported: false authors: [ Coldsmiles_7 ] website: www.infstar.cn @@ -62,6 +62,9 @@ permissions: essentialsc.command.repair: description: Allows use of /repair command default: op + essentialsc.command.repair.all: + description: Allows use of /repair all + default: op essentialsc.command.blocks: description: Allows use of /essc blocks command default: true @@ -99,6 +102,7 @@ permissions: essentialsc.command.reload: true essentialsc.command.feed: true essentialsc.command.repair: true + essentialsc.command.repair.all: true essentialsc.command.help: true essentialsc.shulkerbox.open: true essentialsc.mobdrops.enderman: true