Skip to content

Commit

Permalink
Add InventoryHolder developer docs (PaperMC#132)
Browse files Browse the repository at this point in the history
  • Loading branch information
Nacioszeczek authored Feb 20, 2023
1 parent 805c1d1 commit 06267a1
Show file tree
Hide file tree
Showing 2 changed files with 163 additions and 0 deletions.
1 change: 1 addition & 0 deletions config/sidebar.paper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ const paper: SidebarsConfig = {
],
},
"dev/api/pdc",
"dev/api/custom-inventory-holder",
],
},
],
Expand Down
162 changes: 162 additions & 0 deletions docs/paper/dev/api/custom-inventory-holder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
---
slug: /dev/custom-inventory-holder
---

# 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.

:::info

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.

:::info

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.

:::

```java
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.

:::note

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.

:::

```java
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`.

```java
@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:

```java
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.

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

:::info

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.

:::

0 comments on commit 06267a1

Please sign in to comment.