Inventory
What is an Inventory?
InvUI has its own inventories, not to be confused with org.bukkit.inventory.Inventory
. InvUI's inventories can be embedded in guis, which will allow players to interact with these slots.
Virtual Inventory
For most use cases, you will want to use a VirtualInventory
. This is just a container for item stacks that you can add to your guis like so:
Inventory Events
InvUI's inventories have a powerful event system. There are multiple events that you can listen to, each of which is fired at a different stage of the interaction and can be used for different purposes. ItemPreUpdateEvent
and ItemPostUpdateEvent
are fired with an UpdateReason
. If a player interaction caused the event, this will be a PlayerUpdateReason
from which you can retrieve the player and additional information about the click.
ItemPreUpdateEvent
This event is called before changes were fully processed. Cancelling this event or changing the amount of items that were added or removed will affect the source of the change (i.e. the player's cursor most of the time) appropriately, if possible. This allows restricting which or how many items can be put into an inventory or even a specific slot of an inventory.
In the following example, the ItemPreUpdateEvent
is cancelled or handled in such a way that only red and orange wool can be put into the inventory. Additionally, orange wool can no longer be stacked:
inv.addPreUpdateHandler { event ->
if (event.isAdd || event.isSwap) {
when (event.newItem?.type) {
Material.RED_WOOL -> Unit // red wool can be added normally
Material.ORANGE_WOOL -> event.newItem?.amount = 1 // orange wool stack size is limited to 1
else -> event.isCancelled = true // cancel event for all other item types
}
}
}
inv.addPreUpdateHandler(event -> {
if (event.isAdd() || event.isSwap()) {
switch (event.getNewItem().getType()) {
case Material.RED_WOOL -> {} // red wool can be added normally
case Material.ORANGE_WOOL -> event.getNewItem().setAmount(1); // orange wool stack size is limited to 1
default -> event.setCancelled(true); // cancel event for all other item types
}
}
});
ItemPostUpdateEvent
This event is called after changes were performed on a slot. It is not cancellable and changes done to the inventory during this event will not affect the source of the change.
In the following example, the ItemPostUpdateEvent
is used to implement a trash can menu:
InventoryClickEvent
InvUI also has its own inventory click event, not to be confused with org.bukkit.event.inventory.InventoryClickEvent
. This event is fired when a player clicks on a slot in a gui-embedded inventory. For most cases, ItemPreUpdateEvent
and ItemPostUpdateEvent
are sufficient. However, in very specialized cases, you might want to intercept certain click actions and use them for something else.
In the following example, the InventoryClickEvent
is used to change the number key presses from moving the item to the hotbar slots to changing their amount instead:
inv.addClickHandler(event -> {
if (event.getClickType() == ClickType.NUMBER_KEY) {
event.setCancelled(true);
var newItem = event.getInventory().getItem(event.getSlot());
if (newItem != null)
newItem.setAmount(event.getHotbarButton() + 1);
event.getInventory().setItem(null, event.getSlot(), newItem);
}
});
Serialization
You can also serialize and deserialize VirtualInventory
(i.e. saving and loading it):
Serializing a VirtualInventory
:
Deserializing a VirtualInventory
:
There is also VirtualInventoryManager
, which automatically writes virtual inventories registered with it to disk on shutdown and reads them back on startup. This allows you to very easily create persistent inventories, but note that using VirtualInventoryManager
with a large amount of inventories will cause a slowdown on startup as all inventories are loaded on startup at once.
Referencing Inventory
The ReferencingInventory
can be used to reference a Bukkit inventory, such as the player's inventory. For example, you can easily implement a gui to look at another player's inventory using it:
Other configuration options
Gui priority
The gui priority defines the order in which inventories of a gui are iterated over for actions like shift-clicking items or collecting them to the cursor with a double click. Gui priority is categorized, so you can define different priorities per category.
In the following example, the gui priorities are configured in such a way that items are shift-clicked into the right inventory first, but collecting to the cursor prioritizes the left inventory:
val left = VirtualInventory(9)
left.setGuiPriority(OperationCategory.ADD, -1)
left.setGuiPriority(OperationCategory.COLLECT, 1)
val right = VirtualInventory(9)
val gui = Gui.builder()
.setStructure(
"# # # # # # # # #",
"# x x x # y y y #",
"# x x x # y y y #",
"# x x x # y y y #",
"# # # # # # # # #",
)
.addIngredient('x', left)
.addIngredient('y', right)
.build()
var left = new VirtualInventory(9);
left.setGuiPriority(OperationCategory.ADD, -1);
left.setGuiPriority(OperationCategory.COLLECT, 1);
var right = new VirtualInventory(9)
var gui = Gui.builder()
.setStructure(
"# # # # # # # # #",
"# x x x # y y y #",
"# x x x # y y y #",
"# x x x # y y y #",
"# # # # # # # # #"
)
.addIngredient('x', left)
.addIngredient('y', right)
.build();
Iteration order
The iteration order defines in which order the slots of multi-slot operations like adding or collecting are chosen. By default, the iteration order is from first to the last slot (i.e. items are added into the first available slot and collected from the first matching slot). Like the gui priority, the iteration order is categorized, so you can define different orders per category.
You can change the iteration to a completely custom sequence of slots, but there are also utilities to just reverse it. The following example reverses the iteration order for adding items, but keeps the iteration order for collection items:
val inv = VirtualInventory(7 * 4)
inv.reverseIterationOrder(OperationCategory.ADD)
val gui = Gui.builder()
.setStructure(
"# # # # # # # # #",
"# x x x x x x x #",
"# x x x x x x x #",
"# x x x x x x x #",
"# # # # # # # # #",
)
.addIngredient('#', Item.simple(ItemBuilder(Material.BLACK_STAINED_GLASS_PANE).hideTooltip(true)))
.addIngredient('x', inv)
.build()
var inv = new VirtualInventory(7 * 4);
inv.reverseIterationOrder(OperationCategory.ADD);
var gui = Gui.builder()
.setStructure(
"# # # # # # # # #",
"# x x x x x x x #",
"# x x x x x x x #",
"# x x x x x x x #",
"# # # # # # # # #"
)
.addIngredient('#', Item.simple(new ItemBuilder(Material.BLACK_STAINED_GLASS_PANE).hideTooltip(true)))
.addIngredient('x', inv)
.build();
Background
The slots in your inventory may be empty, but this does not mean that they have to be visually empty as well. You can set a background ItemProvider
for your inventory, which will be used to display empty slots. Inventory interactions will keep working as if the slots were empty.
Item dragging does not work on slots with a background.
Obscured slots
By default, multi-slot operations like shift-clicking into or collecting from inventories that are embedded in guis will ignore all slots that are not visible, even if the inventory has more slots. You can change this behavior by setting Gui.setIgnoreObscuredInventorySlots(false)
:
The following example uses a scroll gui to display a large inventory that does not fit on one screen. By default, shift-clicking does nothing if there are no empty visible slots, but with setIgnoreObscuredInventorySlots(false)
, this is not the case:
val inv = VirtualInventory(7 * 6)
repeat(7 * 3) { inv.addItem(null, ItemStack.of(Material.DIAMOND, 64)) }
val gui = ScrollGui.inventoriesBuilder()
.setStructure(
"# # # # # # # # #",
"# x x x x x x x u",
"# x x x x x x x #",
"# x x x x x x x d",
"# # # # # # # # #",
)
.setContent(listOf(inv))
.setIgnoreObscuredInventorySlots(false)
.build()
var inv = new VirtualInventory(7 * 6);
for (int i = 0; i < 7 * 3; i++) {
inv.addItem(null, ItemStack.of(Material.DIAMOND, 64));
}
var gui = ScrollGui.inventoriesBuilder()
.setStructure(
"# # # # # # # # #",
"# x x x x x x x u",
"# x x x x x x x #",
"# x x x x x x x d",
"# # # # # # # # #"
)
.setContent(List.of(inv))
.setIgnoreObscuredInventorySlots(false)
.build();