Add Click.Change

This commit is contained in:
GoldenStack 2024-04-12 13:21:04 -05:00 committed by mworzala
parent ea7ef09be7
commit 511c3d3e2e
No known key found for this signature in database
GPG Key ID: B148F922E64797C7
4 changed files with 97 additions and 58 deletions

View File

@ -11,6 +11,7 @@ import net.minestom.server.inventory.click.ClickProcessors;
import net.minestom.server.item.ItemStack; import net.minestom.server.item.ItemStack;
import net.minestom.server.network.packet.server.play.OpenWindowPacket; import net.minestom.server.network.packet.server.play.OpenWindowPacket;
import net.minestom.server.network.packet.server.play.WindowPropertyPacket; 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.NotNull;
import org.jetbrains.annotations.Nullable; 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) { public static void apply(@NotNull Click.Result result, @NotNull Player player, @NotNull Inventory inventory) {
for (var entry : result.changes().entrySet()) { PlayerInventory playerInventory = player.getInventory();
inventory.setItemStack(entry.getKey(), entry.getValue());
}
for (var entry : result.playerInventoryChanges().entrySet()) { for (var change : result.changes()) {
player.getInventory().setItemStack(entry.getKey(), entry.getValue()); 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) { if (result.newCursorItem() != null) {
player.getInventory().setCursorItem(result.newCursorItem()); playerInventory.setCursorItem(result.newCursorItem());
} }
if (result.sideEffects() instanceof Click.SideEffect.DropFromPlayer drop) { if (result.sideEffects() instanceof Click.SideEffect.DropFromPlayer drop) {

View File

@ -7,9 +7,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.IntFunction; import java.util.function.IntFunction;
@ -204,36 +202,28 @@ public final class Click {
return player.apply(PlayerInventoryUtils.protocolToMinestom(slot, mainSize())); return player.apply(PlayerInventoryUtils.protocolToMinestom(slot, mainSize()));
} }
} }
}
public @NotNull Click.Setter setter() { public sealed interface Change {
return new Setter(mainSize); record Main(int slot, @NotNull ItemStack item) implements Change {}
} record Player(int slot, @NotNull ItemStack item) implements Change {}
} }
public static final class Setter { public static final class Setter {
private final Map<Integer, ItemStack> main = new HashMap<>(); private final List<Change> changes = new ArrayList<>();
private final Map<Integer, ItemStack> player = new HashMap<>();
private @Nullable ItemStack cursor; private @Nullable ItemStack cursor;
private @Nullable SideEffect sideEffect; private @Nullable SideEffect sideEffect;
private final int clickedSize; Setter() {
Setter(int clickedSize) {
this.clickedSize = clickedSize;
} }
public @NotNull Setter set(int slot, @NotNull ItemStack item) { public @NotNull Setter set(int slot, @NotNull ItemStack item) {
if (slot >= clickedSize) { changes.add(new Change.Main(slot, item));
int converted = PlayerInventoryUtils.protocolToMinestom(slot, clickedSize); return this;
return setPlayer(converted, item);
} else {
main.put(slot, item);
return this;
}
} }
public @NotNull Setter setPlayer(int slot, @NotNull ItemStack item) { public @NotNull Setter setPlayer(int slot, @NotNull ItemStack item) {
player.put(slot, item); changes.add(new Change.Player(slot, item));
return this; return this;
} }
@ -248,26 +238,22 @@ public final class Click {
} }
public @NotNull Click.Result build() { 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. * 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 list of changes that will occur
* @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 newCursorItem the player's cursor item after this click. Null indicates no change
* @param sideEffects the side effects of this click * @param sideEffects the side effects of this click
*/ */
public record Result(@NotNull Map<Integer, ItemStack> changes, public record Result(@NotNull List<Change> changes, @Nullable ItemStack newCursorItem, @Nullable Click.SideEffect sideEffects) {
@NotNull Map<Integer, ItemStack> playerInventoryChanges, public static final Result NOTHING = new Result(List.of(), null, null);
@Nullable ItemStack newCursorItem, @Nullable Click.SideEffect sideEffects) {
public static final Result NOTHING = new Result(Map.of(), Map.of(), null, null);
public Result { public Result {
changes = Map.copyOf(changes); changes = List.copyOf(changes);
playerInventoryChanges = Map.copyOf(playerInventoryChanges);
} }
} }

View File

@ -34,9 +34,9 @@ public final class ClickProcessors {
Pair<ItemStack, ItemStack> pair = TransactionOperator.STACK_LEFT.apply(clickedItem, cursor); Pair<ItemStack, ItemStack> pair = TransactionOperator.STACK_LEFT.apply(clickedItem, cursor);
if (pair != null) { // Stackable items, combine their counts 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 } 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 { } else {
return Click.Result.NOTHING; return Click.Result.NOTHING;
} }
@ -51,20 +51,20 @@ public final class ClickProcessors {
int newAmount = (int) Math.ceil(RULE.getAmount(clickedItem) / 2d); int newAmount = (int) Math.ceil(RULE.getAmount(clickedItem) / 2d);
Pair<ItemStack, ItemStack> cursorSlot = TransactionOperator.stackLeftN(newAmount).apply(cursor, clickedItem); Pair<ItemStack, ItemStack> 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(); new Click.Setter().cursor(cursorSlot.left()).set(slot, cursorSlot.right()).build();
} else if (clickedItem.isAir() || RULE.canBeStacked(clickedItem, cursor)) { // Can add, transfer one over } else if (clickedItem.isAir() || RULE.canBeStacked(clickedItem, cursor)) { // Can add, transfer one over
Pair<ItemStack, ItemStack> slotCursor = TransactionOperator.stackLeftN(1).apply(clickedItem, cursor); Pair<ItemStack, ItemStack> 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(); new Click.Setter().set(slot, slotCursor.left()).cursor(slotCursor.right()).build();
} else { // Two existing of items of different types, so switch } 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) { public static @NotNull Click.Result middleClick(int slot, @NotNull Click.Getter getter) {
var item = getter.get(slot); var item = getter.get(slot);
if (getter.cursor().isAir() && !item.isAir()) { 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 { } else {
return Click.Result.NOTHING; return Click.Result.NOTHING;
} }
@ -77,7 +77,7 @@ public final class ClickProcessors {
slots.removeIf(i -> i == slot); slots.removeIf(i -> i == slot);
Pair<ItemStack, Map<Integer, ItemStack>> result = TransactionType.add(slots, slots).process(clicked, getter::get); Pair<ItemStack, Map<Integer, ItemStack>> 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); result.right().forEach(setter::set);
return !result.left().equals(clicked) ? 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); var stacked = TransactionType.general(TransactionOperator.filter(TransactionOperator.STACK_RIGHT, (left, right) -> RULE.getAmount(left) == RULE.getMaxSize(left)), slots);
Pair<ItemStack, Map<Integer, ItemStack>> result = TransactionType.join(unstacked, stacked).process(cursor, getter::get); Pair<ItemStack, Map<Integer, ItemStack>> 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); result.right().forEach(setter::set);
return !result.left().equals(cursor) ? return !result.left().equals(cursor) ?
@ -106,7 +106,7 @@ public final class ClickProcessors {
if (cursor.isAir()) return Click.Result.NOTHING; if (cursor.isAir()) return Click.Result.NOTHING;
Pair<ItemStack, Map<Integer, ItemStack>> result = TransactionType.general(TransactionOperator.stackLeftN(countPerSlot), slots).process(cursor, getter::get); Pair<ItemStack, Map<Integer, ItemStack>> 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); result.right().forEach(setter::set);
return !result.left().equals(cursor) ? return !result.left().equals(cursor) ?
@ -116,7 +116,7 @@ public final class ClickProcessors {
public static @NotNull Click.Result middleDragClick(@NotNull List<Integer> slots, @NotNull Click.Getter getter) { public static @NotNull Click.Result middleDragClick(@NotNull List<Integer> slots, @NotNull Click.Getter getter) {
final ItemStack cursor = getter.cursor(); final ItemStack cursor = getter.cursor();
Click.Setter setter = getter.setter(); Click.Setter setter = new Click.Setter();
for (int slot : slots) { for (int slot : slots) {
if (getter.get(slot).isAir()) { if (getter.get(slot).isAir()) {
setter.set(slot, cursor); setter.set(slot, cursor);
@ -132,7 +132,7 @@ public final class ClickProcessors {
var pair = TransactionOperator.stackLeftN(amount).apply(ItemStack.AIR, cursor); 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()) return new Click.Setter().cursor(pair.right())
.sideEffects(new Click.SideEffect.DropFromPlayer(pair.left())) .sideEffects(new Click.SideEffect.DropFromPlayer(pair.left()))
.build(); .build();
} }
@ -144,7 +144,7 @@ public final class ClickProcessors {
var pair = TransactionOperator.stackLeftN(amount).apply(ItemStack.AIR, item); 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()) return new Click.Setter().set(slot, pair.right())
.sideEffects(new Click.SideEffect.DropFromPlayer(pair.left())) .sideEffects(new Click.SideEffect.DropFromPlayer(pair.left()))
.build(); .build();
} }
@ -187,18 +187,18 @@ public final class ClickProcessors {
var selectedItem = getter.get(clickedSlot); 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(); yield new Click.Setter().set(clickedSlot, hotbarItem).setPlayer(hotbarSlot, selectedItem).build();
} }
case Click.Info.OffhandSwap(int slot) -> { case Click.Info.OffhandSwap(int slot) -> {
var offhandItem = getter.player().apply(PlayerInventoryUtils.OFF_HAND_SLOT); var offhandItem = getter.player().apply(PlayerInventoryUtils.OFF_HAND_SLOT);
var selectedItem = getter.get(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(); 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) -> 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();
}; };
} }

View File

@ -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.client.play.ClientClickWindowPacket;
import net.minestom.server.network.packet.server.SendablePacket; import net.minestom.server.network.packet.server.SendablePacket;
import net.minestom.server.network.player.PlayerConnection; import net.minestom.server.network.player.PlayerConnection;
import net.minestom.server.utils.inventory.PlayerInventoryUtils;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.util.List; import java.util.*;
import java.util.UUID;
import java.util.function.UnaryOperator; import java.util.function.UnaryOperator;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
@ -52,18 +52,63 @@ public final class ClickUtils {
var player = createPlayer(); var player = createPlayer();
var inventory = createInventory(); var inventory = createInventory();
ContainerInventory.apply(initialChanges.apply(new Click.Setter(inventory.getSize())).build(), player, inventory); var expected = expectedChanges.apply(new Click.Setter()).build();
var changes = inventory.handleClick(player, info);
assertEquals(expectedChanges.apply(new Click.Setter(inventory.getSize())).build(), changes); 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<Click.Setter> initialChanges, @NotNull Click.Info info, @NotNull UnaryOperator<Click.Setter> expectedChanges) { public static void assertPlayerClick(@NotNull UnaryOperator<Click.Setter> initialChanges, @NotNull Click.Info info, @NotNull UnaryOperator<Click.Setter> expectedChanges) {
var player = createPlayer(); var player = createPlayer();
var inventory = player.getInventory(); var inventory = player.getInventory();
ContainerInventory.apply(initialChanges.apply(new Click.Setter(inventory.getSize())).build(), player, inventory); var expected = expectedChanges.apply(new Click.Setter()).build();
var changes = inventory.handleClick(player, info);
assertEquals(expectedChanges.apply(new Click.Setter(inventory.getSize())).build(), changes); 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<Integer, ItemStack> foldMain(List<Click.Change> changes, int size) {
Map<Integer, ItemStack> 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<Integer, ItemStack> foldPlayer(List<Click.Change> changes, int size) {
Map<Integer, ItemStack> 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) { public static void assertProcessed(@NotNull Click.Preprocessor preprocessor, @NotNull Player player, @Nullable Click.Info info, @NotNull ClientClickWindowPacket packet) {