diff --git a/src/main/java/net/minestom/server/inventory/click/Click.java b/src/main/java/net/minestom/server/inventory/click/Click.java index 159a00e3f..31da28e7a 100644 --- a/src/main/java/net/minestom/server/inventory/click/Click.java +++ b/src/main/java/net/minestom/server/inventory/click/Click.java @@ -22,19 +22,35 @@ public final class Click { * The inventory used should be known from context. */ public sealed interface Info { - record Left(int slot) implements Info {} record Right(int slot) implements Info {} - record Middle(int slot) implements Info {} // Creative only + record Middle(int slot) implements Info { + // Creative only + } record LeftShift(int slot) implements Info {} record RightShift(int slot) implements Info {} record Double(int slot) implements Info {} - record LeftDrag(List slots) implements Info {} - record RightDrag(List slots) implements Info {} - record MiddleDrag(List slots) implements Info {} // Creative only + record LeftDrag(List slots) implements Info { + public LeftDrag { + slots = List.copyOf(slots); + } + } + + record RightDrag(List slots) implements Info { + public RightDrag { + slots = List.copyOf(slots); + } + } + + record MiddleDrag(List slots) implements Info { + // Creative only + public MiddleDrag { + slots = List.copyOf(slots); + } + } record LeftDropCursor() implements Info {} record RightDropCursor() implements Info {} @@ -47,14 +63,12 @@ public final class Click { record CreativeSetItem(int slot, @NotNull ItemStack item) implements Info {} record CreativeDropItem(@NotNull ItemStack item) implements Info {} - } /** * Preprocesses click packets, turning them into {@link Info} instances for further processing. */ public static final class Preprocessor { - private final List leftDrag = new ArrayList<>(); private final List rightDrag = new ArrayList<>(); private final List middleDrag = new ArrayList<>(); @@ -69,8 +83,8 @@ public final class Click { * Processes the provided click packet, turning it into a {@link Info}. * This will do simple verification of the packet before sending it to {@link #process(ClientClickWindowPacket.ClickType, int, byte, boolean)}. * - * @param packet the raw click packet - * @param isCreative whether or not the player is in creative mode (used for ignoring some actions) + * @param packet the raw click packet + * @param isCreative whether the player is in creative mode (used for ignoring some actions) * @return the information about the click, or nothing if there was no immediately usable information */ public @Nullable Click.Info process(@NotNull ClientClickWindowPacket packet, @NotNull Inventory inventory, boolean isCreative) { @@ -81,25 +95,24 @@ public final class Click { int slot = inventory instanceof PlayerInventory ? PlayerInventoryUtils.protocolToMinestom(originalSlot) : originalSlot; if (originalSlot == -999) slot = -999; - boolean creativeRequired = switch (type) { + final boolean creativeRequired = switch (type) { case CLONE -> true; case QUICK_CRAFT -> button == 8 || button == 9 || button == 10; default -> false; }; - if (creativeRequired && !isCreative) return null; - int maxSize = inventory.getSize() + (inventory instanceof PlayerInventory ? 0 : PlayerInventoryUtils.INNER_SIZE); + final int maxSize = inventory.getSize() + (inventory instanceof PlayerInventory ? 0 : PlayerInventoryUtils.INNER_SIZE); return process(type, slot, button, slot >= 0 && slot < maxSize); } /** * Processes a packet into click info. * - * @param type the type of the click - * @param slot the clicked slot - * @param button the sent button - * @param valid whether or not {@code slot} fits within the inventory (may be unused, depending on click) + * @param type the type of the click + * @param slot the clicked slot + * @param button the sent button + * @param valid whether {@code slot} fits within the inventory (may be unused, depending on click) * @return the information about the click, or nothing if there was no immediately usable information */ public @Nullable Click.Info process(@NotNull ClientClickWindowPacket.ClickType type, @@ -182,7 +195,6 @@ public final class Click { public record Getter(@NotNull IntFunction main, @NotNull IntFunction player, @NotNull ItemStack cursor, int mainSize) { - public @NotNull ItemStack get(int slot) { if (slot < mainSize()) { return main.apply(slot); @@ -196,8 +208,7 @@ public final class Click { } } - public static class Setter { - + public static final class Setter { private final Map main = new HashMap<>(); private final Map player = new HashMap<>(); private @Nullable ItemStack cursor; @@ -237,37 +248,32 @@ public final class Click { public @NotNull Click.Result build() { return new Result(main, player, cursor, sideEffect); } - } /** * Stores changes that occurred or will occur as the result of a click. - * @param changes the map of changes that will occur to the inventory + * + * @param changes the map of changes that will occur to the inventory * @param playerInventoryChanges the map of changes that will occur to the player inventory - * @param newCursorItem the player's cursor item after this click. Null indicates no change - * @param sideEffects the side effects of this click + * @param newCursorItem the player's cursor item after this click. Null indicates no change + * @param sideEffects the side effects of this click */ - public record Result(@NotNull Map changes, @NotNull Map playerInventoryChanges, + public record Result(@NotNull Map changes, + @NotNull Map playerInventoryChanges, @Nullable ItemStack newCursorItem, @Nullable Click.SideEffect sideEffects) { - - public static @NotNull Result nothing() { - return new Result(Map.of(), Map.of(), null, null); - } + public static final Result NOTHING = new Result(Map.of(), Map.of(), null, null); public Result { changes = Map.copyOf(changes); playerInventoryChanges = Map.copyOf(playerInventoryChanges); } - } /** * Represents side effects that may occur as the result of an inventory click. */ public sealed interface SideEffect { - - record DropFromPlayer(@NotNull List items) implements SideEffect { - + record DropFromPlayer(@NotNull List<@NotNull ItemStack> items) implements SideEffect { public DropFromPlayer { items = List.copyOf(items); } diff --git a/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java b/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java index 7a159b0fb..3a5ae9d65 100644 --- a/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java +++ b/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java @@ -18,12 +18,11 @@ import java.util.stream.IntStream; * Provides standard implementations of most click functions. */ public final class ClickProcessors { - private static final @NotNull StackingRule RULE = StackingRule.get(); public static @NotNull Click.Result leftClick(int slot, @NotNull Click.Getter getter) { - ItemStack cursor = getter.cursor(); - ItemStack clickedItem = getter.get(slot); + final ItemStack cursor = getter.cursor(); + final ItemStack clickedItem = getter.get(slot); Pair pair = TransactionOperator.STACK_LEFT.apply(clickedItem, cursor); if (pair != null) { // Stackable items, combine their counts @@ -31,24 +30,23 @@ public final class ClickProcessors { } else if (!RULE.canBeStacked(cursor, clickedItem)) { // If they're unstackable, switch them return getter.setter().set(slot, cursor).cursor(clickedItem).build(); } else { - return Click.Result.nothing(); + return Click.Result.NOTHING; } } public static @NotNull Click.Result rightClick(int slot, @NotNull Click.Getter getter) { - ItemStack cursor = getter.cursor(); - ItemStack clickedItem = getter.get(slot); - - if (cursor.isAir() && clickedItem.isAir()) return Click.Result.nothing(); // Both are air, no changes + final ItemStack cursor = getter.cursor(); + final ItemStack clickedItem = getter.get(slot); + if (cursor.isAir() && clickedItem.isAir()) return Click.Result.NOTHING; // Both are air, no changes if (cursor.isAir()) { // Take half (rounded up) of the clicked item int newAmount = (int) Math.ceil(RULE.getAmount(clickedItem) / 2d); Pair cursorSlot = TransactionOperator.stackLeftN(newAmount).apply(cursor, clickedItem); - return cursorSlot == null ? Click.Result.nothing() : + return cursorSlot == null ? Click.Result.NOTHING : getter.setter().cursor(cursorSlot.left()).set(slot, cursorSlot.right()).build(); } else if (clickedItem.isAir() || RULE.canBeStacked(clickedItem, cursor)) { // Can add, transfer one over Pair slotCursor = TransactionOperator.stackLeftN(1).apply(clickedItem, cursor); - return slotCursor == null ? Click.Result.nothing() : + return slotCursor == null ? Click.Result.NOTHING : getter.setter().set(slot, slotCursor.left()).cursor(slotCursor.right()).build(); } else { // Two existing of items of different types, so switch return getter.setter().cursor(clickedItem).set(slot, cursor).build(); @@ -60,12 +58,12 @@ public final class ClickProcessors { if (getter.cursor().isAir() && !item.isAir()) { return getter.setter().cursor(RULE.apply(item, RULE.getMaxSize(item))).build(); } else { - return Click.Result.nothing(); + return Click.Result.NOTHING; } } public static @NotNull Click.Result shiftClick(int slot, @NotNull List slots, @NotNull Click.Getter getter) { - ItemStack clicked = getter.get(slot); + final ItemStack clicked = getter.get(slot); slots = new ArrayList<>(slots); slots.removeIf(i -> i == slot); @@ -80,8 +78,8 @@ public final class ClickProcessors { } public static @NotNull Click.Result doubleClick(@NotNull List slots, @NotNull Click.Getter getter) { - var cursor = getter.cursor(); - if (cursor.isAir()) Click.Result.nothing(); + final ItemStack cursor = getter.cursor(); + if (cursor.isAir()) return Click.Result.NOTHING; var unstacked = TransactionType.general(TransactionOperator.filter(TransactionOperator.STACK_RIGHT, (left, right) -> RULE.getAmount(left) < RULE.getMaxSize(left)), slots); var stacked = TransactionType.general(TransactionOperator.filter(TransactionOperator.STACK_RIGHT, (left, right) -> RULE.getAmount(left) == RULE.getMaxSize(left)), slots); @@ -96,8 +94,8 @@ public final class ClickProcessors { } public static @NotNull Click.Result dragClick(int countPerSlot, @NotNull List slots, @NotNull Click.Getter getter) { - var cursor = getter.cursor(); - if (cursor.isAir()) return Click.Result.nothing(); + final ItemStack cursor = getter.cursor(); + if (cursor.isAir()) return Click.Result.NOTHING; Pair> result = TransactionType.general(TransactionOperator.stackLeftN(countPerSlot), slots).process(cursor, getter::get); Click.Setter setter = getter.setter(); @@ -109,25 +107,22 @@ public final class ClickProcessors { } public static @NotNull Click.Result middleDragClick(@NotNull List slots, @NotNull Click.Getter getter) { - var cursor = getter.cursor(); - + final ItemStack cursor = getter.cursor(); Click.Setter setter = getter.setter(); - for (int slot : slots) { if (getter.get(slot).isAir()) { setter.set(slot, cursor); } } - return setter.build(); } public static @NotNull Click.Result dropFromCursor(int amount, @NotNull Click.Getter getter) { - var cursor = getter.cursor(); - if (cursor.isAir()) Click.Result.nothing(); // Do nothing + final ItemStack cursor = getter.cursor(); + if (cursor.isAir()) return Click.Result.NOTHING; // Do nothing var pair = TransactionOperator.stackLeftN(amount).apply(ItemStack.AIR, cursor); - if (pair == null) return Click.Result.nothing(); + if (pair == null) return Click.Result.NOTHING; return getter.setter().cursor(pair.right()) .sideEffects(new Click.SideEffect.DropFromPlayer(pair.left())) @@ -135,11 +130,11 @@ public final class ClickProcessors { } public static @NotNull Click.Result dropFromSlot(int slot, int amount, @NotNull Click.Getter getter) { - var item = getter.get(slot); - if (item.isAir()) return Click.Result.nothing(); // Do nothing + final ItemStack item = getter.get(slot); + if (item.isAir()) return Click.Result.NOTHING; // Do nothing var pair = TransactionOperator.stackLeftN(amount).apply(ItemStack.AIR, item); - if (pair == null) return Click.Result.nothing(); + if (pair == null) return Click.Result.NOTHING; return getter.setter().set(slot, pair.right()) .sideEffects(new Click.SideEffect.DropFromPlayer(pair.left())) @@ -150,9 +145,10 @@ public final class ClickProcessors { * Handles clicks, given a shift click provider and a double click provider.
* When shift clicks or double clicks need to be handled, the slots provided from the relevant handler will be * checked in their given order.
- * For example, double clicking will collect items of the same type as the cursor; the slots provided by the double + * For example, double-clicking will collect items of the same type as the cursor; the slots provided by the double * click slot provider will be checked sequentially and used if they have the same type as - * @param shiftClickSlots the shift click slot supplier + * + * @param shiftClickSlots the shift click slot supplier * @param doubleClickSlots the double click slot supplier */ public static @NotNull BiFunction standard(@NotNull SlotSuggestor shiftClickSlots, @NotNull SlotSuggestor doubleClickSlots) { @@ -173,18 +169,18 @@ public final class ClickProcessors { case Click.Info.DropSlot(int slot, boolean all) -> dropFromSlot(slot, all ? RULE.getAmount(getter.get(slot)) : 1, getter); case Click.Info.LeftDropCursor() -> dropFromCursor(getter.cursor().amount(), getter); case Click.Info.RightDropCursor() -> dropFromCursor(1, getter); - case Click.Info.MiddleDropCursor() -> Click.Result.nothing(); + case Click.Info.MiddleDropCursor() -> Click.Result.NOTHING; case Click.Info.HotbarSwap(int hotbarSlot, int clickedSlot) -> { var hotbarItem = getter.player().apply(hotbarSlot); var selectedItem = getter.get(clickedSlot); - if (hotbarItem.equals(selectedItem)) yield Click.Result.nothing(); + if (hotbarItem.equals(selectedItem)) yield Click.Result.NOTHING; yield getter.setter().setPlayer(hotbarSlot, selectedItem).set(clickedSlot, hotbarItem).build(); } case Click.Info.OffhandSwap(int slot) -> { var offhandItem = getter.player().apply(PlayerInventoryUtils.OFF_HAND_SLOT); var selectedItem = getter.get(slot); - if (offhandItem.equals(selectedItem)) yield Click.Result.nothing(); + if (offhandItem.equals(selectedItem)) yield Click.Result.NOTHING; yield getter.setter().setPlayer(PlayerInventoryUtils.OFF_HAND_SLOT, selectedItem).set(slot, offhandItem).build(); } @@ -204,16 +200,17 @@ public final class ClickProcessors { /** * Suggests slots to be used for this operation. + * * @param builder the result builder - * @param item the item clicked - * @param slot the slot of the clicked item + * @param item the item clicked + * @param slot the slot of the clicked item * @return the list of slots, in order of priority, to be used for this operation */ - @NotNull IntStream get(@NotNull Click.Getter builder, @NotNull ItemStack item, int slot); + @NotNull + IntStream get(@NotNull Click.Getter builder, @NotNull ItemStack item, int slot); default @NotNull List getList(@NotNull Click.Getter builder, @NotNull ItemStack item, int slot) { return get(builder, item, slot).boxed().toList(); } } - } diff --git a/src/main/java/net/minestom/server/utils/inventory/PlayerInventoryUtils.java b/src/main/java/net/minestom/server/utils/inventory/PlayerInventoryUtils.java index 793753f6e..17fba1f4f 100644 --- a/src/main/java/net/minestom/server/utils/inventory/PlayerInventoryUtils.java +++ b/src/main/java/net/minestom/server/utils/inventory/PlayerInventoryUtils.java @@ -5,11 +5,10 @@ package net.minestom.server.utils.inventory; * Minestom uses different slot IDs for player inventories as the Minecraft protocol uses a strange system (e.g. the * crafting result is the first slot).
* These can be mapped 1:1 to and from protocol slots using {@link #minestomToProtocol(int)} and {@link #protocolToMinestom(int)}.
- * + *

* Read about protocol slot IDs here. */ public final class PlayerInventoryUtils { - public static final int INVENTORY_SIZE = 46; public static final int INNER_SIZE = 36; @@ -28,12 +27,12 @@ public final class PlayerInventoryUtils { public static final int OFF_HAND_SLOT = 45; private PlayerInventoryUtils() { - } /** * Converts a Minestom slot ID to a Minecraft protocol slot ID.
* This is the inverse of {@link #protocolToMinestom(int)}. + * * @param slot the internal slot ID to convert * @return the protocol slot ID, or -1 if the given slot could not be converted */ @@ -64,6 +63,7 @@ public final class PlayerInventoryUtils { /** * Converts a Minecraft protocol slot ID to a Minestom slot ID.
* This is the inverse of {@link #minestomToProtocol(int)}. + * * @param slot the protocol slot ID to convert * @return the Minestom slot ID, or -1 if the given slot could not be converted */ @@ -97,7 +97,7 @@ public final class PlayerInventoryUtils { * open.
* This is the inverse of {@link #protocolToMinestom(int, int)}. * - * @param slot the player slot that was interacted with + * @param slot the player slot that was interacted with * @param openInventorySize the size of the inventory opened by the player (not the player's inventory) * @return the protocol slot ID */ @@ -111,14 +111,12 @@ public final class PlayerInventoryUtils { * open.
* This is the inverse of {@link #minestomToProtocol(int, int)}. * - * @param slot the protocol slot ID, situated directly after the slot IDs for the open inventory + * @param slot the protocol slot ID, situated directly after the slot IDs for the open inventory * @param openInventorySize the size of the inventory opened by the player (not the player's inventory) * @return the player slot ID */ public static int protocolToMinestom(int slot, int openInventorySize) { if (slot < openInventorySize) return -1; - return PlayerInventoryUtils.protocolToMinestom(slot - openInventorySize + PROTOCOL_OFFSET); } - -} \ No newline at end of file +}