Forge 1.20.2 GUI按钮物品可拖拽复制与索引越界问题求助
Forge 1.20.2 Pixelmon附属GUI模组问题排查与解决
问题概述
我正在为Pixelmon附属模组在Forge 1.20.2中开发一款类Spigot插件的GUI,功能基本正常,但存在两个问题:
- 点击GUI内普通物品无法拖拽,但点击「上一页/下一页」按钮时,可抓取按钮物品并复制,还会清空玩家背包;
- 点击玩家背包或GUI外的槽位时,控制台抛出
IndexOutOfBoundsException(索引59超出长度54)。
重写的clicked方法代码
public void clicked(int slotId, int dragType, ClickType clickType, Player playerEntity) { if (!playerEntity.level().isClientSide) { if (slotId >= this.container.getContainerSize()) return; ItemStack clickedItem = this.container.getItem(slotId); GUIConfigManager.GUIConfig guiConfig = this.guiConfigManager.guiConfigs.get(this.guiConfigKey); ItemStack nextButtonItemStack = (guiConfig.nextButton != null) ? createButtonStack(guiConfig.nextButton) : ItemStack.EMPTY; ItemStack backButtonItemStack = (guiConfig.backButton != null) ? createButtonStack(guiConfig.backButton) : ItemStack.EMPTY; boolean isNextButton = (!nextButtonItemStack.isEmpty() && ItemStack.isSameItemSameTags(clickedItem, nextButtonItemStack) && this.currentPage < this.totalPages); boolean isBackButton = (!backButtonItemStack.isEmpty() && ItemStack.isSameItemSameTags(clickedItem, backButtonItemStack) && this.currentPage > 1); if (isNextButton) { this.currentPage++; displayPage(this.currentPage); return; } if (isBackButton) { this.currentPage--; displayPage(this.currentPage); return; } if (clickedItem.hasTag() && clickedItem.getTag().contains("SetIdentifier")) { String setIdentifier = clickedItem.getTag().getString("SetIdentifier"); this.player.closeContainer(); SimpleMenuProvider simpleMenuProvider = new SimpleMenuProvider((id, inventory, player) -> new CustomSetChestMenu(id, inventory, this.player, this.guiConfigManager, this.setsConfigManager, setIdentifier), Component.literal(guiConfig.guiName)); this.player.openMenu(simpleMenuProvider); return; } ItemStack mainMenuItemStack = (guiConfig.mainMenuItem != null) ? createMainMenuItemStack(guiConfig) : ItemStack.EMPTY; boolean isMainMenuButton = (!mainMenuItemStack.isEmpty() && ItemStack.isSameItemSameTags(clickedItem, mainMenuItemStack)); if (isMainMenuButton) { this.player.closeContainer(); SimpleMenuProvider simpleMenuProvider = new SimpleMenuProvider((id, inventory, player) -> new CustomChestMenu(id, inventory, this.player, this.guiConfigManager, this.setsConfigManager, "main"), Component.literal("Main GUI")); this.player.openMenu(simpleMenuProvider); return; } if (clickType == ClickType.PICKUP || clickType == ClickType.QUICK_MOVE) return; } super.clicked(slotId, dragType, clickType, playerEntity); } public ItemStack quickMoveStack(Player player, int index) { return ItemStack.EMPTY; }
控制台报错信息
[20Mar2024 00:42:16.385] [Server thread/ERROR] [net.minecraft.network.protocol.PacketUtils/]: Failed to handle packet net.minecraft.network.protocol.game.ServerboundContainerClickPacket@2c62ee95, suppressing error java.lang.IndexOutOfBoundsException: Index 59 out of bounds for length 54 at jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64) ~[?:?] {} at jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:70) ~[?:?] {} at jdk.internal.util.Preconditions.checkIndex(Preconditions.java:266) ~[?:?] {} at java.util.Objects.checkIndex(Objects.java:361) ~[?:?] {re:mixin} at java.util.ArrayList.get(ArrayList.java:427) ~[?:?] {re:mixin,re:computing_frames} at net.minecraft.core.NonNullList.get(NonNullList.java:47) ~[server-1.20.2-20230921.100330-srg.jar%23167!/:?] {re:classloading} at net.minecraft.world.inventory.AbstractContainerMenu.m_38853_(AbstractContainerMenuMixin.java:287) ~[server-1.20.2-20230921.100330-srg.jar%23167!/:?] {re:classloading,pl:accesstransformer:B,pl:mixin:APP:mixins.arclight.core.json:world.inventory.AbstractContainerMenuMixin,pl:mixin:A} at net.minecraft.server.network.ServerGamePacketListenerImpl.m_5914_(ServerPlayNetHandlerMixin.java:3076) ~[server-1.20.2-20230921.100330-srg.jar%23167!/:?] {re:classloading,pl:accesstransformer:B,pl:mixin:APP:mixins.arclight.core.json:network.ServerPlayNetHandlerMixin,pl:mixin:APP:mixins.arclight.impl.forge.optimization.json:network.ServerGamePacketListenerImplMixin_Optimize,pl:mixin:A} at net.minecraft.network.protocol.game.ServerboundContainerClickPacket.m_5797_(ServerboundContainerClickPacket.java:56) ~[server-1.20.2-20230921.100330-srg.jar%23167!/:?] {re:classloading} at net.minecraft.network.protocol.game.ServerboundContainerClickPacket.m_5797_(ServerboundContainerClickPacket.java:12) ~[server-1.20.2-20230921.100330-srg.jar%23167!/:?] {re:classloading} at net.minecraft.network.protocol.PacketUtils.md8880c7$lambda$ensureRunningOnSameThread$0$0(PacketThreadUtilMixin.java:537) ~[server-1.20.2-20230921.100330-srg.jar%23167!/:?] {re:classloading,pl:mixin:APP:mixins.arclight.core.json:network.protocol.PacketThreadUtilMixin,pl:mixin:A} at net.minecraft.server.TickTask.run(TickTask.java:18) ~[server-1.20.2-20230921.100330-srg.jar%23167!/:?] {re:classloading} at net.minecraft.util.thread.BlockableEventLoop.m_6367_(BlockableEventLoop.java:156) ~[server-1.20.2-20230921.100330-srg.jar%23167!/:?] {re:classloading,pl:accesstransformer:B} at net.minecraft.util.thread.ReentrantBlockableEventLoop.m_6367_(ReentrantBlockableEventLoop.java:23) ~[server-1.20.2-20230921.100330-srg.jar%23167!/:?] {re:classloading} at net.minecraft.server.MinecraftServer.m_6367_(MinecraftServerMixin.java:765) ~[server-1.20.2-20230921.100330-srg.jar%23167!/:?] {re:classloading,pl:accesstransformer:B,pl:mixin:APP:mixins.arclight.core.json:server.MinecraftServerMixin,pl:mixin:A} at net.minecraft.server.MinecraftServer.m_6367_(MinecraftServerMixin.java:160) ~[server-1.20.2-20230921.100330-srg.jar%23167!/:?] {re:classloading,pl:accesstransformer:B,pl:mixin:APP:mixins.arclight.core.json:server.MinecraftServerMixin,pl:mixin:A} at net.minecraft.util.thread.BlockableEventLoop.m_7245_(BlockableEventLoop.java:130) ~[server-1.20.2-20230921.100330-srg.jar%23167!/:?] {re:classloading,pl:accesstransformer:B} at net.minecraft.server.MinecraftServer.m_129961_(MinecraftServerMixin.java:748) ~[server-1.20.2-20230921.100330-srg.jar%23167!/:?] {re:classloading,pl:accesstransformer:B,pl:mixin:APP:mixins.arclight.core.json:server.MinecraftServerMixin,pl:mixin:A} at net.minecraft.server.MinecraftServer.m_7245_(MinecraftServerMixin.java:742) ~[server-1.20.2-20230921.100330-srg.jar%23167!/:?] {re:classloading,pl:accesstransformer:B,pl:mixin:APP:mixins.arclight.core.json:server.MinecraftServerMixin,pl:mixin:A} at net.minecraft.util.thread.BlockableEventLoop.m_18699_(BlockableEventLoop.java:115) ~[server-1.20.2-20230921.100330-srg.jar%23167!/:?] {re:classloading,pl:accesstransformer:B} at net.minecraft.server.MinecraftServer.m_130012_(MinecraftServerMixin.java:727) ~[server-1.20.2-20230921.100330-srg.jar%23167!/:?] {re:classloading,pl:accesstransformer:B,pl:mixin:APP:mixins.arclight.core.json:server.MinecraftServerMixin,pl:mixin:A} at net.minecraft.server.MinecraftServer.m_130011_(MinecraftServerMixin.java:2081) ~[server-1.20.2-20230921.100330-srg.jar%23167!/:?] {re:classloading,pl:accesstransformer:B,pl:mixin:APP:mixins.arclight.core.json:server.MinecraftServerMixin,pl:mixin:A} at net.minecraft.server.MinecraftServer.m_206580_(MinecraftServerMixin.java:249) ~[server-1.20.2-20230921.100330-srg.jar%23167!/:?] {re:classloading,pl:accesstransformer:B,pl:mixin:APP:mixins.arclight.core.json:server.MinecraftServerMixin,pl:mixin:A} at java.lang.Thread.run(Thread.java:840) ~[?:?] {re:mixin,re:mixin}
问题解决方案
问题1:GUI物品拖拽异常与按钮复制/背包清空
- 普通物品无法拖拽:代码中直接拦截所有
ClickType.PICKUP和QUICK_MOVE操作,阻断了正常交互。若需允许普通物品拖拽,移除该拦截;若需禁止普通物品移动,需通过物品标记精准拦截,而非阻断所有拾取操作。 - 按钮被抓取复制+背包清空:按钮未标记为不可移动,且点击后仍会触发父类
clicked方法的物品交互逻辑。需在按钮点击后直接返回,不调用父类方法,同时给按钮物品添加不可移动标记。
修改后的核心逻辑:
public void clicked(int slotId, int dragType, ClickType clickType, Player playerEntity) { if (slotId >= this.container.getContainerSize()) return; ItemStack clickedItem = this.container.getItem(slotId); GUIConfigManager.GUIConfig guiConfig = this.guiConfigManager.guiConfigs.get(this.guiConfigKey); // 按钮判断与处理 ItemStack nextButtonItemStack = (guiConfig.nextButton != null) ? createButtonStack(guiConfig.nextButton) : ItemStack.EMPTY; ItemStack backButtonItemStack = (guiConfig.backButton != null) ? createButtonStack(guiConfig.backButton) : ItemStack.EMPTY; boolean isNextButton = (!nextButtonItemStack.isEmpty() && ItemStack.isSameItemSameTags(clickedItem, nextButtonItemStack) && this.currentPage < this.totalPages); boolean isBackButton = (!backButtonItemStack.isEmpty() && ItemStack.isSameItemSameTags(clickedItem, backButtonItemStack) && this.currentPage > 1); boolean isMainMenuButton = false; if (guiConfig.mainMenuItem != null) { ItemStack mainMenuItemStack = createMainMenuItemStack(guiConfig); isMainMenuButton = ItemStack.isSameItemSameTags(clickedItem, mainMenuItemStack); } // 处理按钮/菜单跳转,直接返回不触发父类逻辑 if (isNextButton) { this.currentPage++; displayPage(this.currentPage); return; } if (isBackButton) { this.currentPage--; displayPage(this.currentPage); return; } if (isMainMenuButton) { this.player.closeContainer(); SimpleMenuProvider simpleMenuProvider = new SimpleMenuProvider((id, inventory, player) -> new CustomChestMenu(id, inventory, this.player, this.guiConfigManager, this.setsConfigManager, "main"), Component.literal("Main GUI")); this.player.openMenu(simpleMenuProvider); return; } if (clickedItem.hasTag() && clickedItem.getTag().contains("SetIdentifier")) { String setIdentifier = clickedItem.getTag().getString("SetIdentifier"); this.player.closeContainer(); SimpleMenuProvider simpleMenuProvider = new SimpleMenuProvider((id, inventory, player) -> new CustomSetChestMenu(id, inventory, this.player, this.guiConfigManager, this.setsConfigManager, setIdentifier), Component.literal(guiConfig.guiName)); this.player.openMenu(simpleMenuProvider); return; } // 仅拦截无标记的普通物品拖拽 if (!playerEntity.level().isClientSide) { if ((clickType == ClickType.PICKUP || clickType == ClickType.QUICK_MOVE) && (!clickedItem.hasTag() || !clickedItem.getTag().contains("IsGUIBotton"))) { return; } } super.clicked(slotId, dragType, clickType, playerEntity); }
按钮物品添加不可移动标记:
private ItemStack createButtonStack(GUIConfig.ButtonConfig config) { ItemStack stack = new ItemStack(Items.valueOf(config.item)); stack.setHoverName(Component.literal(config.displayName)); stack.getOrCreateTag().putBoolean("IsGUIBotton", true); stack.getOrCreateTag().putBoolean("Unbreakable", true); return stack; }
问题2:IndexOutOfBoundsException(索引59超出长度54)
报错原因是点击背包槽位时,仅在服务端判断槽位越界后返回,但客户端逻辑未处理,且父类clicked方法仍被调用,导致服务端尝试访问不存在的槽位。
解决方法:将槽位越界判断移到方法最开头,无论客户端还是服务端,点击非GUI槽位直接返回,不执行后续逻辑:
public void clicked(int slotId, int dragType, ClickType clickType, Player playerEntity) { // 优先判断槽位是否属于GUI容器,越界直接返回 if (slotId >= this.container.getContainerSize()) { return; } // 原有逻辑... }
同时检查quickMoveStack方法,若需禁止快速移动可保持返回ItemStack.EMPTY,若要支持则需实现正确的物品转移逻辑,避免槽位越界。
内容的提问来源于stack exchange,提问作者Shadow
相关产品推荐
相关产品推荐

