mirror of https://github.com/Minestom/Minestom.git
Add Click.Change
This commit is contained in:
parent
1dc9a20541
commit
e9d957814e
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue