Custom InventoryHolder
Custom InventoryHolders can be used to identify your plugin's inventories in events.
Why use an InventoryHolder?
Custom InventoryHolders simplify the steps you need to do to make sure an inventory was created by your plugin.
Using inventory names for identification is unreliable as other plugins can create inventories with names exactly as yours and with components you need to make sure the name is exactly the same or serialize it to other formats.
Custom InventoryHolders have no such downsides and by using them you're guaranteed to have methods available to handle your inventory.
Creating a custom InventoryHolder
InventoryHolder is an interface that we must implement.
We can do this the following way: create a new class that will create our Inventory
in the constructor.
The constructor takes your main plugin class as an argument in order to create the Inventory
.
If you wish, you can use the static method Bukkit.createInventory(InventoryHolder, int)
instead and remove the argument.
public class MyInventory implements InventoryHolder {
private final Inventory inventory;
public MyInventory(MyPlugin plugin) {
// Create an Inventory with 9 slots, `this` here is our InventoryHolder.
this.inventory = plugin.getServer().createInventory(this, 9);
}
@Override
public Inventory getInventory() {
return this.inventory;
}
}
Opening the inventory
To open the inventory, first we have to instantiate our MyInventory
class and then open the inventory for the player.
You can do that wherever you need.
We pass an instance of our plugin's main class as it's required by the constructor. If you've used the static method and removed the constructor argument you don't have to pass it here.
Player player; // Assume we have a Player instance.
// This can be a command, another event or anywhere else you have a Player.
MyInventory myInventory = new MyInventory(myPlugin);
player.openInventory(myInventory.getInventory());
Listening to an event
Once we have the inventory open, we can listen to any inventory events we like and check if the Inventory#getHolder()
returns an instance of our MyInventory
.
@EventHandler
public void onInventoryClick(InventoryClickEvent event) {
Inventory inventory = event.getInventory();
// Check if the holder is our MyInventory,
// if yes, use instanceof pattern matching to store it in a variable immediately.
if (!(inventory.getHolder() instanceof MyInventory myInventory)) {
// It's not our inventory, ignore it.
return;
}
// Do what we need in the event.
}
Storing data on the custom InventoryHolder
You can store extra data for your inventories on the custom InventoryHolder by adding fields and methods to your class.
Let's make an inventory that counts the amount of times we clicked a stone inside it.
First let's modify our MyInventory
class a little:
public class MyInventory implements InventoryHolder {
private final Inventory inventory;
private int clicks = 0; // Store the amount of clicks.
public MyInventory(MyPlugin plugin) {
this.inventory = plugin.getServer().createInventory(this, 9);
// Set the stone that we're going to be clicking.
this.inventory.setItem(0, new ItemStack(Material.STONE));
}
// A method we will call in the listener whenever the player clicks the stone.
public void addClick() {
this.clicks++;
this.updateCounter();
}
// A method that will update the counter item.
private void updateCounter() {
this.inventory.setItem(8, new ItemStack(Material.BEDROCK, this.clicks));
}
@Override
public Inventory getInventory() {
return this.inventory;
}
}
Now, we can modify our listener to check if the player clicked the stone, and if so, add a click.
@EventHandler
public void onInventoryClick(InventoryClickEvent event) {
// We're getting the clicked inventory to avoid situations where the player
// already has a stone in their inventory and clicks that one.
Inventory inventory = event.getClickedInventory();
// Add a null check in case the player clicked outside the window.
if (inventory == null || !(inventory.getHolder() instanceof MyInventory myInventory)) {
return;
}
event.setCancelled(true);
ItemStack clicked = event.getCurrentItem();
// Check if the player clicked the stone.
if (clicked != null && clicked.getType() == Material.STONE) {
// Use the method we have on MyInventory to increment the field
// and update the counter.
myInventory.addClick();
}
}
You can store the created MyInventory
instance, e.g. on a Map<UUID, MyInventory>
for per-player use, or as a field to share the inventory between
all players, and use it to persist the counter even when opening the inventory for the next time.