diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index ae695bbe9..260ce8675 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -1758,6 +1758,39 @@ public class Player extends LivingEntity implements CommandSender, Localizable, } } + /** + * Updates the player's inventory state, given a window id to compare against. + * Note: This should only be used when receiving a {@link net.minestom.server.network.packet.client.play.ClientCloseWindowPacket}. + * Use {@link #closeInventory()} instead for more general use. + * @param windowId The window id to compare against. + */ + public void updateCloseInventoryState(byte windowId) { + if (windowId == 0) { + // Put cursor item back in inventory if possible + ItemStack stack = getInventory().getCursorItem(); + boolean success = getInventory().addItemStack(stack); + // Drop if inventory is full + if (!success) { + dropItem(stack); + } + getInventory().setCursorItem(ItemStack.AIR); + } else { + if (getOpenInventory() != null && windowId == getOpenInventory().getWindowId()) { + ItemStack stack = getOpenInventory().getCursorItem(this); + // Try to put item into our inventory + boolean success = getInventory().addItemStack(stack); + // Drop if inventory is full + if (!success) { + dropItem(stack); + } + getOpenInventory().setCursorItem(this, ItemStack.AIR); + } else { + // Something has gone wrong + logger.warn("Tried to update inventory state with invalid window id!"); + } + } + } + /** * Used internally to prevent an inventory click to be processed * when the inventory listeners closed the inventory. diff --git a/src/main/java/net/minestom/server/listener/WindowListener.java b/src/main/java/net/minestom/server/listener/WindowListener.java index f56b785c3..8b2e4dd38 100644 --- a/src/main/java/net/minestom/server/listener/WindowListener.java +++ b/src/main/java/net/minestom/server/listener/WindowListener.java @@ -27,8 +27,6 @@ public class WindowListener { final byte button = packet.button(); final ClientClickWindowPacket.ClickType clickType = packet.clickType(); - //System.out.println("Window id: " + windowId + " | slot: " + slot + " | button: " + button + " | clickType: " + clickType); - boolean successful = false; // prevent click in a non-interactive slot (why does it exist?) @@ -90,7 +88,7 @@ public class WindowListener { InventoryCloseEvent inventoryCloseEvent = new InventoryCloseEvent(player.getOpenInventory(), player); EventDispatcher.call(inventoryCloseEvent); - player.closeInventory(); + player.updateCloseInventoryState(packet.windowId()); Inventory newInventory = inventoryCloseEvent.getNewInventory(); if (newInventory != null) @@ -103,10 +101,10 @@ public class WindowListener { */ private static void refreshCursorItem(Player player, AbstractInventory inventory) { ItemStack cursorItem; - if (inventory instanceof PlayerInventory) { - cursorItem = ((PlayerInventory) inventory).getCursorItem(); - } else if (inventory instanceof Inventory) { - cursorItem = ((Inventory) inventory).getCursorItem(player); + if (inventory instanceof PlayerInventory playerInventory) { + cursorItem = playerInventory.getCursorItem(); + } else if (inventory instanceof Inventory standardInventory) { + cursorItem = standardInventory.getCursorItem(player); } else { throw new RuntimeException("Invalid inventory: " + inventory.getClass()); } @@ -115,10 +113,10 @@ public class WindowListener { } private static void setCursor(Player player, AbstractInventory inventory, ItemStack itemStack) { - if (inventory instanceof PlayerInventory) { - ((PlayerInventory) inventory).setCursorItem(itemStack); - } else if (inventory instanceof Inventory) { - ((Inventory) inventory).setCursorItem(player, itemStack); + if (inventory instanceof PlayerInventory playerInventory) { + playerInventory.setCursorItem(itemStack); + } else if (inventory instanceof Inventory standardInventory) { + standardInventory.setCursorItem(player, itemStack); } else { throw new RuntimeException("Invalid inventory: " + inventory.getClass()); } diff --git a/src/test/java/net/minestom/server/inventory/InventoryCloseStateTest.java b/src/test/java/net/minestom/server/inventory/InventoryCloseStateTest.java new file mode 100644 index 000000000..7f62f730f --- /dev/null +++ b/src/test/java/net/minestom/server/inventory/InventoryCloseStateTest.java @@ -0,0 +1,36 @@ +package net.minestom.server.inventory; + +import net.kyori.adventure.text.Component; +import net.minestom.server.coordinate.Pos; +import net.minestom.server.network.ConnectionState; +import net.minestom.server.network.packet.client.play.ClientCloseWindowPacket; +import net.minestom.server.network.packet.server.play.CloseWindowPacket; +import net.minestom.testing.Env; +import net.minestom.testing.EnvTest; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@EnvTest +public class InventoryCloseStateTest { + + + @Test + public void doNotReceiveClosePacketFromServerWhenSendingClientCloseWindowPacket(Env env) { + var instance = env.createFlatInstance(); + var connection = env.createConnection(); + var player = connection.connect(instance, new Pos(0, 42, 0)).join(); + assertEquals(instance, player.getInstance()); + + var packetTracker = connection.trackIncoming(CloseWindowPacket.class); + var inventory = new Inventory(InventoryType.CHEST_2_ROW, Component.text("Test")); + player.openInventory(inventory); + player.closeInventory(); // Closes the inventory server-side, should send a CloseWindowPacket + player.openInventory(inventory); + // Send the close window packet + player.addPacketToQueue(ConnectionState.PLAY, new ClientCloseWindowPacket(inventory.getWindowId())); + player.interpretPacketQueue(); + packetTracker.assertSingle(closeWindowPacket -> assertEquals(inventory.getWindowId(), closeWindowPacket.windowId())); + packetTracker.assertCount(1); // Assert we only get 1 close window packet from the closeInventory(); call + } +}