主要编辑:在原始帖子下方包括 NMS / OBC,以展示如何通过反射来做到这一点。
Player 库存由 Mojang 选择在客户端缓存。它在打开时不需要询问任何数据,因此除了服务器加入时的初始请求外,它不会向服务器发送任何请求。无法测试库存何时打开,只有在进行修改时才能测试。
我不太确定为什么除了某些按键触发之外您还需要该事件。如果不调用 InvetoryClickEvent,库存将永远不会更改,因此当玩家加入/进入需要跟踪库存的状态时,只需扫描一次库存并监听 InventoryClickEvent 以处理更改。
编辑:存储不正确,因为服务器处理更改,缓存是一个更好的术语。
使用 NMS/OBC:
这可以使用反射和 CraftBukkit / NMS 类来实现,但是这样做会经常发生变化,并且会导致您的插件中断每次更新,除非您对自己的反射进行过验证(这方面的教程很丰富)。
目前的问题:
Bukkit 不允许您监听来自给出命令或直接代码库存操作的库存变化。因此,我们需要添加一种方法来监听这些变化。最好的方法是覆盖更改库存的方法,并添加事件以方便将来更改,或者在那里进行计算。我将解释后者,因为它更容易处理,并且还存在各种自定义事件教程。
设置环境:
在使用 NMS / OBC 时,需要导入 CraftBukkit 来获取你需要的类。下载 CraftBukkit jar 并将其导入您的项目,但您通常会这样做。如果您收到一些方法签名方法,请确保 Bukkit 具有更高的导入优先级。同样,存在向您展示如何在大多数 IDE 上修复该错误的教程。
覆盖玩家库存:
我们需要做的第一件事是创建玩家库存类的子类。查看CraftPlayer insternals,我们看到它存储了CraftInventoryPlayer 类,而不是PlayerInventory,因此,这是我们必须扩展的类。创建扩展 CraftInventoryPlayer 的新类。确保它有一个接受net.minecraft.server.VERSION.PlayerInventory 的构造函数。 VERSION 应替换为当前 NMS / OBC 版本字符串。当前为 1.7.10,版本为v1_7_R4。这会随着几乎每个 Minecraft 版本的变化而变化,并且是大多数版本错误的根源。随着您的类扩展和构造函数调用超级构造函数,您有一个基本的自定义清单。现在我们必须决定要覆盖哪些方法。
覆盖addAll 方法:
假设Essentials等插件使用addAll方法,这就是我们需要监控的方法。我们要做的是覆盖addAll 方法并将它的功能包装在我们希望进行的检查周围。在您的自定义库存类中,我们执行以下操作。
public HashMap<Integer, ItemStack> addItem(ItemStack... items) {
HashMap<Integer, ItemStack> leftovers = super.addItem(items);
//Examination code
return leftovers;
}
此方法将调用原始的addItem 方法,但允许您检查返回值并检查实际添加到玩家库存中的内容。通过检查items 和leftovers 中的差异,您可以准确判断哪些物品以及其中添加了多少到库存中,并在发生重要事情时调用您自己的事件和方法。我将让您编写考试代码,因为我不确定您要完成什么。
用我们的自定义类替换玩家的库存:
我不确定在哪里进行此替换,但我提供了我能想到的最佳选择。我们想在插件启用时替换玩家的物品栏,但是一旦它被禁用,我们需要撤消所做的更改,以便我们的自定义类可以正确卸载。我们将听取PlayerJoinEvent 和PlayerQuitEvent 进行这些更改。
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
CraftPlayer craftPlayer = (CraftPlayer) event.getPlayer();
//I am not actually typing this code in an IDE, so feel free to change
//the try-catch block to only catch what is needed
try {
Field field = CraftHumanEntity.class.getDeclaredField("inventory");
field.setAccessible(true);
CraftInventoryPlayer originalInventory = (CraftInventoryPlayer) field.get(craftPlayer);
//Store inventory here, I will use a Map that would be declared above.
originalInventories.put(craftPlayer, originalInventory);
field.set(craftPlayer, new CustomInventory(craftPlayer.getHandle().inventory));
} catch (Exception e) {
Bukkit.getLogger.log(Level.SEVERE, "Error creating custom player inventory", e);
}
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
CraftPlayer craftPlayer = (CraftPlayer) event.getPlayer();
//I am not actually typing this code in an IDE, so feel free to change
//the try-catch block to only catch what is needed
try {
Field field = CraftHumanEntity.class.getDeclaredField("inventory");
field.setAccessible(true);
CraftInventoryPlayer originalInventory = originalInventories.get(craftPlayer);
//Store inventory here, I will use a Map that would be declared above.
field.set(craftPlayer, originalInventory);
} catch (Exception e) {
Bukkit.getLogger.log(Level.SEVERE, "Error replacing player inventory with original", e);
}
}
这 2 位反射有点复杂,但我会尽可能地解释它们。几乎每个Bukkit 接口在CraftBukkit 中都有一个相应的实现,它在接口名称前加上“Craft”。有时词序也会改变。在PlayerJoinEvent 监听器中,我们得到CraftHumanEntity 类并得到它的inventory 字段。这是存储玩家库存的字段。它是私有的,所以我们使用getDeclaredField 方法而不是getField 方法,并且必须提供声明它的确切类。然后,我们使该字段可访问并操作它的数据。对于玩家加入,我们get 存储在该字段中的当前CraftInventoryPlayer,然后将其存储在其他地方以供以后检索。然后我们 set 将该字段添加到我们的自定义库存对象。请注意,构造函数接受了net.minecraft.server.PlayerInventory,因此我们将该库存提供给构造函数。我们终于捕捉到了这里可能发生的所有各种异常,并且我们已经成功地覆盖了玩家的物品栏。在PlayerQuitEvent 中,我们做相反的事情,将我们的自定义库存替换为原始库存,因为我们不再需要对其进行管理。
如果我上面概述的任何内容都不起作用,请随时告诉我。其中大部分是理论上的,但根据相关问题的先前经验,它应该可以工作。