Minecraft插件中PlayerInteractEvent触发两次致物品重复使用问题
问题:Minecraft 1.16.5插件中FlashDust物品使用时事件重复触发导致物品重复消耗
背景
我正在开发Minecraft 1.16.5版本的插件,添加了特殊物品FlashDust,使用后会对范围内玩家施加效果。代码中注册了PlayerInteractEvent事件,尝试通过检查event.getHand()来限制物品的使用手部。
问题描述
玩家使用该物品时,PlayerInteractEvent事件会连续触发两次,导致物品被重复消耗,尽管已通过EquipmentSlot.HAND进行手部过滤仍无法解决。我怀疑事件同时触发主副手,但手部检查无效。
代码示例
FlashDust类代码
package so.max1soft.customitems.dusts; import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.block.Action; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.util.Vector; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import org.bukkit.Particle; import java.util.HashMap; import java.util.List; import java.util.Map; public class FlashDust implements Listener { private final JavaPlugin plugin; private final Map<Player, Long> lastUse = new HashMap<>(); public FlashDust(JavaPlugin plugin) { this.plugin = plugin; } public static void register(JavaPlugin plugin) { plugin.getServer().getPluginManager().registerEvents(new FlashDust(plugin), plugin); } public static ItemStack createItem(JavaPlugin plugin) { FileConfiguration config = plugin.getConfig(); ItemStack item = new ItemStack(Material.SUGAR); ItemMeta meta = item.getItemMeta(); if (meta == null) { return item; } String name = config.getString("dusts.flash.name"); List<String> lore = config.getStringList("dusts.flash.lore"); meta.setDisplayName(name); meta.setLore(lore); List<String> enchantments = config.getStringList("dusts.flash.enchantments"); for (String enchantString : enchantments) { String[] parts = enchantString.split(":"); if (parts.length != 2) { plugin.getLogger().warning("Invalid enchantment format: " + enchantString); continue; } String enchantmentName = parts[0].toUpperCase(); Enchantment enchantment = Enchantment.getByKey(NamespacedKey.minecraft(enchantmentName.toLowerCase())); if (enchantment == null) { plugin.getLogger().warning("Unknown enchantment: " + enchantmentName); continue; } try { int level = Integer.parseInt(parts[1]); meta.addEnchant(enchantment, level, true); } catch (NumberFormatException e) { plugin.getLogger().warning("Invalid enchantment level: " + parts[1] + " for enchantment " + enchantmentName); } } meta.addItemFlags(ItemFlag.HIDE_ENCHANTS); item.setItemMeta(meta); return item; } @EventHandler public void onPlayerUseFlasDust(PlayerInteractEvent event) { if (event.getHand() == EquipmentSlot.HAND) { if (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK) { return; } Player player = event.getPlayer(); ItemStack item = event.getItem(); if (item == null || item.getType() != Material.SUGAR) { return; } ItemMeta meta = item.getItemMeta(); if (meta == null || !meta.hasDisplayName() || !meta.getDisplayName().equals(plugin.getConfig().getString("dusts.flash.name"))) { return; } if (lastUse.containsKey(player) && lastUse.get(player) + 1000L > System.currentTimeMillis()) { return; } if (!isCooldownActive(player)) { player.sendMessage("You are on cooldown!"); return; } setCooldown(player); event.setCancelled(true); processFlasDustUsage(player, item); } } private boolean isCooldownActive(Player player) { Long lastInteract = lastUse.get(player); if (lastInteract == null) { return true; } return System.currentTimeMillis() - lastInteract >= 20000L; } private void setCooldown(Player player) { lastUse.put(player, System.currentTimeMillis()); } private void processFlasDustUsage(Player player, ItemStack item) { FileConfiguration config = plugin.getConfig(); String itemName = config.getString("dusts.flash.name"); List<String> expectedLore = config.getStringList("dusts.flash.lore"); ItemMeta meta = item.getItemMeta(); if (!meta.getDisplayName().equals(itemName) || !meta.hasLore() || !meta.getLore().equals(expectedLore)) { plugin.getLogger().info("Item does not match expected name or lore, cancelling event."); return; } if (item.getAmount() > 1) { item.setAmount(item.getAmount() - 1); } else { player.getInventory().remove(item); } applyEffectsAndParticles(player); } private void applyEffectsAndParticles(Player player) { Vector initialLocation = player.getLocation().toVector(); double fixedY = player.getLocation().getY(); new BukkitRunnable() { double radius = 0; final double maxRadius = 7; final double step = 0.5; @Override public void run() { radius += step; if (radius > maxRadius) { cancel(); return; } for (double angle = 0; angle < 360; angle += 10) { double radians = Math.toRadians(angle); double x = radius * Math.cos(radians); double z = radius * Math.sin(radians); Vector particleLocation = initialLocation.clone().add(new Vector(x, 0, z)); player.getWorld().spawnParticle(Particle.CLOUD, particleLocation.getX(), fixedY + 0.5, particleLocation.getZ(), 1, 0, 0, 0, 0); } for (Player nearbyPlayer : player.getWorld().getPlayers()) { if (nearbyPlayer.equals(player)) continue; if (nearbyPlayer.getLocation().distance(initialLocation.toLocation(player.getWorld())) <= radius) { if (!nearbyPlayer.hasPotionEffect(PotionEffectType.BLINDNESS)) { nearbyPlayer.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, 7 * 20, 0, false, false)); nearbyPlayer.addPotionEffect(new PotionEffect(PotionEffectType.POISON, 3 * 20, 0, false, false)); nearbyPlayer.addPotionEffect(new PotionEffect(PotionEffectType.SLOW, 7 * 20, 0, false, false)); nearbyPlayer.addPotionEffect(new PotionEffect(PotionEffectType.GLOWING, 7 * 20, 0, false, false)); } } } } }.runTaskTimer(plugin, 0, 2); } }
Main类代码
public class Main extends JavaPlugin { @Override public void onEnable() { saveDefaultConfig(); FileConfiguration config = getConfig(); // Отладочная информация getLogger().info("Спавн Брейкер шанс: " + config.getDouble("spawnbreak.chance")); FlashDust.register(this); ZenithDust.register(this); RadiusPickaxe.register(this); AutoSmeltingPickaxe.register(this); MagnetPickaxe.register(this); CombinedPickaxe.register(this); AutoSmeltingShovel.register(this); SpawnerBreaker.register(this); getCommand("socustomitems").setExecutor(this); getCommand("socustomitems").setTabCompleter(new CustomTabCompleter()); } @Override public void onDisable() { } @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { if (command.getName().equalsIgnoreCase("socustomitems") && args.length == 3 && args[0].equalsIgnoreCase("give")) { if (sender.hasPermission("socustomitems.give")) { Player target = getServer().getPlayer(args[1]); if (target != null) { String pickaxeType = args[2]; if (pickaxeType.equalsIgnoreCase("radius")) { target.getInventory().addItem(RadiusPickaxe.createItem(this)); } else if (pickaxeType.equalsIgnoreCase("flame")) { target.getInventory().addItem(AutoSmeltingPickaxe.createItem(this)); } else if (pickaxeType.equalsIgnoreCase("magnet")) { target.getInventory().addItem(MagnetPickaxe.createItem(this)); } else if (pickaxeType.equalsIgnoreCase("combo")) { target.getInventory().addItem(CombinedPickaxe.createItem(this)); } else if (pickaxeType.equalsIgnoreCase("flameshovel")) { target.getInventory().addItem(AutoSmeltingShovel.createItem(this)); } else if (pickaxeType.equalsIgnoreCase("spawnbreaker")) { target.getInventory().addItem(SpawnerBreaker.createItem(this)); } else if (pickaxeType.equalsIgnoreCase("flashdust")) { target.getInventory().addItem(FlashDust.createItem(this)); } else if (pickaxeType.equalsIgnoreCase("zenithdust")) { target.getInventory().addItem(ZenithDust.createItem(this)); } else { sender.sendMessage("Типы: radius, flame, magnet, combo, flameshovel, spawnbreaker, flashdust, zenithdust"); } return true; } else { sender.sendMessage("Такого кубхеда не существует!"); } } } return false; } }
解决方案
问题根源
在Minecraft 1.16.5的Spigot/Bukkit API中,PlayerInteractEvent会分别为主手(EquipmentSlot.HAND)和副手(EquipmentSlot.OFF_HAND)各触发一次事件。你的代码中手部判断的逻辑缩进存在隐患,且冷却判断逻辑混乱,导致两次事件都能通过验证,进而重复消耗物品。
修复步骤
统一事件过滤逻辑
合并手部和动作判断,确保只处理主手的右键点击事件,避免无效事件进入后续流程。简化冷却判断逻辑
原代码中存在双重冷却检查(1秒和20秒),逻辑矛盾,合并为统一的20秒冷却判断,确保单次使用后立即拦截后续重复事件。明确阻止物品重复使用
处理完成后调用event.setUseItemInHand(Event.Result.DENY),彻底阻止事件的后续处理。
修复后的onPlayerUseFlasDust方法
@EventHandler public void onPlayerUseFlasDust(PlayerInteractEvent event) { // 仅处理主手右键点击事件 if (event.getHand() != EquipmentSlot.HAND || (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK)) { return; } Player player = event.getPlayer(); ItemStack item = event.getItem(); // 验证物品是否为FlashDust if (item == null || item.getType() != Material.SUGAR) { return; } ItemMeta meta = item.getItemMeta(); String expectedName = plugin.getConfig().getString("dusts.flash.name"); if (meta == null || !meta.hasDisplayName() || !meta.getDisplayName().equals(expectedName)) { return; } // 检查冷却 long currentTime = System.currentTimeMillis(); if (lastUse.containsKey(player)) { long timeSinceLastUse = currentTime - lastUse.get(player); if (timeSinceLastUse < 20000L) { player.sendMessage("You are on cooldown!"); event.setCancelled(true); return; } } // 设置冷却,拦截后续重复触发 lastUse.put(player, currentTime); // 取消事件默认行为,阻止重复处理 event.setCancelled(true); event.setUseItemInHand(org.bukkit.event.Event.Result.DENY); // 处理物品消耗和效果 processFlasDustUsage(player, item); }
额外优化建议
- 移除
processFlasDustUsage中重复的物品名称和lore检查,前面已完成验证,无需重复判断。 - 优化物品消耗逻辑,直接操作对应手部的物品,避免
remove(item)的不确定性:item.setAmount(item.getAmount() - 1); if (item.getAmount() <= 0) { player.getInventory().setItem(event.getHand(), null); }
内容的提问来源于stack exchange,提问作者So Max1soft
相关产品推荐
相关产品推荐

