Add Click.Change

This commit is contained in:
GoldenStack 2024-04-12 13:21:04 -05:00
parent 1dc9a20541
commit e9d957814e
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.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) {

View File

@ -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<Integer, ItemStack> main = new HashMap<>();
private final Map<Integer, ItemStack> player = new HashMap<>();
private final List<Change> 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<Integer, ItemStack> changes,
@NotNull Map<Integer, ItemStack> 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<Change> 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);
}
}

View File

@ -34,9 +34,9 @@ public final class ClickProcessors {
Pair<ItemStack, ItemStack> 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<ItemStack, ItemStack> 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<ItemStack, ItemStack> 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<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);
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<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);
return !result.left().equals(cursor) ?
@ -106,7 +106,7 @@ public final class ClickProcessors {
if (cursor.isAir()) return Click.Result.NOTHING;
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);
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) {
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();
};
}

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.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<Click.Setter> initialChanges, @NotNull Click.Info info, @NotNull UnaryOperator<Click.Setter> 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<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) {