From 511c3d3e2e27c0f4e44891c80db74bc7edf69ff3 Mon Sep 17 00:00:00 2001 From: GoldenStack Date: Fri, 12 Apr 2024 13:21:04 -0500 Subject: [PATCH] Add Click.Change --- .../server/inventory/ContainerInventory.java | 20 ++++-- .../server/inventory/click/Click.java | 42 +++++-------- .../inventory/click/ClickProcessors.java | 32 +++++----- .../server/inventory/click/ClickUtils.java | 61 ++++++++++++++++--- 4 files changed, 97 insertions(+), 58 deletions(-) diff --git a/src/main/java/net/minestom/server/inventory/ContainerInventory.java b/src/main/java/net/minestom/server/inventory/ContainerInventory.java index 2bb6d6041..3510d817c 100644 --- a/src/main/java/net/minestom/server/inventory/ContainerInventory.java +++ b/src/main/java/net/minestom/server/inventory/ContainerInventory.java @@ -11,6 +11,7 @@ import net.minestom.server.inventory.click.ClickProcessors; import net.minestom.server.item.ItemStack; import net.minestom.server.network.packet.server.play.OpenWindowPacket; import net.minestom.server.network.packet.server.play.WindowPropertyPacket; +import net.minestom.server.utils.inventory.PlayerInventoryUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -72,16 +73,23 @@ public non-sealed class ContainerInventory extends InventoryImpl { } public static void apply(@NotNull Click.Result result, @NotNull Player player, @NotNull Inventory inventory) { - for (var entry : result.changes().entrySet()) { - inventory.setItemStack(entry.getKey(), entry.getValue()); - } + PlayerInventory playerInventory = player.getInventory(); - for (var entry : result.playerInventoryChanges().entrySet()) { - player.getInventory().setItemStack(entry.getKey(), entry.getValue()); + for (var change : result.changes()) { + if (change instanceof Click.Change.Main(int slot, ItemStack item)) { + if (slot < inventory.getSize()) { + inventory.setItemStack(slot, item); + } else { + int converted = PlayerInventoryUtils.protocolToMinestom(slot, inventory.getSize()); + playerInventory.setItemStack(converted, item); + } + } else if (change instanceof Click.Change.Player(int slot, ItemStack item)) { + playerInventory.setItemStack(slot, item); + } } if (result.newCursorItem() != null) { - player.getInventory().setCursorItem(result.newCursorItem()); + playerInventory.setCursorItem(result.newCursorItem()); } if (result.sideEffects() instanceof Click.SideEffect.DropFromPlayer drop) { 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 7623a6d4b..448c6309f 100644 --- a/src/main/java/net/minestom/server/inventory/click/Click.java +++ b/src/main/java/net/minestom/server/inventory/click/Click.java @@ -7,9 +7,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.function.Consumer; import java.util.function.IntFunction; @@ -204,36 +202,28 @@ public final class Click { return player.apply(PlayerInventoryUtils.protocolToMinestom(slot, mainSize())); } } + } - public @NotNull Click.Setter setter() { - return new Setter(mainSize); - } + public sealed interface Change { + record Main(int slot, @NotNull ItemStack item) implements Change {} + record Player(int slot, @NotNull ItemStack item) implements Change {} } public static final class Setter { - private final Map main = new HashMap<>(); - private final Map player = new HashMap<>(); + private final List changes = new ArrayList<>(); private @Nullable ItemStack cursor; private @Nullable SideEffect sideEffect; - private final int clickedSize; - - Setter(int clickedSize) { - this.clickedSize = clickedSize; + Setter() { } public @NotNull Setter set(int slot, @NotNull ItemStack item) { - if (slot >= clickedSize) { - int converted = PlayerInventoryUtils.protocolToMinestom(slot, clickedSize); - return setPlayer(converted, item); - } else { - main.put(slot, item); - return this; - } + changes.add(new Change.Main(slot, item)); + return this; } public @NotNull Setter setPlayer(int slot, @NotNull ItemStack item) { - player.put(slot, item); + changes.add(new Change.Player(slot, item)); return this; } @@ -248,26 +238,22 @@ public final class Click { } public @NotNull Click.Result build() { - return new Result(main, player, cursor, sideEffect); + return new Result(changes, 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 playerInventoryChanges the map of changes that will occur to the player inventory + * @param changes the list of changes that will occur * @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, - @Nullable ItemStack newCursorItem, @Nullable Click.SideEffect sideEffects) { - public static final Result NOTHING = new Result(Map.of(), Map.of(), null, null); + public record Result(@NotNull List changes, @Nullable ItemStack newCursorItem, @Nullable Click.SideEffect sideEffects) { + public static final Result NOTHING = new Result(List.of(), null, null); public Result { - changes = Map.copyOf(changes); - playerInventoryChanges = Map.copyOf(playerInventoryChanges); + changes = List.copyOf(changes); } } 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 666618b5f..cc6892cbf 100644 --- a/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java +++ b/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java @@ -34,9 +34,9 @@ public final class ClickProcessors { Pair pair = TransactionOperator.STACK_LEFT.apply(clickedItem, cursor); if (pair != null) { // Stackable items, combine their counts - return getter.setter().set(slot, pair.left()).cursor(pair.right()).build(); + return new Click.Setter().set(slot, pair.left()).cursor(pair.right()).build(); } else if (!RULE.canBeStacked(cursor, clickedItem)) { // If they're unstackable, switch them - return getter.setter().set(slot, cursor).cursor(clickedItem).build(); + return new Click.Setter().set(slot, cursor).cursor(clickedItem).build(); } else { return Click.Result.NOTHING; } @@ -51,20 +51,20 @@ public final class ClickProcessors { int newAmount = (int) Math.ceil(RULE.getAmount(clickedItem) / 2d); Pair cursorSlot = TransactionOperator.stackLeftN(newAmount).apply(cursor, clickedItem); return cursorSlot == null ? Click.Result.NOTHING : - getter.setter().cursor(cursorSlot.left()).set(slot, cursorSlot.right()).build(); + new Click.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 : - getter.setter().set(slot, slotCursor.left()).cursor(slotCursor.right()).build(); + new Click.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(); + return new Click.Setter().cursor(clickedItem).set(slot, cursor).build(); } } public static @NotNull Click.Result middleClick(int slot, @NotNull Click.Getter getter) { var item = getter.get(slot); if (getter.cursor().isAir() && !item.isAir()) { - return getter.setter().cursor(RULE.apply(item, RULE.getMaxSize(item))).build(); + return new Click.Setter().cursor(RULE.apply(item, RULE.getMaxSize(item))).build(); } else { return Click.Result.NOTHING; } @@ -77,7 +77,7 @@ public final class ClickProcessors { slots.removeIf(i -> i == slot); Pair> result = TransactionType.add(slots, slots).process(clicked, getter::get); - Click.Setter setter = getter.setter(); + Click.Setter setter = new Click.Setter(); result.right().forEach(setter::set); return !result.left().equals(clicked) ? @@ -93,7 +93,7 @@ public final class ClickProcessors { var stacked = TransactionType.general(TransactionOperator.filter(TransactionOperator.STACK_RIGHT, (left, right) -> RULE.getAmount(left) == RULE.getMaxSize(left)), slots); Pair> result = TransactionType.join(unstacked, stacked).process(cursor, getter::get); - Click.Setter setter = getter.setter(); + Click.Setter setter = new Click.Setter(); result.right().forEach(setter::set); return !result.left().equals(cursor) ? @@ -106,7 +106,7 @@ public final class ClickProcessors { if (cursor.isAir()) return Click.Result.NOTHING; Pair> result = TransactionType.general(TransactionOperator.stackLeftN(countPerSlot), slots).process(cursor, getter::get); - Click.Setter setter = getter.setter(); + Click.Setter setter = new Click.Setter(); result.right().forEach(setter::set); return !result.left().equals(cursor) ? @@ -116,7 +116,7 @@ public final class ClickProcessors { public static @NotNull Click.Result middleDragClick(@NotNull List slots, @NotNull Click.Getter getter) { final ItemStack cursor = getter.cursor(); - Click.Setter setter = getter.setter(); + Click.Setter setter = new Click.Setter(); for (int slot : slots) { if (getter.get(slot).isAir()) { setter.set(slot, cursor); @@ -132,7 +132,7 @@ public final class ClickProcessors { var pair = TransactionOperator.stackLeftN(amount).apply(ItemStack.AIR, cursor); if (pair == null) return Click.Result.NOTHING; - return getter.setter().cursor(pair.right()) + return new Click.Setter().cursor(pair.right()) .sideEffects(new Click.SideEffect.DropFromPlayer(pair.left())) .build(); } @@ -144,7 +144,7 @@ public final class ClickProcessors { var pair = TransactionOperator.stackLeftN(amount).apply(ItemStack.AIR, item); if (pair == null) return Click.Result.NOTHING; - return getter.setter().set(slot, pair.right()) + return new Click.Setter().set(slot, pair.right()) .sideEffects(new Click.SideEffect.DropFromPlayer(pair.left())) .build(); } @@ -187,18 +187,18 @@ public final class ClickProcessors { var selectedItem = getter.get(clickedSlot); if (hotbarItem.equals(selectedItem)) yield Click.Result.NOTHING; - yield getter.setter().setPlayer(hotbarSlot, selectedItem).set(clickedSlot, hotbarItem).build(); + yield new Click.Setter().set(clickedSlot, hotbarItem).setPlayer(hotbarSlot, selectedItem).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; - yield getter.setter().setPlayer(PlayerInventoryUtils.OFF_HAND_SLOT, selectedItem).set(slot, offhandItem).build(); + yield new Click.Setter().set(slot, offhandItem).setPlayer(PlayerInventoryUtils.OFF_HAND_SLOT, selectedItem).build(); } - case Click.Info.CreativeSetItem(int slot, ItemStack item) -> getter.setter().set(slot, item).build(); + case Click.Info.CreativeSetItem(int slot, ItemStack item) -> new Click.Setter().set(slot, item).build(); case Click.Info.CreativeDropItem(ItemStack item) -> - getter.setter().sideEffects(new Click.SideEffect.DropFromPlayer(item)).build(); + new Click.Setter().sideEffects(new Click.SideEffect.DropFromPlayer(item)).build(); }; } diff --git a/src/test/java/net/minestom/server/inventory/click/ClickUtils.java b/src/test/java/net/minestom/server/inventory/click/ClickUtils.java index 0e2d5b75f..bdc9cac7f 100644 --- a/src/test/java/net/minestom/server/inventory/click/ClickUtils.java +++ b/src/test/java/net/minestom/server/inventory/click/ClickUtils.java @@ -8,12 +8,12 @@ import net.minestom.server.item.ItemStack; import net.minestom.server.network.packet.client.play.ClientClickWindowPacket; import net.minestom.server.network.packet.server.SendablePacket; import net.minestom.server.network.player.PlayerConnection; +import net.minestom.server.utils.inventory.PlayerInventoryUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.net.SocketAddress; -import java.util.List; -import java.util.UUID; +import java.util.*; import java.util.function.UnaryOperator; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -52,18 +52,63 @@ public final class ClickUtils { var player = createPlayer(); var inventory = createInventory(); - ContainerInventory.apply(initialChanges.apply(new Click.Setter(inventory.getSize())).build(), player, inventory); - var changes = inventory.handleClick(player, info); - assertEquals(expectedChanges.apply(new Click.Setter(inventory.getSize())).build(), changes); + var expected = expectedChanges.apply(new Click.Setter()).build(); + + ContainerInventory.apply(initialChanges.apply(new Click.Setter()).build(), player, inventory); + var actual = inventory.handleClick(player, info); + + assertChanges(expected, actual, inventory.getSize()); } public static void assertPlayerClick(@NotNull UnaryOperator initialChanges, @NotNull Click.Info info, @NotNull UnaryOperator expectedChanges) { var player = createPlayer(); var inventory = player.getInventory(); - ContainerInventory.apply(initialChanges.apply(new Click.Setter(inventory.getSize())).build(), player, inventory); - var changes = inventory.handleClick(player, info); - assertEquals(expectedChanges.apply(new Click.Setter(inventory.getSize())).build(), changes); + var expected = expectedChanges.apply(new Click.Setter()).build(); + + ContainerInventory.apply(initialChanges.apply(new Click.Setter()).build(), player, inventory); + var actual = inventory.handleClick(player, info); + + assertChanges(expected, actual, inventory.getSize()); + } + + public static void assertChanges(Click.Result expected, Click.Result actual, int size) { + if (expected == null || actual == null) { + assertEquals(expected, actual); + return; + } + + assertEquals(foldMain(expected.changes(), size), foldMain(actual.changes(), size)); + assertEquals(foldPlayer(expected.changes(), size), foldPlayer(actual.changes(), size)); + + assertEquals(expected.newCursorItem(), actual.newCursorItem()); + assertEquals(expected.sideEffects(), actual.sideEffects()); + } + + private static Map foldMain(List changes, int size) { + Map map = new HashMap<>(); + + for (var change : changes) { + if (change instanceof Click.Change.Main(int slot, ItemStack item) && slot < size) { + map.put(slot, item); + } + } + + return map; + } + + private static Map foldPlayer(List changes, int size) { + Map map = new HashMap<>(); + + for (var change : changes) { + if (change instanceof Click.Change.Main(int slot, ItemStack item) && slot >= size) { + map.put(PlayerInventoryUtils.protocolToMinestom(slot, size), item); + } else if (change instanceof Click.Change.Player(int slot, ItemStack item)) { + map.put(slot, item); + } + } + + return map; } public static void assertProcessed(@NotNull Click.Preprocessor preprocessor, @NotNull Player player, @Nullable Click.Info info, @NotNull ClientClickWindowPacket packet) {