简体   繁体   中英

Minecraft Bukkit API Event for opening PlayerInventory

I need an Event which is called, when the PlayerInventory is opened or the content is changed. The InventoryOpenEvent works only with chests, furnances etc, but not with the PlayerInventory. I'm using the Java 1.6 SDK with the craftbukkit 1.7 API.

I have the plugin ProWalls installed on my Minecraft TheWalls Server. Now I'm coding a own plugin, which should extend this plulgin. My plugin simply should let the user to select a kit before the game starts and when the game started and all players are teleported to their start locations, my plugin should add the kit items of the kit selected before.

My problem is now, that I need the moment, when the player is teleported to the spawnpoint. I first tried it with PlayerTeleportEvent, but this is too early. The items will be added, before ProWalls clear the inventory and adds his own Item.

Now I want to try it with an Event called, when a item was added in the inventory of the player. Then I can check if the added item is the item which is added by ProWalls at begin of the game and if this is true, I will add my own items.

ProWalls calls the method initGameSettings() at start to save the inventory, clear it and add his own item. So another possible solution would be, to make an event called when the plugin ProWalls calls his own method initGameSettings(). But I don't know, if this is possible. With my current code, I see the items added from my kit a few moments in my inventory but milliseconds later, the items will be removed. If there is no other solution I maybe had to to it with a timer, who waits a few milliseconds and adds the items than. But this is, as I find, no good solution...

For better understanding here the order again: Player joins TheWalls Game, ProWalls plugin calls initGameSettings(), plugin clears player inventory and adds own item, Now my plugin should add the items of the kit.

MAJOR EDIT: Including NMS / OBC below original post to show how to do this with reflection.

The Player inventory is cached client-side by choice of Mojang. It doesn't need to ask for any data when being opened so it doesn't send any requests to the server except the initial request on server join. There is no way to test for when the inventory is opened, only when modifications are made.

I'm not quite sure why you would need that event other than for some key press trigger though. The inventory will never change without calling an InvetoryClickEvent , so when the player joins / enters a state where they need their inventory tracked, just scan their inventory once and listen for the InventoryClickEvent to handle changes.

EDIT: Stored is incorrect since the server handles changes, cached is a better term.

Using NMS / OBC:

This is possible using reflection and the CraftBukkit / NMS classes, but doing so is subject to change often and will cause your plugin to break every update unless you future-proof your reflection (Tutorials for this are abundant).

The current problem:

Bukkit doesn't let you listen for inventory changes that come from give commands or direct code inventory manipluation. Thus, we need to add a way to listen for these changes. The best way to do that is by overwriting the methods that change the inventory and either adding events for ease of future changes, or doing calculations there. I'll explain the latter since it is easier to deal with and various custom event tutorials also exist.

Setting up the environment:

When using NMS / OBC, you need to import CraftBukkit to get the classes you need. Download the CraftBukkit jar and import it to your project however you normally do so. If you receive some method signature methods, make sure Bukkit has a higher priority for imports. Again, tutorials exist to show you how to fix that error on most IDEs.

Overwriting the players inventory:

The first thing we need to do is create a subclass of the players inventory class. Looking at the CraftPlayer insternals, we see that it stores a CraftInventoryPlayer class, not a PlayerInventory , therefore, this is the class we have to extend. Create your new class that extends CraftInventoryPlayer . Make sure it has a constructor that accepts a net.minecraft.server.VERSION.PlayerInventory . The VERSION should be replaced by the current NMS / OBC version string. Currently for 1.7.10, the version is v1_7_R4 . This changes with almost every minecraft version change and is the source of most of your version errors. With your class extended and constructor calling the super constructor, you have a basic custom inventory. Now we must decide which methods to overwrite.

Overwriting the addAll method:

Assuming that Essentials and other plugins use the addAll method, this is the method we need to monitor. What we will do is override the addAll method and wrap it's functionality around the checks we wish to make. In your custom inventory class, we do the following.

public HashMap<Integer, ItemStack> addItem(ItemStack... items) {
    HashMap<Integer, ItemStack> leftovers = super.addItem(items);
    //Examination code
    return leftovers;
}

This method will call the original addItem method, but allows you to examine the return value and check what was actually added to the player's inventory. By examining the differences in items and leftovers you can tell exactly what items and how many of them were added to the inventory, and call your own events and methods when something important happens. I'll leave you to write the examination code since I'm not exactly sure what you are trying to accomplish.

Replacing the player's inventory with our custom class:

I'm not exactly sure where to do this replacement, but I've provided the best possible choice that I could think of. We want to replace the player's inventory when our plugin is enabled, but once it is disabled, we need to undo the changes we did so that our custom class can be unloaded properly. We will listen to the PlayerJoinEvent and PlayerQuitEvent to do these changes.

@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);
    }
}

These 2 bits of reflection are midly complicated, but I'll explain them as well as I can. Almost every Bukkit interface has a corresponding implementation in CraftBukkit that prefixes the interface name with "Craft". Sometimes the word ordering is changed as well. In the PlayerJoinEvent listener, we get the CraftHumanEntity class and get it's inventory field. This is the field that stores the player's inventory. It is private so we use the getDeclaredField method as opposed to the getField method and must supply the exact class that declares it. We then make the field accessible and manipulate it's data. For player joins, we get the current CraftInventoryPlayer that is stored in that field and then store that elsewhere for later retrieval. Then we set that field to our custom inventory object. Note that the constructor accepted a net.minecraft.server.PlayerInventory so we supply that inventory to the constructor. We finally catch all of the various exceptions that could occur here and we have successfully overwritten the player's inventory. In the PlayerQuitEvent we do the opposite, replacing our custom inventory with the original because we do not need to manage it anymore.

If anything I have outlined above does not work, feel free to let me know. Most of this is theoretical, but from prior experience with related problems, it should work.

You can use the InventoryOpenEvent:

@EventHandler
public void onInvOpen(InventoryOpenEvent e)

Maybe you have to check the player:

if(!(e.getPlayer().equals(anotherplayer)))
    return;

Then you can check if the inventory is a player's inventory:

if(e.getInventory() !instanceof PlayerInventory
    return;

(It's just to check whether the inventory is a player's inventory)

You can also check whose player inventory it is by checking the inventory holder:

Player invholder = (Player) e.getInventory().getHolder();
if(!invholder.equals(*anotherplayer*))
    return;

Then you have your event!

(I didn't test it, but a similar code worked for me...)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM