Store all processors in ClickProcessors

This commit is contained in:
themode 2024-04-11 22:42:02 +02:00 committed by mworzala
parent 17d907305f
commit 447475c44e
No known key found for this signature in database
GPG Key ID: B148F922E64797C7
5 changed files with 121 additions and 102 deletions

View File

@ -7,6 +7,7 @@ import net.minestom.server.event.inventory.InventoryClickEvent;
import net.minestom.server.event.inventory.InventoryPostClickEvent;
import net.minestom.server.event.inventory.InventoryPreClickEvent;
import net.minestom.server.inventory.click.Click;
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;
@ -14,7 +15,6 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
/**
* Represents an inventory which can be viewed by a collection of {@link Player}.
@ -26,12 +26,14 @@ public non-sealed class ContainerInventory extends InventoryImpl {
/**
* Processes a click, returning a result. This will call events for the click.
*
* @param inventory the clicked inventory (could be a player inventory)
* @param player the player who clicked
* @param info the click info describing the click
* @param player the player who clicked
* @param info the click info describing the click
* @return the click result, or null if the click did not occur
*/
public static @Nullable Click.Result handleClick(@NotNull Inventory inventory, @NotNull Player player, @NotNull Click.Info info, @NotNull BiFunction<Click.@NotNull Info, Click.@NotNull Getter, Click.@NotNull Result> processor) {
public static @Nullable Click.Result handleClick(@NotNull Inventory inventory, @NotNull Player player, @NotNull Click.Info info,
@NotNull ClickProcessors.InventoryProcessor processor) {
PlayerInventory playerInventory = player.getInventory();
InventoryPreClickEvent preClickEvent = new InventoryPreClickEvent(playerInventory, inventory, player, info);
@ -141,6 +143,12 @@ public non-sealed class ContainerInventory extends InventoryImpl {
update();
}
@Override
public @Nullable Click.Result handleClick(@NotNull Player player, Click.@NotNull Info info) {
return ContainerInventory.handleClick(this, player, info,
ClickProcessors.PROCESSORS_MAP.getOrDefault(inventoryType, ClickProcessors.GENERIC_PROCESSOR));
}
@Override
public byte getWindowId() {
return id;

View File

@ -3,8 +3,6 @@ package net.minestom.server.inventory;
import net.minestom.server.entity.Player;
import net.minestom.server.event.EventDispatcher;
import net.minestom.server.event.inventory.InventoryItemChangeEvent;
import net.minestom.server.inventory.click.Click;
import net.minestom.server.inventory.click.ClickProcessors;
import net.minestom.server.item.ItemStack;
import net.minestom.server.network.packet.server.play.CloseWindowPacket;
import net.minestom.server.network.packet.server.play.SetSlotPacket;
@ -13,7 +11,6 @@ import net.minestom.server.tag.TagHandler;
import net.minestom.server.utils.MathUtils;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
@ -110,19 +107,6 @@ sealed abstract class InventoryImpl implements Inventory permits ContainerInvent
player.sendPacket(new WindowItemsPacket(getWindowId(), 0, List.of(itemStacks), player.getInventory().getCursorItem()));
}
@Override
public @Nullable Click.Result handleClick(@NotNull Player player, @NotNull Click.Info info) {
var processor = ClickProcessors.standard(
(builder, item, slot) -> slot >= getSize() ?
IntStream.range(0, getSize()) :
PlayerInventory.getInnerShiftClickSlots(getSize()),
(builder, item, slot) -> IntStream.concat(
IntStream.range(0, getSize()),
PlayerInventory.getInnerDoubleClickSlots(getSize())
));
return ContainerInventory.handleClick(this, player, info, processor);
}
@Override
public @NotNull ItemStack getItemStack(int slot) {
return (ItemStack) ITEM_UPDATER.getVolatile(itemStacks, slot);
@ -168,7 +152,6 @@ sealed abstract class InventoryImpl implements Inventory permits ContainerInvent
*/
protected final void safeItemInsert(int slot, @NotNull ItemStack itemStack, boolean sendPacket) {
lock.lock();
try {
ItemStack previous = itemStacks[slot];
if (itemStack.equals(previous)) return; // Avoid sending updates if the item has not changed
@ -189,7 +172,6 @@ sealed abstract class InventoryImpl implements Inventory permits ContainerInvent
@Override
public void replaceItemStack(int slot, @NotNull UnaryOperator<@NotNull ItemStack> operator) {
lock.lock();
try {
var currentItem = getItemStack(slot);
setItemStack(slot, operator.apply(currentItem));
@ -201,12 +183,10 @@ sealed abstract class InventoryImpl implements Inventory permits ContainerInvent
@Override
public void clear() {
lock.lock();
try {
for (Player viewer : getViewers()) {
viewer.getInventory().setCursorItem(ItemStack.AIR, false);
}
// Clear the item array
for (int i = 0; i < size; i++) {
safeItemInsert(i, ItemStack.AIR, false);
@ -294,5 +274,4 @@ sealed abstract class InventoryImpl implements Inventory permits ContainerInvent
}
return result;
}
}

View File

@ -7,15 +7,12 @@ import net.minestom.server.event.item.EntityEquipEvent;
import net.minestom.server.inventory.click.Click;
import net.minestom.server.inventory.click.ClickProcessors;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import net.minestom.server.network.packet.server.play.SetSlotPacket;
import net.minestom.server.network.packet.server.play.WindowItemsPacket;
import net.minestom.server.utils.inventory.PlayerInventoryUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;
@ -171,42 +168,7 @@ public non-sealed class PlayerInventory extends InventoryImpl {
@Override
public @Nullable Click.Result handleClick(@NotNull Player player, @NotNull Click.Info info) {
var processor = ClickProcessors.standard(
(getter, item, slot) -> {
List<Integer> slots = new ArrayList<>();
final EquipmentSlot equipmentSlot = item.material().registry().equipmentSlot();
if (equipmentSlot != null && slot != equipmentSlot.armorSlot()) {
slots.add(equipmentSlot.armorSlot());
}
if (item.material() == Material.SHIELD && slot != OFF_HAND_SLOT) {
slots.add(OFF_HAND_SLOT);
}
if (slot < 9 || slot > 35) {
IntStream.range(9, 36).forEach(slots::add);
}
if (slot < 0 || slot > 8) {
IntStream.range(0, 9).forEach(slots::add);
}
if (slot == CRAFT_RESULT) {
Collections.reverse(slots);
}
return slots.stream().mapToInt(i -> i);
},
(getter, item, slot) -> Stream.of(
IntStream.range(CRAFT_SLOT_1, CRAFT_SLOT_4 + 1), // 1-4
IntStream.range(HELMET_SLOT, BOOTS_SLOT + 1), // 5-8
IntStream.range(9, 36), // 9-35
IntStream.range(0, 9), // 36-44
IntStream.of(OFF_HAND_SLOT) // 45
).flatMapToInt(i -> i)
);
return ContainerInventory.handleClick(this, player, info, processor);
return ContainerInventory.handleClick(this, player, info, ClickProcessors.PLAYER_PROCESSOR);
}
public @NotNull ItemStack getEquipment(@NotNull EquipmentSlot slot, int heldSlot) {

View File

@ -1,18 +1,27 @@
package net.minestom.server.inventory.click;
import it.unimi.dsi.fastutil.Pair;
import net.minestom.server.entity.EquipmentSlot;
import net.minestom.server.inventory.InventoryType;
import net.minestom.server.inventory.PlayerInventory;
import net.minestom.server.inventory.TransactionOperator;
import net.minestom.server.inventory.TransactionType;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import net.minestom.server.item.StackingRule;
import net.minestom.server.utils.inventory.PlayerInventoryUtils;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import static java.util.Map.entry;
import static net.minestom.server.utils.inventory.PlayerInventoryUtils.*;
/**
* Provides standard implementations of most click functions.
@ -151,14 +160,17 @@ public final class ClickProcessors {
* @param shiftClickSlots the shift click slot supplier
* @param doubleClickSlots the double click slot supplier
*/
public static @NotNull BiFunction<Click.@NotNull Info, Click.@NotNull Getter, Click.@NotNull Result> standard(@NotNull SlotSuggestor shiftClickSlots, @NotNull SlotSuggestor doubleClickSlots) {
public static ClickProcessors.@NotNull InventoryProcessor standard(@NotNull SlotSuggestor shiftClickSlots, @NotNull SlotSuggestor doubleClickSlots) {
return (info, getter) -> switch (info) {
case Click.Info.Left(int slot) -> leftClick(slot, getter);
case Click.Info.Right(int slot) -> rightClick(slot, getter);
case Click.Info.Middle(int slot) -> middleClick(slot, getter);
case Click.Info.LeftShift(int slot) -> shiftClick(slot, shiftClickSlots.getList(getter, getter.get(slot), slot), getter);
case Click.Info.RightShift(int slot) -> shiftClick(slot, shiftClickSlots.getList(getter, getter.get(slot), slot), getter);
case Click.Info.Double(int slot) -> doubleClick(doubleClickSlots.getList(getter, getter.get(slot), slot), getter);
case Click.Info.LeftShift(int slot) ->
shiftClick(slot, shiftClickSlots.getList(getter, getter.get(slot), slot), getter);
case Click.Info.RightShift(int slot) ->
shiftClick(slot, shiftClickSlots.getList(getter, getter.get(slot), slot), getter);
case Click.Info.Double(int slot) ->
doubleClick(doubleClickSlots.getList(getter, getter.get(slot), slot), getter);
case Click.Info.LeftDrag(List<Integer> slots) -> {
int cursorAmount = RULE.getAmount(getter.cursor());
int amount = (int) Math.floor(cursorAmount / (double) slots.size());
@ -166,7 +178,8 @@ public final class ClickProcessors {
}
case Click.Info.RightDrag(List<Integer> slots) -> dragClick(1, slots, getter);
case Click.Info.MiddleDrag(List<Integer> slots) -> middleDragClick(slots, getter);
case Click.Info.DropSlot(int slot, boolean all) -> dropFromSlot(slot, all ? RULE.getAmount(getter.get(slot)) : 1, getter);
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;
@ -185,10 +198,14 @@ public final class ClickProcessors {
yield getter.setter().setPlayer(PlayerInventoryUtils.OFF_HAND_SLOT, selectedItem).set(slot, offhandItem).build();
}
case Click.Info.CreativeSetItem(int slot, ItemStack item) -> getter.setter().set(slot, item).build();
case Click.Info.CreativeDropItem(ItemStack item) -> getter.setter().sideEffects(new Click.SideEffect.DropFromPlayer(item)).build();
case Click.Info.CreativeDropItem(ItemStack item) ->
getter.setter().sideEffects(new Click.SideEffect.DropFromPlayer(item)).build();
};
}
public interface InventoryProcessor extends BiFunction<Click.Info, Click.Getter, Click.Result> {
}
/**
* A generic interface for providing options for clicks like shift clicks and double clicks.<br>
* This addresses the issue of certain click operations only being able to interact with certain slots: for example,
@ -213,4 +230,89 @@ public final class ClickProcessors {
return get(builder, item, slot).boxed().toList();
}
}
/**
* Handle player inventory (without any container open).
*/
public static final InventoryProcessor PLAYER_PROCESSOR = ClickProcessors.standard(
(getter, item, slot) -> {
List<Integer> slots = new ArrayList<>();
final EquipmentSlot equipmentSlot = item.material().registry().equipmentSlot();
if (equipmentSlot != null && slot != equipmentSlot.armorSlot()) {
slots.add(equipmentSlot.armorSlot());
}
if (item.material() == Material.SHIELD && slot != OFF_HAND_SLOT) {
slots.add(OFF_HAND_SLOT);
}
if (slot < 9 || slot > 35) IntStream.range(9, 36).forEach(slots::add);
if (slot < 0 || slot > 8) IntStream.range(0, 9).forEach(slots::add);
if (slot == CRAFT_RESULT) {
Collections.reverse(slots);
}
return slots.stream().mapToInt(i -> i);
},
(getter, item, slot) -> Stream.of(
IntStream.range(CRAFT_SLOT_1, CRAFT_SLOT_4 + 1), // 1-4
IntStream.range(HELMET_SLOT, BOOTS_SLOT + 1), // 5-8
IntStream.range(9, 36), // 9-35
IntStream.range(0, 9), // 36-44
IntStream.of(OFF_HAND_SLOT) // 45
).flatMapToInt(i -> i)
);
/**
* Assumes all the container's slots to be accessible.
*/
public static final InventoryProcessor GENERIC_PROCESSOR = ClickProcessors.standard(
(builder, item, slot) -> {
final int size = builder.mainSize();
return slot >= size ?
IntStream.range(0, size) :
PlayerInventory.getInnerShiftClickSlots(size);
},
(builder, item, slot) -> {
final int size = builder.mainSize();
return IntStream.concat(
IntStream.range(0, size),
PlayerInventory.getInnerDoubleClickSlots(size)
);
});
// SPECIALIZED PROCESSORS DEFINITIONS
/**
* Client prediction appears to disallow shift clicking into furnace inventories.<br>
* Instead:
* - Shift clicks in the inventory go to the player inventory like normal
* - Shift clicks in the hotbar go to the storage
* - Shift clicks in the storage go to the hotbar
*/
public static final InventoryProcessor FURNACE_PROCESSOR = ClickProcessors.standard(
(builder, item, slot) -> {
final int size = builder.mainSize();
if (slot < size) {
return PlayerInventory.getInnerShiftClickSlots(size);
} else if (slot < size + 27) {
return IntStream.range(27, 36).map(i -> i + size);
} else {
return IntStream.range(0, 27).map(i -> i + size);
}
},
(builder, item, slot) -> {
final int size = builder.mainSize();
return IntStream.concat(
IntStream.range(0, size),
PlayerInventory.getInnerDoubleClickSlots(size)
);
});
public static final Map<InventoryType, InventoryProcessor> PROCESSORS_MAP = Map.ofEntries(
entry(InventoryType.FURNACE, FURNACE_PROCESSOR)
);
}

View File

@ -1,16 +1,10 @@
package net.minestom.server.inventory.type;
import net.kyori.adventure.text.Component;
import net.minestom.server.entity.Player;
import net.minestom.server.inventory.ContainerInventory;
import net.minestom.server.inventory.InventoryProperty;
import net.minestom.server.inventory.InventoryType;
import net.minestom.server.inventory.PlayerInventory;
import net.minestom.server.inventory.click.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.stream.IntStream;
public class FurnaceInventory extends ContainerInventory {
@ -27,32 +21,6 @@ public class FurnaceInventory extends ContainerInventory {
super(InventoryType.FURNACE, title);
}
/**
* Client prediction appears to disallow shift clicking into furnace inventories.<br>
* Instead:
* - Shift clicks in the inventory go to the player inventory like normal
* - Shift clicks in the hotbar go to the storage
* - Shift clicks in the storage go to the hotbar
*/
@Override
public @Nullable Click.Result handleClick(@NotNull Player player, @NotNull Click.Info info) {
var processor = ClickProcessors.standard(
(builder, item, slot) -> {
if (slot < getSize()) {
return PlayerInventory.getInnerShiftClickSlots(getSize());
} else if (slot < getSize() + 27) {
return IntStream.range(27, 36).map(i -> i + getSize());
} else {
return IntStream.range(0, 27).map(i -> i + getSize());
}
},
(builder, item, slot) -> IntStream.concat(
IntStream.range(0, getSize()),
PlayerInventory.getInnerDoubleClickSlots(getSize())
));
return ContainerInventory.handleClick(this, player, info, processor);
}
/**
* Represents the amount of tick until the fire icon come empty.
*