Integrate PlayerInventory (#2332)

* Integrate player inventory and normal inventory

* Fix errors and broken tests (drop before adding, wrong order, etc)

* Refactor slot refreshing

* Fix viewer logic error

* Fix incorrect merge

* Move inventory update

* Minor simplifications

* More minor simplifications
This commit is contained in:
GoldenStack 2024-10-28 23:58:24 +00:00 committed by Matt Worzala
parent 41738bb62a
commit e0939f089b
23 changed files with 245 additions and 293 deletions

View File

@ -48,6 +48,7 @@ import net.minestom.server.instance.EntityTracker;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.SharedInstance;
import net.minestom.server.instance.block.Block;
import net.minestom.server.inventory.AbstractInventory;
import net.minestom.server.inventory.Inventory;
import net.minestom.server.inventory.PlayerInventory;
import net.minestom.server.item.ItemComponent;
@ -175,7 +176,7 @@ public class Player extends LivingEntity implements CommandSender, HoverEventSou
private int portalCooldown = 0;
protected PlayerInventory inventory;
private Inventory openInventory;
private AbstractInventory openInventory;
// Used internally to allow the closing of inventory within the inventory listener
private boolean didCloseInventory;
@ -236,7 +237,7 @@ public class Player extends LivingEntity implements CommandSender, HoverEventSou
setRespawnPoint(Pos.ZERO);
this.inventory = new PlayerInventory(this);
this.inventory = new PlayerInventory();
setCanPickupItem(true); // By default
@ -295,6 +296,9 @@ public class Player extends LivingEntity implements CommandSender, HoverEventSou
true);
sendPacket(joinGamePacket);
// Start sending inventory updates
inventory.addViewer(this);
// Difficulty
sendPacket(new ServerDifficultyPacket(MinecraftServer.getDifficulty(), true));
@ -573,8 +577,9 @@ public class Player extends LivingEntity implements CommandSender, HoverEventSou
super.remove(permanent);
final Inventory currentInventory = getOpenInventory();
final AbstractInventory currentInventory = getOpenInventory();
if (currentInventory != null) currentInventory.removeViewer(this);
MinecraftServer.getBossBarManager().removeAllBossBars(this);
// Advancement tabs cache
{
@ -1724,7 +1729,7 @@ public class Player extends LivingEntity implements CommandSender, HoverEventSou
*
* @return the currently open inventory, null if there is not (player inventory is not detected)
*/
public @Nullable Inventory getOpenInventory() {
public @Nullable AbstractInventory getOpenInventory() {
return openInventory;
}
@ -1738,19 +1743,13 @@ public class Player extends LivingEntity implements CommandSender, HoverEventSou
InventoryOpenEvent inventoryOpenEvent = new InventoryOpenEvent(inventory, this);
EventDispatcher.callCancellable(inventoryOpenEvent, () -> {
Inventory openInventory = getOpenInventory();
AbstractInventory openInventory = getOpenInventory();
if (openInventory != null) {
openInventory.removeViewer(this);
}
Inventory newInventory = inventoryOpenEvent.getInventory();
if (newInventory == null) {
// just close the inventory
return;
}
AbstractInventory newInventory = inventoryOpenEvent.getInventory();
sendPacket(new OpenWindowPacket(newInventory.getWindowId(),
newInventory.getInventoryType().getWindowType(), newInventory.getTitle()));
newInventory.addViewer(this);
this.openInventory = newInventory;
});
@ -1767,31 +1766,15 @@ public class Player extends LivingEntity implements CommandSender, HoverEventSou
@ApiStatus.Internal
public void closeInventory(boolean fromClient) {
Inventory openInventory = getOpenInventory();
AbstractInventory openInventory = getOpenInventory();
if (openInventory == null) return;
// Drop cursor item when closing inventory
ItemStack cursorItem = getInventory().getCursorItem();
getInventory().setCursorItem(ItemStack.AIR);
this.openInventory = null;
openInventory.removeViewer(this);
inventory.update();
if (!cursorItem.isAir()) {
// Add item to inventory if he hasn't been able to drop it
if (!dropItem(cursorItem)) {
getInventory().addItemStack(cursorItem);
}
}
if (openInventory == getOpenInventory()) {
CloseWindowPacket closeWindowPacket;
if (openInventory == null) {
closeWindowPacket = new CloseWindowPacket((byte) 0);
} else {
closeWindowPacket = new CloseWindowPacket(openInventory.getWindowId());
openInventory.removeViewer(this); // Clear cache
this.openInventory = null;
}
if (!fromClient) sendPacket(closeWindowPacket);
inventory.update();
this.didCloseInventory = true;
if (!fromClient) {
didCloseInventory = true;
}
}
@ -2276,12 +2259,12 @@ public class Player extends LivingEntity implements CommandSender, HoverEventSou
@Override
public @NotNull ItemStack getEquipment(@NotNull EquipmentSlot slot) {
return inventory.getEquipment(slot);
return inventory.getEquipment(slot, heldSlot);
}
@Override
public void setEquipment(@NotNull EquipmentSlot slot, @NotNull ItemStack itemStack) {
inventory.setEquipment(slot, itemStack);
inventory.setEquipment(slot, heldSlot, itemStack);
}
@Override

View File

@ -5,7 +5,7 @@ import net.minestom.server.entity.Player;
import net.minestom.server.event.trait.*;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.block.Block;
import net.minestom.server.inventory.Inventory;
import net.minestom.server.inventory.AbstractInventory;
import net.minestom.server.item.ItemStack;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
@ -34,7 +34,7 @@ public interface EventFilter<E extends Event, H> {
EventFilter<PlayerEvent, Player> PLAYER = from(PlayerEvent.class, Player.class, PlayerEvent::getPlayer);
EventFilter<ItemEvent, ItemStack> ITEM = from(ItemEvent.class, ItemStack.class, ItemEvent::getItemStack);
EventFilter<InstanceEvent, Instance> INSTANCE = from(InstanceEvent.class, Instance.class, InstanceEvent::getInstance);
EventFilter<InventoryEvent, Inventory> INVENTORY = from(InventoryEvent.class, Inventory.class, InventoryEvent::getInventory);
EventFilter<InventoryEvent, AbstractInventory> INVENTORY = from(InventoryEvent.class, AbstractInventory.class, InventoryEvent::getInventory);
EventFilter<BlockEvent, Block> BLOCK = from(BlockEvent.class, Block.class, BlockEvent::getBlock);
static <E extends Event, H> EventFilter<E, H> from(@NotNull Class<E> eventType,

View File

@ -3,11 +3,10 @@ package net.minestom.server.event.inventory;
import net.minestom.server.entity.Player;
import net.minestom.server.event.trait.InventoryEvent;
import net.minestom.server.event.trait.PlayerInstanceEvent;
import net.minestom.server.inventory.Inventory;
import net.minestom.server.inventory.AbstractInventory;
import net.minestom.server.inventory.click.ClickType;
import net.minestom.server.item.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Called after {@link InventoryPreClickEvent}, this event cannot be cancelled and items related to the click
@ -15,14 +14,14 @@ import org.jetbrains.annotations.Nullable;
*/
public class InventoryClickEvent implements InventoryEvent, PlayerInstanceEvent {
private final Inventory inventory;
private final AbstractInventory inventory;
private final Player player;
private final int slot;
private final ClickType clickType;
private final ItemStack clickedItem;
private final ItemStack cursorItem;
public InventoryClickEvent(@Nullable Inventory inventory, @NotNull Player player,
public InventoryClickEvent(@NotNull AbstractInventory inventory, @NotNull Player player,
int slot, @NotNull ClickType clickType,
@NotNull ItemStack clicked, @NotNull ItemStack cursor) {
this.inventory = inventory;
@ -83,7 +82,7 @@ public class InventoryClickEvent implements InventoryEvent, PlayerInstanceEvent
}
@Override
public @Nullable Inventory getInventory() {
public @NotNull AbstractInventory getInventory() {
return inventory;
}
}

View File

@ -3,20 +3,21 @@ package net.minestom.server.event.inventory;
import net.minestom.server.entity.Player;
import net.minestom.server.event.trait.InventoryEvent;
import net.minestom.server.event.trait.PlayerInstanceEvent;
import net.minestom.server.inventory.AbstractInventory;
import net.minestom.server.inventory.Inventory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Called when an {@link Inventory} is closed by a player.
* Called when an {@link AbstractInventory} is closed by a player.
*/
public class InventoryCloseEvent implements InventoryEvent, PlayerInstanceEvent {
private final Inventory inventory;
private final AbstractInventory inventory;
private final Player player;
private Inventory newInventory;
public InventoryCloseEvent(@Nullable Inventory inventory, @NotNull Player player) {
public InventoryCloseEvent(@NotNull AbstractInventory inventory, @NotNull Player player) {
this.inventory = inventory;
this.player = player;
}
@ -51,7 +52,7 @@ public class InventoryCloseEvent implements InventoryEvent, PlayerInstanceEvent
}
@Override
public @Nullable Inventory getInventory() {
public @NotNull AbstractInventory getInventory() {
return inventory;
}
}

View File

@ -3,10 +3,8 @@ package net.minestom.server.event.inventory;
import net.minestom.server.event.trait.InventoryEvent;
import net.minestom.server.event.trait.RecursiveEvent;
import net.minestom.server.inventory.AbstractInventory;
import net.minestom.server.inventory.Inventory;
import net.minestom.server.item.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Called when {@link AbstractInventory#safeItemInsert(int, ItemStack)} is being invoked.
@ -17,12 +15,12 @@ import org.jetbrains.annotations.Nullable;
@SuppressWarnings("JavadocReference")
public class InventoryItemChangeEvent implements InventoryEvent, RecursiveEvent {
private final Inventory inventory;
private final AbstractInventory inventory;
private final int slot;
private final ItemStack previousItem;
private final ItemStack newItem;
public InventoryItemChangeEvent(@Nullable Inventory inventory, int slot,
public InventoryItemChangeEvent(@NotNull AbstractInventory inventory, int slot,
@NotNull ItemStack previousItem, @NotNull ItemStack newItem) {
this.inventory = inventory;
this.slot = slot;
@ -58,7 +56,7 @@ public class InventoryItemChangeEvent implements InventoryEvent, RecursiveEvent
}
@Override
public @Nullable Inventory getInventory() {
public @NotNull AbstractInventory getInventory() {
return inventory;
}
}

View File

@ -4,23 +4,23 @@ import net.minestom.server.entity.Player;
import net.minestom.server.event.trait.CancellableEvent;
import net.minestom.server.event.trait.InventoryEvent;
import net.minestom.server.event.trait.PlayerInstanceEvent;
import net.minestom.server.inventory.AbstractInventory;
import net.minestom.server.inventory.Inventory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Called when a player open an {@link Inventory}.
* Called when a player open an {@link AbstractInventory}.
* <p>
* Executed by {@link Player#openInventory(Inventory)}.
*/
public class InventoryOpenEvent implements InventoryEvent, PlayerInstanceEvent, CancellableEvent {
private Inventory inventory;
private AbstractInventory inventory;
private final Player player;
private boolean cancelled;
public InventoryOpenEvent(@Nullable Inventory inventory, @NotNull Player player) {
public InventoryOpenEvent(@NotNull AbstractInventory inventory, @NotNull Player player) {
this.inventory = inventory;
this.player = player;
}
@ -36,13 +36,12 @@ public class InventoryOpenEvent implements InventoryEvent, PlayerInstanceEvent,
}
/**
* Gets the inventory to open, this could have been change by the {@link #setInventory(Inventory)}.
* Gets the inventory to open, this could have been change by the {@link #setInventory(AbstractInventory)}.
*
* @return the inventory to open, null to just close the current inventory if any
*/
@Nullable
@Override
public Inventory getInventory() {
public @NotNull AbstractInventory getInventory() {
return inventory;
}
@ -53,7 +52,7 @@ public class InventoryOpenEvent implements InventoryEvent, PlayerInstanceEvent,
*
* @param inventory the inventory to open
*/
public void setInventory(@Nullable Inventory inventory) {
public void setInventory(@NotNull AbstractInventory inventory) {
this.inventory = inventory;
}

View File

@ -4,18 +4,17 @@ import net.minestom.server.entity.Player;
import net.minestom.server.event.trait.CancellableEvent;
import net.minestom.server.event.trait.InventoryEvent;
import net.minestom.server.event.trait.PlayerInstanceEvent;
import net.minestom.server.inventory.Inventory;
import net.minestom.server.inventory.AbstractInventory;
import net.minestom.server.inventory.click.ClickType;
import net.minestom.server.item.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Called before {@link InventoryClickEvent}, used to potentially cancel the click.
*/
public class InventoryPreClickEvent implements InventoryEvent, PlayerInstanceEvent, CancellableEvent {
private final Inventory inventory;
private final AbstractInventory inventory;
private final Player player;
private final int slot;
private final ClickType clickType;
@ -24,7 +23,7 @@ public class InventoryPreClickEvent implements InventoryEvent, PlayerInstanceEve
private boolean cancelled;
public InventoryPreClickEvent(@Nullable Inventory inventory,
public InventoryPreClickEvent(@NotNull AbstractInventory inventory,
@NotNull Player player,
int slot, @NotNull ClickType clickType,
@NotNull ItemStack clicked, @NotNull ItemStack cursor) {
@ -114,7 +113,7 @@ public class InventoryPreClickEvent implements InventoryEvent, PlayerInstanceEve
}
@Override
public @Nullable Inventory getInventory() {
public @NotNull AbstractInventory getInventory() {
return inventory;
}
}

View File

@ -1,31 +0,0 @@
package net.minestom.server.event.inventory;
import net.minestom.server.entity.Player;
import net.minestom.server.event.trait.PlayerInstanceEvent;
import net.minestom.server.inventory.AbstractInventory;
import net.minestom.server.inventory.PlayerInventory;
import net.minestom.server.item.ItemStack;
import org.jetbrains.annotations.NotNull;
/**
* Called when {@link AbstractInventory#safeItemInsert(int, ItemStack)} is being invoked on a {@link PlayerInventory}.
* This event cannot be cancelled and items related to the change are already moved.
* <p>
* When this event is being called, {@link InventoryItemChangeEvent} listeners will also be triggered, so you can
* listen only for an ancestor event and check whether it is an instance of that class.
*/
@SuppressWarnings("JavadocReference")
public class PlayerInventoryItemChangeEvent extends InventoryItemChangeEvent implements PlayerInstanceEvent {
private final Player player;
public PlayerInventoryItemChangeEvent(@NotNull Player player, int slot, @NotNull ItemStack previousItem, @NotNull ItemStack newItem) {
super(null, slot, previousItem, newItem);
this.player = player;
}
@Override
public @NotNull Player getPlayer() {
return player;
}
}

View File

@ -1,18 +1,16 @@
package net.minestom.server.event.trait;
import net.minestom.server.event.Event;
import net.minestom.server.inventory.Inventory;
import org.jetbrains.annotations.Nullable;
import net.minestom.server.inventory.AbstractInventory;
import org.jetbrains.annotations.NotNull;
/**
* Represents any event inside an {@link Inventory}.
* Represents any event inside an {@link AbstractInventory}.
*/
public interface InventoryEvent extends Event {
/**
* Gets the inventory.
*
* @return the inventory, null if this is a player's inventory
* Gets the inventory that was clicked.
*/
@Nullable Inventory getInventory();
@NotNull AbstractInventory getInventory();
}

View File

@ -1,11 +1,15 @@
package net.minestom.server.inventory;
import net.minestom.server.Viewable;
import net.minestom.server.entity.Player;
import net.minestom.server.event.EventDispatcher;
import net.minestom.server.event.inventory.InventoryItemChangeEvent;
import net.minestom.server.event.inventory.PlayerInventoryItemChangeEvent;
import net.minestom.server.inventory.click.InventoryClickProcessor;
import net.minestom.server.inventory.condition.InventoryCondition;
import net.minestom.server.item.ItemStack;
import net.minestom.server.network.packet.server.play.CloseWindowPacket;
import net.minestom.server.network.packet.server.play.SetSlotPacket;
import net.minestom.server.network.packet.server.play.WindowItemsPacket;
import net.minestom.server.tag.TagHandler;
import net.minestom.server.tag.Taggable;
import net.minestom.server.utils.MathUtils;
@ -14,16 +18,15 @@ import org.jetbrains.annotations.NotNull;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.function.UnaryOperator;
/**
* Represents an inventory where items can be modified/retrieved.
*/
public sealed abstract class AbstractInventory implements InventoryClickHandler, Taggable
public sealed abstract class AbstractInventory implements InventoryClickHandler, Taggable, Viewable
permits Inventory, PlayerInventory {
private static final VarHandle ITEM_UPDATER = MethodHandles.arrayElementVarHandle(ItemStack[].class);
@ -38,58 +41,97 @@ public sealed abstract class AbstractInventory implements InventoryClickHandler,
private final TagHandler tagHandler = TagHandler.newHandler();
// the players currently viewing this inventory
protected final Set<Player> viewers = new CopyOnWriteArraySet<>();
protected final Set<Player> unmodifiableViewers = Collections.unmodifiableSet(viewers);
protected AbstractInventory(int size) {
this.size = size;
this.itemStacks = new ItemStack[getSize()];
Arrays.fill(itemStacks, ItemStack.AIR);
}
/**
* Gets this window id.
* <p>
* This is the id that the client will send to identify the affected inventory, mostly used by packets.
*
* @return the window id
*/
public abstract byte getWindowId();
@Override
public @NotNull Set<Player> getViewers() {
return unmodifiableViewers;
}
@Override
public boolean addViewer(@NotNull Player player) {
if (!this.viewers.add(player)) return false;
update(player);
return true;
}
@Override
public boolean removeViewer(@NotNull Player player) {
if (!this.viewers.remove(player)) return false;
// Drop cursor item when closing inventory
ItemStack cursorItem = player.getInventory().getCursorItem();
player.getInventory().setCursorItem(ItemStack.AIR);
if (!cursorItem.isAir()) {
if (!player.dropItem(cursorItem)) {
player.getInventory().addItemStack(cursorItem);
}
}
if (player.didCloseInventory()) {
player.sendPacket(new CloseWindowPacket(getWindowId()));
}
return true;
}
/**
* Sets an {@link ItemStack} at the specified slot and send relevant update to the viewer(s).
*
* @param slot the slot to set the item
* @param itemStack the item to set
*/
public synchronized void setItemStack(int slot, @NotNull ItemStack itemStack) {
Check.argCondition(!MathUtils.isBetween(slot, 0, getSize()),
"Inventory does not have the slot " + slot);
safeItemInsert(slot, itemStack);
public void setItemStack(int slot, @NotNull ItemStack itemStack) {
setItemStack(slot, itemStack, true);
}
/**
* Inserts safely an item into the inventory.
* <p>
* This will update the slot for all viewers and warn the inventory that
* the window items packet is not up-to-date.
* Sets an {@link ItemStack} at the specified slot and send relevant update to the viewer(s).
*
* @param slot the internal slot id
* @param itemStack the item to insert (use air instead of null)
* @throws IllegalArgumentException if the slot {@code slot} does not exist
* @param slot the slot to set the item
* @param itemStack the item to set
* @param sendPacket whether or not to send packets
*/
protected final void safeItemInsert(int slot, @NotNull ItemStack itemStack, boolean sendPacket) {
public void setItemStack(int slot, @NotNull ItemStack itemStack, boolean sendPacket) {
Check.argCondition(!MathUtils.isBetween(slot, 0, getSize()),
"Inventory does not have the slot " + slot);
ItemStack previous;
synchronized (this) {
Check.argCondition(
!MathUtils.isBetween(slot, 0, getSize()),
"The slot {0} does not exist in this inventory",
slot
);
previous = itemStacks[slot];
if (itemStack.equals(previous)) return; // Avoid sending updates if the item has not changed
UNSAFE_itemInsert(slot, itemStack, sendPacket);
}
if (this instanceof PlayerInventory inv) {
EventDispatcher.call(new PlayerInventoryItemChangeEvent(inv.player, slot, previous, itemStack));
} else if (this instanceof Inventory inv) {
EventDispatcher.call(new InventoryItemChangeEvent(inv, slot, previous, itemStack));
UNSAFE_itemInsert(slot, itemStack, previous, sendPacket);
}
EventDispatcher.call(new InventoryItemChangeEvent(this, slot, previous, itemStack));
}
protected final void safeItemInsert(int slot, @NotNull ItemStack itemStack) {
safeItemInsert(slot, itemStack, true);
protected void UNSAFE_itemInsert(int slot, @NotNull ItemStack item, @NotNull ItemStack previous, boolean sendPacket) {
itemStacks[slot] = item;
if (sendPacket) sendSlotRefresh(slot, item, previous);
}
protected abstract void UNSAFE_itemInsert(int slot, @NotNull ItemStack itemStack, boolean sendPacket);
public void sendSlotRefresh(int slot, @NotNull ItemStack item, @NotNull ItemStack previous) {
sendPacketToViewers(new SetSlotPacket(getWindowId(), 0, (short) slot, item));
}
public synchronized <T> @NotNull T processItemStack(@NotNull ItemStack itemStack,
@NotNull TransactionType type,
@ -167,13 +209,27 @@ public sealed abstract class AbstractInventory implements InventoryClickHandler,
public synchronized void clear() {
// Clear the item array
for (int i = 0; i < size; i++) {
safeItemInsert(i, ItemStack.AIR, false);
setItemStack(i, ItemStack.AIR, false);
}
// Send the cleared inventory to viewers
update();
}
public abstract void update();
/**
* Refreshes the inventory for all viewers.
*/
public void update() {
this.viewers.forEach(this::update);
}
/**
* Refreshes the inventory for a specific viewer.
*
* @param player the player to update the inventory for
*/
public void update(@NotNull Player player) {
player.sendPacket(new WindowItemsPacket(getWindowId(), 0, List.of(itemStacks), player.getInventory().getCursorItem()));
}
/**
* Gets the {@link ItemStack} at the specified slot.

View File

@ -1,22 +1,16 @@
package net.minestom.server.inventory;
import net.kyori.adventure.text.Component;
import net.minestom.server.Viewable;
import net.minestom.server.entity.Player;
import net.minestom.server.inventory.click.ClickType;
import net.minestom.server.inventory.click.InventoryClickResult;
import net.minestom.server.item.ItemStack;
import net.minestom.server.network.packet.server.play.OpenWindowPacket;
import net.minestom.server.network.packet.server.play.SetSlotPacket;
import net.minestom.server.network.packet.server.play.WindowItemsPacket;
import net.minestom.server.network.packet.server.play.WindowPropertyPacket;
import net.minestom.server.utils.inventory.PlayerInventoryUtils;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
/**
@ -25,22 +19,15 @@ import java.util.concurrent.atomic.AtomicInteger;
* You can create one with {@link Inventory#Inventory(InventoryType, String)} or by making your own subclass.
* It can then be opened using {@link Player#openInventory(Inventory)}.
*/
public non-sealed class Inventory extends AbstractInventory implements Viewable {
public non-sealed class Inventory extends AbstractInventory {
private static final AtomicInteger ID_COUNTER = new AtomicInteger();
// the id of this inventory
private final byte id;
// the type of this inventory
private final InventoryType inventoryType;
// the title of this inventory
private Component title;
private final int offset;
// the players currently viewing this inventory
private final Set<Player> viewers = new CopyOnWriteArraySet<>();
private final Set<Player> unmodifiableViewers = Collections.unmodifiableSet(viewers);
public Inventory(@NotNull InventoryType inventoryType, @NotNull Component title) {
super(inventoryType.getSize());
this.id = generateId();
@ -89,42 +76,11 @@ public non-sealed class Inventory extends AbstractInventory implements Viewable
update();
}
/**
* Gets this window id.
* <p>
* This is the id that the client will send to identify the affected inventory, mostly used by packets.
*
* @return the window id
*/
@Override
public byte getWindowId() {
return id;
}
/**
* Refreshes the inventory for all viewers.
*/
@Override
public void update() {
this.viewers.forEach(p -> p.sendPacket(createNewWindowItemsPacket(p)));
}
/**
* Refreshes the inventory for a specific viewer.
* <p>
* The player needs to be a viewer, otherwise nothing is sent.
*
* @param player the player to update the inventory
*/
public void update(@NotNull Player player) {
if (!isViewer(player)) return;
player.sendPacket(createNewWindowItemsPacket(player));
}
@Override
public @NotNull Set<Player> getViewers() {
return unmodifiableViewers;
}
/**
* This will not open the inventory for {@code player}, use {@link Player#openInventory(Inventory)}.
*
@ -133,9 +89,12 @@ public non-sealed class Inventory extends AbstractInventory implements Viewable
*/
@Override
public boolean addViewer(@NotNull Player player) {
final boolean result = this.viewers.add(player);
if (!this.viewers.add(player)) return false;
// Also send the open window packet
player.sendPacket(new OpenWindowPacket(id, inventoryType.getWindowType(), title));
update(player);
return result;
return true;
}
/**
@ -146,9 +105,10 @@ public non-sealed class Inventory extends AbstractInventory implements Viewable
*/
@Override
public boolean removeViewer(@NotNull Player player) {
final boolean result = this.viewers.remove(player);
if (!super.removeViewer(player)) return false;
this.clickProcessor.clearCache(player);
return result;
return true;
}
/**
@ -173,16 +133,6 @@ public non-sealed class Inventory extends AbstractInventory implements Viewable
player.getInventory().setCursorItem(cursorItem);
}
@Override
protected void UNSAFE_itemInsert(int slot, @NotNull ItemStack itemStack, boolean sendPacket) {
itemStacks[slot] = itemStack;
if (sendPacket) sendPacketToViewers(new SetSlotPacket(getWindowId(), 0, (short) slot, itemStack));
}
private @NotNull WindowItemsPacket createNewWindowItemsPacket(Player player) {
return new WindowItemsPacket(getWindowId(), 0, List.of(getItemStacks()), player.getInventory().getCursorItem());
}
/**
* Sends a window property to all viewers.
*
@ -200,9 +150,9 @@ public non-sealed class Inventory extends AbstractInventory implements Viewable
final ItemStack cursor = playerInventory.getCursorItem();
final boolean isInWindow = isClickInWindow(slot);
final int clickSlot = isInWindow ? slot : PlayerInventoryUtils.convertSlot(slot, offset);
final AbstractInventory clickedInventory = isInWindow ? this : playerInventory;
final ItemStack clicked = isInWindow ? getItemStack(slot) : playerInventory.getItemStack(clickSlot);
final InventoryClickResult clickResult = clickProcessor.leftClick(player,
isInWindow ? this : playerInventory, clickSlot, clicked, cursor);
final InventoryClickResult clickResult = clickProcessor.leftClick(player, clickedInventory, clickSlot, clicked, cursor);
if (clickResult.isCancel()) {
updateAll(player);
return false;
@ -213,7 +163,7 @@ public non-sealed class Inventory extends AbstractInventory implements Viewable
playerInventory.setItemStack(clickSlot, clickResult.getClicked());
}
playerInventory.setCursorItem(clickResult.getCursor());
callClickEvent(player, isInWindow ? this : null, slot, ClickType.LEFT_CLICK, clicked, cursor);
callClickEvent(player, clickedInventory, slot, ClickType.LEFT_CLICK, clicked, cursor);
return true;
}
@ -224,8 +174,8 @@ public non-sealed class Inventory extends AbstractInventory implements Viewable
final boolean isInWindow = isClickInWindow(slot);
final int clickSlot = isInWindow ? slot : PlayerInventoryUtils.convertSlot(slot, offset);
final ItemStack clicked = isInWindow ? getItemStack(slot) : playerInventory.getItemStack(clickSlot);
final InventoryClickResult clickResult = clickProcessor.rightClick(player,
isInWindow ? this : playerInventory, clickSlot, clicked, cursor);
final AbstractInventory clickedInventory = isInWindow ? this : playerInventory;
final InventoryClickResult clickResult = clickProcessor.rightClick(player, clickedInventory, clickSlot, clicked, cursor);
if (clickResult.isCancel()) {
updateAll(player);
return false;
@ -236,7 +186,7 @@ public non-sealed class Inventory extends AbstractInventory implements Viewable
playerInventory.setItemStack(clickSlot, clickResult.getClicked());
}
playerInventory.setCursorItem(clickResult.getCursor());
callClickEvent(player, isInWindow ? this : null, slot, ClickType.RIGHT_CLICK, clicked, cursor);
callClickEvent(player, clickedInventory, slot, ClickType.RIGHT_CLICK, clicked, cursor);
return true;
}
@ -274,8 +224,8 @@ public non-sealed class Inventory extends AbstractInventory implements Viewable
final int clickSlot = isInWindow ? slot : PlayerInventoryUtils.convertSlot(slot, offset);
final ItemStack clicked = isInWindow ? getItemStack(slot) : playerInventory.getItemStack(clickSlot);
final ItemStack heldItem = playerInventory.getItemStack(convertedKey);
final InventoryClickResult clickResult = clickProcessor.changeHeld(player,
isInWindow ? this : playerInventory, clickSlot, convertedKey, clicked, heldItem);
final AbstractInventory clickedInventory = isInWindow ? this : playerInventory;
final InventoryClickResult clickResult = clickProcessor.changeHeld(player, clickedInventory, clickSlot, convertedKey, clicked, heldItem);
if (clickResult.isCancel()) {
updateAll(player);
return false;
@ -286,7 +236,7 @@ public non-sealed class Inventory extends AbstractInventory implements Viewable
playerInventory.setItemStack(clickSlot, clickResult.getClicked());
}
playerInventory.setItemStack(convertedKey, clickResult.getCursor());
callClickEvent(player, isInWindow ? this : null, slot, ClickType.CHANGE_HELD, clicked, playerInventory.getCursorItem());
callClickEvent(player, clickedInventory, slot, ClickType.CHANGE_HELD, clicked, playerInventory.getCursorItem());
return true;
}

View File

@ -76,7 +76,7 @@ public sealed interface InventoryClickHandler permits AbstractInventory {
*/
boolean doubleClick(@NotNull Player player, int slot);
default void callClickEvent(@NotNull Player player, Inventory inventory, int slot,
default void callClickEvent(@NotNull Player player, @NotNull AbstractInventory inventory, int slot,
@NotNull ClickType clickType, @NotNull ItemStack clicked, @NotNull ItemStack cursor) {
EventDispatcher.call(new InventoryClickEvent(inventory, player, slot, clickType, clicked, cursor));
}

View File

@ -1,7 +1,6 @@
package net.minestom.server.inventory;
import net.minestom.server.entity.EquipmentSlot;
import net.minestom.server.entity.GameMode;
import net.minestom.server.entity.Player;
import net.minestom.server.event.EventDispatcher;
import net.minestom.server.event.item.EntityEquipEvent;
@ -12,6 +11,7 @@ import net.minestom.server.network.packet.server.play.SetSlotPacket;
import net.minestom.server.network.packet.server.play.WindowItemsPacket;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
@ -20,24 +20,23 @@ import static net.minestom.server.utils.inventory.PlayerInventoryUtils.*;
/**
* Represents the inventory of a {@link Player}, retrieved with {@link Player#getInventory()}.
*/
public non-sealed class PlayerInventory extends AbstractInventory implements EquipmentHandler {
public non-sealed class PlayerInventory extends AbstractInventory {
public static final int INVENTORY_SIZE = 46;
public static final int INNER_INVENTORY_SIZE = 36;
protected final Player player;
private ItemStack cursorItem = ItemStack.AIR;
public PlayerInventory(@NotNull Player player) {
public PlayerInventory() {
super(INVENTORY_SIZE);
this.player = player;
}
@Override
public synchronized void clear() {
cursorItem = ItemStack.AIR;
super.clear();
// Update equipments
this.player.sendPacketToViewersAndSelf(player.getEquipmentsPacket());
viewers.forEach(viewer -> viewer.sendPacketToViewersAndSelf(viewer.getEquipmentsPacket()));
}
@Override
@ -45,35 +44,45 @@ public non-sealed class PlayerInventory extends AbstractInventory implements Equ
return INNER_INVENTORY_SIZE;
}
private int getSlotId(@NotNull EquipmentSlot slot) {
@Override
public byte getWindowId() {
return 0;
}
private int getSlotId(@NotNull EquipmentSlot slot, byte heldSlot) {
return switch (slot) {
case MAIN_HAND -> player.getHeldSlot();
case MAIN_HAND -> heldSlot;
case OFF_HAND -> OFFHAND_SLOT;
default -> slot.armorSlot();
};
}
@Override
public @NotNull ItemStack getEquipment(@NotNull EquipmentSlot slot) {
if (slot == EquipmentSlot.BODY) return ItemStack.AIR;
return getItemStack(getSlotId(slot));
private @Nullable EquipmentSlot getEquipmentSlot(int slot, byte heldSlot) {
return switch (slot) {
case OFFHAND_SLOT -> EquipmentSlot.OFF_HAND;
case HELMET_SLOT -> EquipmentSlot.HELMET;
case CHESTPLATE_SLOT -> EquipmentSlot.CHESTPLATE;
case LEGGINGS_SLOT -> EquipmentSlot.LEGGINGS;
case BOOTS_SLOT -> EquipmentSlot.BOOTS;
default -> slot == heldSlot ? EquipmentSlot.MAIN_HAND : null;
};
}
@Override
public void setEquipment(@NotNull EquipmentSlot slot, @NotNull ItemStack itemStack) {
public @NotNull ItemStack getEquipment(@NotNull EquipmentSlot slot, byte heldSlot) {
if (slot == EquipmentSlot.BODY) return ItemStack.AIR;
return getItemStack(getSlotId(slot, heldSlot));
}
public void setEquipment(@NotNull EquipmentSlot slot, byte heldSlot, @NotNull ItemStack itemStack) {
if (slot == EquipmentSlot.BODY)
Check.fail("PlayerInventory does not support body equipment");
safeItemInsert(getSlotId(slot), itemStack);
setItemStack(getSlotId(slot, heldSlot), itemStack);
}
/**
* Refreshes the player inventory by sending a {@link WindowItemsPacket} containing all.
* the inventory items
*/
@Override
public void update() {
this.player.sendPacket(createWindowItemsPacket());
public void update(@NotNull Player player) {
player.sendPacket(createWindowItemsPacket());
}
/**
@ -93,49 +102,42 @@ public non-sealed class PlayerInventory extends AbstractInventory implements Equ
public void setCursorItem(@NotNull ItemStack cursorItem) {
if (this.cursorItem.equals(cursorItem)) return;
this.cursorItem = cursorItem;
final SetSlotPacket setSlotPacket = SetSlotPacket.createCursorPacket(cursorItem);
this.player.sendPacket(setSlotPacket);
sendPacketToViewers(SetSlotPacket.createCursorPacket(cursorItem));
}
@Override
protected void UNSAFE_itemInsert(int slot, @NotNull ItemStack itemStack, boolean sendPacket) {
final EquipmentSlot equipmentSlot = switch (slot) {
case HELMET_SLOT -> EquipmentSlot.HELMET;
case CHESTPLATE_SLOT -> EquipmentSlot.CHESTPLATE;
case LEGGINGS_SLOT -> EquipmentSlot.LEGGINGS;
case BOOTS_SLOT -> EquipmentSlot.BOOTS;
case OFFHAND_SLOT -> EquipmentSlot.OFF_HAND;
default -> slot == player.getHeldSlot() ? EquipmentSlot.MAIN_HAND : null;
};
if (equipmentSlot != null) {
EntityEquipEvent entityEquipEvent = new EntityEquipEvent(player, itemStack, equipmentSlot);
EventDispatcher.call(entityEquipEvent);
itemStack = entityEquipEvent.getEquippedItem();
this.player.updateEquipmentAttributes(this.itemStacks[slot], itemStack, equipmentSlot);
}
this.itemStacks[slot] = itemStack;
protected void UNSAFE_itemInsert(int slot, @NotNull ItemStack item, @NotNull ItemStack previous, boolean sendPacket) {
for (Player player : getViewers()) {
final EquipmentSlot equipmentSlot = getEquipmentSlot(slot, player.getHeldSlot());
if (equipmentSlot == null) continue;
if (sendPacket) {
// Sync equipment
if (equipmentSlot != null) this.player.syncEquipment(equipmentSlot);
// Refresh slot
sendSlotRefresh((short) convertToPacketSlot(slot), itemStack);
EntityEquipEvent entityEquipEvent = new EntityEquipEvent(player, item, equipmentSlot);
EventDispatcher.call(entityEquipEvent);
item = entityEquipEvent.getEquippedItem();
}
super.UNSAFE_itemInsert(slot, item, previous, sendPacket);
}
/**
* Refreshes an inventory slot.
*
* @param slot the packet slot,
* see {@link net.minestom.server.utils.inventory.PlayerInventoryUtils#convertToPacketSlot(int)}
* @param itemStack the item stack in the slot
*/
protected void sendSlotRefresh(short slot, ItemStack itemStack) {
var openInventory = player.getOpenInventory();
if (openInventory != null && slot >= OFFSET && slot < OFFSET + INNER_INVENTORY_SIZE) {
this.player.sendPacket(new SetSlotPacket(openInventory.getWindowId(), 0, (short) (slot + openInventory.getSize() - OFFSET), itemStack));
} else if (openInventory == null || slot == OFFHAND_SLOT) {
this.player.sendPacket(new SetSlotPacket((byte) 0, 0, slot, itemStack));
@Override
public void sendSlotRefresh(int slot, @NotNull ItemStack item, @NotNull ItemStack previous) {
SetSlotPacket defaultPacket = new SetSlotPacket(getWindowId(), 0, (byte) slot, item);
for (Player player : getViewers()) {
// Equipment handling
final EquipmentSlot equipmentSlot = getEquipmentSlot(slot, player.getHeldSlot());
if (equipmentSlot != null) {
player.updateEquipmentAttributes(previous, item, equipmentSlot);
player.syncEquipment(equipmentSlot);
}
// Slot handling
AbstractInventory openInventory = player.getOpenInventory();
if (openInventory != null && slot >= OFFSET && slot < OFFSET + INNER_INVENTORY_SIZE) {
player.sendPacket(new SetSlotPacket(openInventory.getWindowId(), 0, (short) (slot + openInventory.getSize() - OFFSET), item));
} else if (openInventory == null || slot == OFFHAND_SLOT) {
player.sendPacket(defaultPacket);
}
}
}

View File

@ -14,7 +14,7 @@ public interface TransactionOption<T> {
* The remaining, can be air.
*/
TransactionOption<ItemStack> ALL = (inventory, result, itemChangesMap) -> {
itemChangesMap.forEach(inventory::safeItemInsert);
itemChangesMap.forEach(inventory::setItemStack);
return result;
};
@ -26,7 +26,7 @@ public interface TransactionOption<T> {
TransactionOption<Boolean> ALL_OR_NOTHING = (inventory, result, itemChangesMap) -> {
if (result.isAir()) {
// Item can be fully placed inside the inventory, do so
itemChangesMap.forEach(inventory::safeItemInsert);
itemChangesMap.forEach(inventory::setItemStack);
return true;
} else {
// Inventory cannot accept the item fully

View File

@ -454,10 +454,9 @@ public final class InventoryClickProcessor {
return clickResult;
}
private void callClickEvent(@NotNull Player player, @Nullable AbstractInventory inventory, int slot,
private void callClickEvent(@NotNull Player player, @NotNull AbstractInventory inventory, int slot,
@NotNull ClickType clickType, @NotNull ItemStack clicked, @NotNull ItemStack cursor) {
final Inventory eventInventory = inventory instanceof Inventory ? (Inventory) inventory : null;
EventDispatcher.call(new InventoryClickEvent(eventInventory, player, slot, clickType, clicked, cursor));
EventDispatcher.call(new InventoryClickEvent(inventory, player, slot, clickType, clicked, cursor));
}
public void clearCache(@NotNull Player player) {

View File

@ -37,7 +37,6 @@ public class BlockPlacementListener {
private static final BlockManager BLOCK_MANAGER = MinecraftServer.getBlockManager();
public static void listener(ClientPlayerBlockPlacementPacket packet, Player player) {
final PlayerInventory playerInventory = player.getInventory();
final PlayerHand hand = packet.hand();
final BlockFace blockFace = packet.blockFace();
Point blockPosition = packet.blockPosition();
@ -179,10 +178,10 @@ public class BlockPlacementListener {
if (playerBlockPlaceEvent.doesConsumeBlock()) {
// Consume the block in the player's hand
final ItemStack newUsedItem = usedItem.consume(1);
playerInventory.setItemInHand(hand, newUsedItem);
player.setItemInHand(hand, newUsedItem);
} else {
// Prevent invisible item on client
playerInventory.update();
player.getInventory().update();
}
}

View File

@ -18,7 +18,6 @@ import net.minestom.server.event.player.PlayerSwapItemEvent;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.block.BlockFace;
import net.minestom.server.inventory.PlayerInventory;
import net.minestom.server.item.ItemComponent;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.component.BlockPredicates;
@ -128,12 +127,12 @@ public final class PlayerDiggingListener {
}
private static void dropStack(Player player) {
final ItemStack droppedItemStack = player.getInventory().getItemInMainHand();
final ItemStack droppedItemStack = player.getItemInMainHand();
dropItem(player, droppedItemStack, ItemStack.AIR);
}
private static void dropSingle(Player player) {
final ItemStack handItem = player.getInventory().getItemInMainHand();
final ItemStack handItem = player.getItemInMainHand();
final int handAmount = handItem.amount();
if (handAmount <= 1) {
// Drop the whole item without copy
@ -162,13 +161,12 @@ public final class PlayerDiggingListener {
}
private static void swapItemHand(Player player) {
final PlayerInventory inventory = player.getInventory();
final ItemStack mainHand = inventory.getItemInMainHand();
final ItemStack offHand = inventory.getItemInOffHand();
final ItemStack mainHand = player.getItemInMainHand();
final ItemStack offHand = player.getItemInOffHand();
PlayerSwapItemEvent swapItemEvent = new PlayerSwapItemEvent(player, offHand, mainHand);
EventDispatcher.callCancellable(swapItemEvent, () -> {
inventory.setItemInMainHand(swapItemEvent.getMainHandItem());
inventory.setItemInOffHand(swapItemEvent.getOffHandItem());
player.setItemInMainHand(swapItemEvent.getMainHandItem());
player.setItemInOffHand(swapItemEvent.getOffHandItem());
});
}
@ -192,11 +190,10 @@ public final class PlayerDiggingListener {
private static void dropItem(@NotNull Player player,
@NotNull ItemStack droppedItem, @NotNull ItemStack handItem) {
final PlayerInventory playerInventory = player.getInventory();
if (player.dropItem(droppedItem)) {
playerInventory.setItemInMainHand(handItem);
player.setItemInMainHand(handItem);
} else {
playerInventory.update();
player.getInventory().update();
}
}

View File

@ -20,7 +20,7 @@ public class UseItemListener {
public static void useItemListener(ClientUseItemPacket packet, Player player) {
final PlayerHand hand = packet.hand();
final ItemStack itemStack = player.getInventory().getItemInHand(hand);
final ItemStack itemStack = player.getItemInHand(hand);
final Material material = itemStack.material();
boolean usingMainHand = player.getItemUseHand() == Player.Hand.MAIN && hand == Player.Hand.OFF;

View File

@ -27,7 +27,7 @@ public class PlayerCreativeSlotTest {
player.setGameMode(GameMode.CREATIVE);
player.addPacketToQueue(new ClientCreativeInventoryActionPacket((short) PlayerInventoryUtils.OFFHAND_SLOT, ItemStack.of(Material.STICK)));
player.interpretPacketQueue();
assertEquals(Material.STICK, player.getInventory().getItemInOffHand().material());
assertEquals(Material.STICK, player.getItemInOffHand().material());
}
@Test

View File

@ -118,19 +118,19 @@ public class PlayerInventoryIntegrationTest {
var equipmentTracker = connectionViewer.trackIncoming(EntityEquipmentPacket.class);
// Setting to an item should send EntityEquipmentPacket to viewer
playerArmored.getInventory().setEquipment(EquipmentSlot.HELMET, MAGIC_STACK);
playerArmored.setEquipment(EquipmentSlot.HELMET, MAGIC_STACK);
equipmentTracker.assertSingle(entityEquipmentPacket -> {
assertEquals(MAGIC_STACK, entityEquipmentPacket.equipments().get(EquipmentSlot.HELMET));
});
// Setting to the same item shouldn't send packet
equipmentTracker = connectionViewer.trackIncoming(EntityEquipmentPacket.class);
playerArmored.getInventory().setEquipment(EquipmentSlot.HELMET, MAGIC_STACK);
playerArmored.setEquipment(EquipmentSlot.HELMET, MAGIC_STACK);
equipmentTracker.assertEmpty();
// Setting to air should send packet
equipmentTracker = connectionViewer.trackIncoming(EntityEquipmentPacket.class);
playerArmored.getInventory().setEquipment(EquipmentSlot.HELMET, ItemStack.AIR);
playerArmored.setEquipment(EquipmentSlot.HELMET, ItemStack.AIR);
equipmentTracker.assertSingle(entityEquipmentPacket -> {
assertEquals(ItemStack.AIR, entityEquipmentPacket.equipments().get(EquipmentSlot.HELMET));
});

View File

@ -3,6 +3,7 @@ package net.minestom.server.inventory.click.integration;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Player;
import net.minestom.server.event.inventory.InventoryPreClickEvent;
import net.minestom.server.inventory.AbstractInventory;
import net.minestom.server.inventory.Inventory;
import net.minestom.server.inventory.InventoryType;
import net.minestom.server.inventory.click.ClickType;
@ -82,7 +83,7 @@ public class HeldClickIntegrationTest {
});
heldClick(player, 3, 40);
assertEquals(ItemStack.AIR, inventory.getItemStack(3));
assertEquals(ItemStack.of(Material.EGG), inventory.getItemInOffHand());
assertEquals(ItemStack.of(Material.EGG), player.getItemInOffHand());
}
// Cancel event
{
@ -151,7 +152,7 @@ public class HeldClickIntegrationTest {
});
heldClickOpenInventory(player, 3, 40);
assertEquals(ItemStack.AIR, inventory.getItemStack(3));
assertEquals(ItemStack.of(Material.EGG), playerInv.getItemInOffHand());
assertEquals(ItemStack.of(Material.EGG), player.getItemInOffHand());
}
// Cancel event
{
@ -171,7 +172,7 @@ public class HeldClickIntegrationTest {
_heldClick(player.getOpenInventory(), false, player, slot, target);
}
private void _heldClick(Inventory openInventory, boolean clickOpenInventory, Player player, int slot, int target) {
private void _heldClick(AbstractInventory openInventory, boolean clickOpenInventory, Player player, int slot, int target) {
final byte windowId = openInventory != null ? openInventory.getWindowId() : 0;
if (clickOpenInventory) {
assert openInventory != null;

View File

@ -4,6 +4,7 @@ package net.minestom.server.inventory.click.integration;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Player;
import net.minestom.server.event.inventory.InventoryPreClickEvent;
import net.minestom.server.inventory.AbstractInventory;
import net.minestom.server.inventory.Inventory;
import net.minestom.server.inventory.InventoryType;
import net.minestom.server.inventory.click.ClickType;
@ -154,7 +155,7 @@ public class LeftClickIntegrationTest {
_leftClick(player.getOpenInventory(), false, player, slot);
}
private void _leftClick(Inventory openInventory, boolean clickOpenInventory, Player player, int slot) {
private void _leftClick(AbstractInventory openInventory, boolean clickOpenInventory, Player player, int slot) {
final byte windowId = openInventory != null ? openInventory.getWindowId() : 0;
if (clickOpenInventory) {
assert openInventory != null;

View File

@ -3,6 +3,7 @@ package net.minestom.server.inventory.click.integration;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Player;
import net.minestom.server.event.inventory.InventoryPreClickEvent;
import net.minestom.server.inventory.AbstractInventory;
import net.minestom.server.inventory.Inventory;
import net.minestom.server.inventory.InventoryType;
import net.minestom.server.inventory.click.ClickType;
@ -175,7 +176,7 @@ public class RightClickIntegrationTest {
_rightClick(player.getOpenInventory(), false, player, slot);
}
private void _rightClick(Inventory openInventory, boolean clickOpenInventory, Player player, int slot) {
private void _rightClick(AbstractInventory openInventory, boolean clickOpenInventory, Player player, int slot) {
final byte windowId = openInventory != null ? openInventory.getWindowId() : 0;
if (clickOpenInventory) {
assert openInventory != null;