Bukkit中创建同一物品独立实例:火焰发射器燃料共享问题
问题原因
你的代码核心问题有两个:
- 监听器与武器实例耦合:你把
FlameLauncher同时作为Listener,如果只注册一个该类实例到Bukkit,所有物品的右键操作都会修改这同一个实例的fuel属性,导致多物品共享数据。 - 自定义数据存储错误:把
fuel存在类成员变量里,而不是每个ItemStack的独立存储中,多个物品自然会互相干扰。
解决方案
1. 拆分监听器与武器定义
把武器的创建逻辑和事件监听逻辑分开,单独写一个WeaponListener类处理所有武器的交互事件,FlameLauncher只作为武器的模板类,负责生成带自定义数据的物品。
2. 用ItemMeta的隐藏Lore存储燃料数据
因为你使用的是Bukkit 1.8版本(不支持PersistentDataContainer),我们可以在物品的Lore中添加一行隐藏的燃料数据,用特殊颜色标记防止玩家修改和看到。
修改后的代码示例
第一步:重构FlameLauncher为武器生成类,移除Listener接口
package fr.laponie.ShooterPlugin; import java.util.Arrays; import java.util.List; import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; public class FlameLauncher implements Weapon { // 生成火焰发射器物品的静态方法,每次调用都创建独立实例 public static ItemStack createFlameLauncher() { ItemStack is = new ItemStack(Material.GOLD_BARDING); ItemMeta im = is.getItemMeta(); // 设置显示名称和基础Lore im.setDisplayName(getDisplayNameByFuel(10.0)); List<String> lore = Arrays.asList( "This Weapon", "produces flames, igniting", "the target", ChatColor.MAGIC + "Fuel:" + 10.0 // 隐藏燃料数据,用特殊颜色隐藏 ); im.setLore(lore); is.setItemMeta(im); return is; } // 根据燃料值生成对应显示名称 public static String getDisplayNameByFuel(double fuel) { if (fuel >= 8.0) { return "FLAME LAUNCHER [" + ChatColor.GREEN + "▪ ▪ ▪ ▪ ▪" + ChatColor.WHITE + "]"; } else if (fuel >= 6.0) { return "FLAME LAUNCHER [" + ChatColor.GREEN + "▪ ▪ ▪ ▪" + ChatColor.WHITE + " ▪]"; } else if (fuel >= 4.0) { return "FLAME LAUNCHER [" + ChatColor.GREEN + "▪ ▪ ▪" + ChatColor.WHITE + " ▪ ▪]"; } else if (fuel >= 2.0) { return "FLAME LAUNCHER [" + ChatColor.GREEN + "▪ ▪" + ChatColor.WHITE + " ▪ ▪ ▪]"; } else if (fuel > 0) { return "FLAME LAUNCHER [" + ChatColor.GREEN + "▪" + ChatColor.WHITE + " ▪ ▪ ▪ ▪]"; } else { return "FLAME LAUNCHER [▪ ▪ ▪ ▪ ▪]"; } } // 实现Weapon接口的空方法(按需补充) @Override public double getDamage() { return 0; } @Override public void setDamage(double dp) {} @Override public Material getMCMaterial() { return null; } @Override public void setMCMaterial(Material m) {} @Override public double getReloadTime() { return 0; } @Override public void setReloadTime(double reloadTime) {} @Override public int getAmmoNb() { return 0; } @Override public void setAmmoNb(int ammo) {} @Override public void onAmmoHitEntity(AmmoHitPlayerEvent event) {} }
第二步:创建独立的事件监听器类
package fr.laponie.ShooterPlugin; import org.bukkit.ChatColor; import org.bukkit.Location; 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.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.util.Vector; import net.minecraft.server.v1_8_R3.EnumParticle; import net.minecraft.server.v1_8_R3.PacketPlayOutWorldParticles; import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; import java.util.List; public class WeaponListener implements Listener { @EventHandler public void onRightClickWeapon(PlayerInteractEvent e) { Player p = e.getPlayer(); Action action = e.getAction(); ItemStack hand = p.getItemInHand(); // 过滤非火焰发射器的物品 if (hand == null || hand.getType() != Material.GOLD_BARDING) return; if (!isFlameLauncher(hand)) return; if (action != Action.RIGHT_CLICK_AIR && action != Action.RIGHT_CLICK_BLOCK) return; // 获取当前物品的燃料值 double fuel = getFuelFromItem(hand); if (fuel <= 0) return; // 消耗燃料 fuel -= 1; // 更新物品的燃料数据和显示名称 updateItemFuel(hand, fuel); // 播放粒子效果 Location playerEyeLoc = p.getEyeLocation(); Vector director = playerEyeLoc.getDirection(); EnumParticle particle = EnumParticle.FLAME; Location ploc = playerEyeLoc.add(director); PacketPlayOutWorldParticles packet = new PacketPlayOutWorldParticles( particle, false, (float) ploc.getX(), (float) ploc.getY(), (float) ploc.getZ(), 0, 0, 0, 0, 5, null ); CraftPlayer cp = (CraftPlayer) p; cp.getHandle().playerConnection.sendPacket(packet); } // 判断物品是否为火焰发射器 private boolean isFlameLauncher(ItemStack item) { if (!item.hasItemMeta()) return false; ItemMeta meta = item.getItemMeta(); return meta.hasDisplayName() && meta.getDisplayName().startsWith("FLAME LAUNCHER"); } // 从Lore中读取隐藏的燃料值 private double getFuelFromItem(ItemStack item) { ItemMeta meta = item.getItemMeta(); List<String> lore = meta.getLore(); String fuelLine = lore.get(lore.size() - 1); String fuelStr = fuelLine.replace(ChatColor.MAGIC + "Fuel:", ""); return Double.parseDouble(fuelStr); } // 更新物品的燃料数据和显示名称 private void updateItemFuel(ItemStack item, double newFuel) { ItemMeta meta = item.getItemMeta(); List<String> lore = meta.getLore(); // 更新隐藏的燃料行 lore.set(lore.size() - 1, ChatColor.MAGIC + "Fuel:" + newFuel); meta.setLore(lore); // 更新显示名称 meta.setDisplayName(FlameLauncher.getDisplayNameByFuel(newFuel)); item.setItemMeta(meta); } }
第三步:在主类中注册监听器和生成物品
在插件主类的onEnable方法中注册监听器:
getServer().getPluginManager().registerEvents(new WeaponListener(), this);
给玩家发放火焰发射器时调用:
player.getInventory().addItem(FlameLauncher.createFlameLauncher());
关键改动说明
- 独立物品实例:每次调用
createFlameLauncher()都会生成新的ItemStack,每个物品的Lore中存储自己的燃料数据,完全独立。 - 监听器解耦:不再依赖
FlameLauncher实例的成员变量,所有操作都针对当前玩家手中的具体物品。 - 数据持久化:燃料数据存在物品的Lore中,玩家下线再上线后,物品的燃料值不会丢失。
内容的提问来源于stack exchange,提问作者Manewow
相关产品推荐
相关产品推荐

