GUI内物品数量叠加Bug排查求助
问题描述
我开发了基于Bukkit的Minecraft插件购买GUI,中间物品由其他类传入,玩家可通过按钮调整购买数量,点击钻石按钮确认时,若拥有足够doubloons即可获取物品。但出现异常:首次购买1个物品正常,再次打开GUI购买1个时,玩家背包物品总数变为3个,后续每次购买都会累加上次的购买数量,直到服务器重启才重置。我尝试多种方式重置amount变量但无效,相关代码如下:
package pro.jabo.jabo.gui; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.entity.Player; 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.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.plugin.java.JavaPlugin; import pro.jabo.jabo.utils.PlayerConfigUtils; import java.util.ArrayList; import java.util.List; import java.util.UUID; public class PurchaseItemGUI implements Listener { private final Inventory inventory; private int amount = 1; private final PlayerConfigUtils playerConfigUtils; private final UUID shopOwnerUUID; private final double pricePerItem; private final String shopName; private boolean isConfirming = false; public PurchaseItemGUI(JavaPlugin plugin, ItemStack item, Player player, UUID shopOwnerUUID, double pricePerItem, String shopName) { this.playerConfigUtils = new PlayerConfigUtils(plugin); this.shopOwnerUUID = shopOwnerUUID; this.pricePerItem = pricePerItem; this.shopName = shopName; this.inventory = Bukkit.createInventory(null, 27, ChatColor.GOLD + "Purchase " + item.getType()); Bukkit.getPluginManager().registerEvents(this, plugin); ItemStack itemToPurchase = new ItemStack(item); itemToPurchase.setAmount(amount); ItemMeta meta = itemToPurchase.getItemMeta(); meta.setDisplayName(ChatColor.GREEN + item.getType().name()); List<String> lore = new ArrayList<>(); lore.add(ChatColor.WHITE + "Amount: " + amount); lore.add(ChatColor.WHITE + "Total Price: " + pricePerItem * amount); meta.setLore(lore); itemToPurchase.setItemMeta(meta); inventory.setItem(13, itemToPurchase); setControlButtons(); } private void setControlButtons() { inventory.setItem(10, createButton(Material.REDSTONE, ChatColor.RED + "-10")); inventory.setItem(11, createButton(Material.REDSTONE, ChatColor.RED + "-1")); inventory.setItem(15, createButton(Material.EMERALD, ChatColor.GREEN + "+1")); inventory.setItem(16, createButton(Material.EMERALD, ChatColor.GREEN + "+10")); inventory.setItem(22, createButton(Material.DIAMOND, ChatColor.BLUE + "CONFIRM")); } private ItemStack createButton(Material material, String name) { ItemStack item = new ItemStack(material); ItemMeta meta = item.getItemMeta(); meta.setDisplayName(name); item.setItemMeta(meta); return item; } public Inventory getInventory() { return inventory; } public String getShopName() { return shopName; } public UUID getShopOwnerUUID() { return shopOwnerUUID; } @EventHandler public void onButtonClick(InventoryClickEvent event) { if (!event.getView().getTitle().startsWith(ChatColor.GOLD + "Purchase ")) { return; } event.setCancelled(true); Player player = (Player) event.getWhoClicked(); ItemStack clickedItem = event.getCurrentItem(); if (clickedItem == null || clickedItem.getType() == Material.AIR) { return; } if (clickedItem.getType() == Material.REDSTONE && clickedItem.getItemMeta().getDisplayName().equals(ChatColor.RED + "-10")) { amount = Math.max(1, amount - 10); } else if (clickedItem.getType() == Material.REDSTONE && clickedItem.getItemMeta().getDisplayName().equals(ChatColor.RED + "-1")) { amount = Math.max(1, amount - 1); } else if (clickedItem.getType() == Material.EMERALD && clickedItem.getItemMeta().getDisplayName().equals(ChatColor.GREEN + "+1")) { amount++; } else if (clickedItem.getType() == Material.EMERALD && clickedItem.getItemMeta().getDisplayName().equals(ChatColor.GREEN + "+10")) { amount += 10; } else if (clickedItem.getType() == Material.DIAMOND && clickedItem.getItemMeta().getDisplayName().equals(ChatColor.BLUE + "CONFIRM")) { if (!isConfirming) { isConfirming = true; confirmPurchase(player); isConfirming = false; } } updateGUI(); } private void updateGUI() { ItemStack item = inventory.getItem(13); if (item != null) { ItemMeta meta = item.getItemMeta(); if (meta != null) { List<String> lore = new ArrayList<>(); lore.add(ChatColor.WHITE + "Amount: " + amount); lore.add(ChatColor.WHITE + "Total Price: " + pricePerItem * amount); meta.setLore(lore); item.setItemMeta(meta); inventory.setItem(13, item); } } } private void confirmPurchase(Player player) { double totalCost = pricePerItem * amount; FileConfiguration playerConfig = playerConfigUtils.getPlayerConfig(player.getUniqueId()); int playerDoubloons = playerConfig.getInt("doubloons"); if (playerDoubloons < totalCost) { player.sendMessage(ChatColor.RED + "You do not have enough doubloons."); player.closeInventory(); return; } playerConfig.set("doubloons", playerDoubloons - totalCost); FileConfiguration shopOwnerConfig = playerConfigUtils.getPlayerConfig(shopOwnerUUID); int shopOwnerDoubloons = shopOwnerConfig.getInt("doubloons"); shopOwnerConfig.set("doubloons", shopOwnerDoubloons + totalCost); ItemStack purchasedItem = new ItemStack(inventory.getItem(13)); purchasedItem.setAmount(amount); player.getInventory().addItem(purchasedItem); player.sendMessage(ChatColor.GREEN + "Purchase successful!"); playerConfigUtils.savePlayerConfig(player.getUniqueId(), playerConfig); playerConfigUtils.savePlayerConfig(shopOwnerUUID, shopOwnerConfig); player.updateInventory(); amount = 1; updateGUI(); player.closeInventory(); } }
问题根源
核心问题是每次创建PurchaseItemGUI实例时,都会注册一个新的事件监听器,且旧的监听器不会被注销。当玩家多次打开GUI后,同一个点击事件会被所有已注册的监听器实例处理:
- 第一次打开GUI,生成实例A并注册监听器A;
- 第二次打开GUI,生成实例B并注册监听器B;
- 点击确认按钮时,监听器A和B都会执行
confirmPurchase方法,两个实例的amount都会用来给玩家添加物品,导致数量累加; - 服务器重启前,所有旧实例的监听器一直存在,每次购买都会触发所有实例的逻辑,数量越累越多。
修复方案
方案:分离监听器与GUI实例(推荐)
创建一个全局单例监听器处理所有购买GUI的点击事件,通过玩家或Inventory映射到对应的PurchaseItemGUI实例,避免重复注册监听器。
修改后的代码如下:
package pro.jabo.jabo.gui; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.plugin.java.JavaPlugin; import pro.jabo.jabo.utils.PlayerConfigUtils; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; // 全局单例监听器 public class PurchaseGUIListener implements Listener { private static final Map<Player, PurchaseItemGUI> playerGUIMap = new HashMap<>(); public static void register(JavaPlugin plugin) { Bukkit.getPluginManager().registerEvents(new PurchaseGUIListener(), plugin); } public static void addPlayerGUI(Player player, PurchaseItemGUI gui) { playerGUIMap.put(player, gui); } @EventHandler public void onButtonClick(InventoryClickEvent event) { if (!(event.getWhoClicked() instanceof Player player)) { return; } PurchaseItemGUI gui = playerGUIMap.get(player); if (gui == null || !event.getView().getTitle().equals(gui.getInventory().getTitle())) { return; } event.setCancelled(true); gui.handleClick(event); } @EventHandler public void onInventoryClose(InventoryCloseEvent event) { if (!(event.getPlayer() instanceof Player player)) { return; } playerGUIMap.remove(player); } } // GUI逻辑类,不再实现Listener public class PurchaseItemGUI { private final Inventory inventory; private int amount = 1; private final PlayerConfigUtils playerConfigUtils; private final UUID shopOwnerUUID; private final double pricePerItem; private final String shopName; private boolean isConfirming = false; public PurchaseItemGUI(JavaPlugin plugin, ItemStack item, Player player, UUID shopOwnerUUID, double pricePerItem, String shopName) { this.playerConfigUtils = new PlayerConfigUtils(plugin); this.shopOwnerUUID = shopOwnerUUID; this.pricePerItem = pricePerItem; this.shopName = shopName; this.inventory = Bukkit.createInventory(null, 27, ChatColor.GOLD + "Purchase " + item.getType()); // 将当前GUI实例与玩家绑定 PurchaseGUIListener.addPlayerGUI(player, this); ItemStack itemToPurchase = new ItemStack(item); itemToPurchase.setAmount(amount); ItemMeta meta = itemToPurchase.getItemMeta(); meta.setDisplayName(ChatColor.GREEN + item.getType().name()); List<String> lore = new ArrayList<>(); lore.add(ChatColor.WHITE + "Amount: " + amount); lore.add(ChatColor.WHITE + "Total Price: " + pricePerItem * amount); meta.setLore(lore); itemToPurchase.setItemMeta(meta); inventory.setItem(13, itemToPurchase); setControlButtons(); } private void setControlButtons() { inventory.setItem(10, createButton(Material.REDSTONE, ChatColor.RED + "-10")); inventory.setItem(11, createButton(Material.REDSTONE, ChatColor.RED + "-1")); inventory.setItem(15, createButton(Material.EMERALD, ChatColor.GREEN + "+1")); inventory.setItem(16, createButton(Material.EMERALD, ChatColor.GREEN + "+10")); inventory.setItem(22, createButton(Material.DIAMOND, ChatColor.BLUE + "CONFIRM")); } private ItemStack createButton(Material material, String name) { ItemStack item = new ItemStack(material); ItemMeta meta = item.getItemMeta(); meta.setDisplayName(name); item.setItemMeta(meta); return item; } public Inventory getInventory() { return inventory; } public String getShopName() { return shopName; } public UUID getShopOwnerUUID() { return shopOwnerUUID; } // 处理点击事件的方法,由全局监听器调用 public void handleClick(InventoryClickEvent event) { Player player = (Player) event.getWhoClicked(); ItemStack clickedItem = event.getCurrentItem(); if (clickedItem == null || clickedItem.getType() == Material.AIR) { return; } if (clickedItem.getType() == Material.REDSTONE && clickedItem.getItemMeta().getDisplayName().equals(ChatColor.RED + "-10")) { amount = Math.max(1, amount - 10); } else if (clickedItem.getType() == Material.REDSTONE && clickedItem.getItemMeta().getDisplayName().equals(ChatColor.RED + "-1")) { amount = Math.max(1, amount - 1); } else if (clickedItem.getType() == Material.EMERALD && clickedItem.getItemMeta().getDisplayName().equals(ChatColor.GREEN + "+1")) { amount++; } else if (clickedItem.getType() == Material.EMERALD && clickedItem.getItemMeta().getDisplayName().equals(ChatColor.GREEN + "+10")) { amount += 10; } else if (clickedItem.getType() == Material.DIAMOND && clickedItem.getItemMeta().getDisplayName().equals(ChatColor.BLUE + "CONFIRM")) { if (!isConfirming) { isConfirming = true; confirmPurchase(player); isConfirming = false; } } updateGUI(); } private void updateGUI() { ItemStack item = inventory.getItem(13); if (item != null) { ItemMeta meta = item.getItemMeta(); if (meta != null) { List<String> lore = new ArrayList<>(); lore.add(ChatColor.WHITE + "Amount: " + amount); lore.add(ChatColor.WHITE + "Total Price: " + pricePerItem * amount); meta.setLore(lore); item.setItemMeta(meta); inventory.setItem(13, item); } } } private void confirmPurchase(Player player) { double totalCost = pricePerItem * amount; FileConfiguration playerConfig = playerConfigUtils.getPlayerConfig(player.getUniqueId()); int playerDoubloons = playerConfig.getInt("doubloons"); if (playerDoubloons < totalCost) { player.sendMessage(ChatColor.RED + "你没有足够的金币!"); player.closeInventory(); return; } playerConfig.set("doubloons", playerDoubloons - totalCost); FileConfiguration shopOwnerConfig = playerConfigUtils.getPlayerConfig(shopOwnerUUID); int shopOwnerDoubloons = shopOwnerConfig.getInt("doubloons"); shopOwnerConfig.set("doubloons", shopOwnerDoubloons + totalCost); ItemStack purchasedItem = new ItemStack(inventory.getItem(13)); purchasedItem.setAmount(amount); player.getInventory().addItem(purchasedItem); player.sendMessage(ChatColor.GREEN + "购买成功!"); playerConfigUtils.savePlayerConfig(player.getUniqueId(), playerConfig); playerConfigUtils.savePlayerConfig(shopOwnerUUID, shopOwnerConfig); player.updateInventory(); amount = 1; updateGUI(); player.closeInventory(); } }
额外注意事项
- 在插件主类的
onEnable方法中,需要先注册全局监听器:
@Override public void onEnable() { PurchaseGUIListener.register(this); // 其他初始化逻辑 }
- 这样每次打开GUI时,只会将当前玩家与GUI实例绑定,关闭GUI时自动解除绑定,每个点击事件只会被对应的GUI实例处理,不会出现多实例累加的问题。
内容的提问来源于stack exchange,提问作者The Clashers
相关产品推荐
相关产品推荐

